From ee4079fa85a8cdd159e992c2632568d2e8a73883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Kar=C3=A9kinian?= Date: Tue, 21 Jul 2015 19:45:23 +0200 Subject: [PATCH] Initial Chef repository --- .chef/knife.rb | 8 + Batali | 29 + Gemfile | 6 + Gemfile.lock | 189 + README.md | 14 + cookbooks/7-zip/CHANGELOG.md | 13 + cookbooks/7-zip/README.md | 50 + cookbooks/7-zip/attributes/default.rb | 31 + cookbooks/7-zip/metadata.json | 31 + cookbooks/7-zip/metadata.rb | 10 + cookbooks/7-zip/recipes/default.rb | 31 + cookbooks/apache2/CHANGELOG.md | 414 + cookbooks/apache2/README.md | 727 + cookbooks/apache2/attributes/default.rb | 354 + cookbooks/apache2/attributes/mod_auth_cas.rb | 21 + .../apache2/attributes/mod_auth_openid.rb | 34 + cookbooks/apache2/attributes/mod_fastcgi.rb | 21 + cookbooks/apache2/attributes/mod_pagespeed.rb | 25 + cookbooks/apache2/attributes/mod_php5.rb | 24 + cookbooks/apache2/attributes/mod_ssl.rb | 59 + cookbooks/apache2/definitions/apache_conf.rb | 45 + .../apache2/definitions/apache_config.rb | 42 + cookbooks/apache2/definitions/apache_mod.rb | 28 + .../apache2/definitions/apache_module.rb | 58 + cookbooks/apache2/definitions/apache_site.rb | 44 + cookbooks/apache2/definitions/web_app.rb | 48 + .../default/apache2_module_conf_generate.pl | 41 + cookbooks/apache2/metadata.json | 1 + cookbooks/apache2/recipes/default.rb | 220 + .../apache2/recipes/mod_access_compat.rb | 20 + cookbooks/apache2/recipes/mod_actions.rb | 22 + cookbooks/apache2/recipes/mod_alias.rb | 22 + cookbooks/apache2/recipes/mod_allowmethods.rb | 20 + cookbooks/apache2/recipes/mod_apreq2.rb | 50 + cookbooks/apache2/recipes/mod_asis.rb | 20 + cookbooks/apache2/recipes/mod_auth_basic.rb | 20 + cookbooks/apache2/recipes/mod_auth_cas.rb | 68 + cookbooks/apache2/recipes/mod_auth_digest.rb | 20 + cookbooks/apache2/recipes/mod_auth_form.rb | 20 + cookbooks/apache2/recipes/mod_auth_openid.rb | 122 + cookbooks/apache2/recipes/mod_authn_anon.rb | 20 + cookbooks/apache2/recipes/mod_authn_core.rb | 23 + cookbooks/apache2/recipes/mod_authn_dbd.rb | 20 + cookbooks/apache2/recipes/mod_authn_dbm.rb | 20 + cookbooks/apache2/recipes/mod_authn_file.rb | 20 + .../apache2/recipes/mod_authn_socache.rb | 20 + cookbooks/apache2/recipes/mod_authnz_ldap.rb | 22 + cookbooks/apache2/recipes/mod_authz_core.rb | 24 + cookbooks/apache2/recipes/mod_authz_dbd.rb | 20 + cookbooks/apache2/recipes/mod_authz_dbm.rb | 20 + .../apache2/recipes/mod_authz_default.rb | 21 + .../apache2/recipes/mod_authz_groupfile.rb | 20 + cookbooks/apache2/recipes/mod_authz_host.rb | 20 + cookbooks/apache2/recipes/mod_authz_owner.rb | 20 + cookbooks/apache2/recipes/mod_authz_user.rb | 20 + cookbooks/apache2/recipes/mod_autoindex.rb | 22 + cookbooks/apache2/recipes/mod_buffer.rb | 20 + cookbooks/apache2/recipes/mod_cache.rb | 20 + cookbooks/apache2/recipes/mod_cache_disk.rb | 22 + .../apache2/recipes/mod_cache_socache.rb | 20 + cookbooks/apache2/recipes/mod_cgi.rb | 26 + cookbooks/apache2/recipes/mod_cgid.rb | 23 + cookbooks/apache2/recipes/mod_charset_lite.rb | 20 + cookbooks/apache2/recipes/mod_cloudflare.rb | 30 + cookbooks/apache2/recipes/mod_data.rb | 20 + cookbooks/apache2/recipes/mod_dav.rb | 20 + cookbooks/apache2/recipes/mod_dav_fs.rb | 23 + cookbooks/apache2/recipes/mod_dav_lock.rb | 21 + cookbooks/apache2/recipes/mod_dav_svn.rb | 39 + cookbooks/apache2/recipes/mod_dbd.rb | 20 + cookbooks/apache2/recipes/mod_deflate.rb | 22 + cookbooks/apache2/recipes/mod_dialup.rb | 20 + cookbooks/apache2/recipes/mod_dir.rb | 22 + cookbooks/apache2/recipes/mod_dump_io.rb | 20 + cookbooks/apache2/recipes/mod_echo.rb | 20 + cookbooks/apache2/recipes/mod_env.rb | 20 + cookbooks/apache2/recipes/mod_expires.rb | 20 + cookbooks/apache2/recipes/mod_ext_filter.rb | 20 + cookbooks/apache2/recipes/mod_fastcgi.rb | 64 + cookbooks/apache2/recipes/mod_fcgid.rb | 55 + cookbooks/apache2/recipes/mod_file_cache.rb | 20 + cookbooks/apache2/recipes/mod_filter.rb | 20 + cookbooks/apache2/recipes/mod_headers.rb | 20 + cookbooks/apache2/recipes/mod_heartbeat.rb | 20 + cookbooks/apache2/recipes/mod_heartmonitor.rb | 20 + cookbooks/apache2/recipes/mod_include.rb | 22 + cookbooks/apache2/recipes/mod_info.rb | 22 + cookbooks/apache2/recipes/mod_jk.rb | 30 + .../recipes/mod_lbmethod_bybusyness.rb | 20 + .../recipes/mod_lbmethod_byrequests.rb | 20 + .../apache2/recipes/mod_lbmethod_bytraffic.rb | 20 + .../apache2/recipes/mod_lbmethod_heartbeat.rb | 20 + cookbooks/apache2/recipes/mod_ldap.rb | 22 + cookbooks/apache2/recipes/mod_log_config.rb | 24 + cookbooks/apache2/recipes/mod_log_debug.rb | 20 + cookbooks/apache2/recipes/mod_log_forensic.rb | 20 + cookbooks/apache2/recipes/mod_logio.rb | 24 + cookbooks/apache2/recipes/mod_lua.rb | 20 + cookbooks/apache2/recipes/mod_macro.rb | 20 + cookbooks/apache2/recipes/mod_mime.rb | 22 + cookbooks/apache2/recipes/mod_mime_magic.rb | 22 + cookbooks/apache2/recipes/mod_negotiation.rb | 22 + cookbooks/apache2/recipes/mod_pagespeed.rb | 37 + cookbooks/apache2/recipes/mod_perl.rb | 53 + cookbooks/apache2/recipes/mod_php5.rb | 72 + cookbooks/apache2/recipes/mod_proxy.rb | 22 + cookbooks/apache2/recipes/mod_proxy_ajp.rb | 21 + .../apache2/recipes/mod_proxy_balancer.rb | 27 + .../apache2/recipes/mod_proxy_connect.rb | 20 + .../apache2/recipes/mod_proxy_express.rb | 20 + cookbooks/apache2/recipes/mod_proxy_fcgi.rb | 20 + cookbooks/apache2/recipes/mod_proxy_fdpass.rb | 20 + cookbooks/apache2/recipes/mod_proxy_ftp.rb | 22 + cookbooks/apache2/recipes/mod_proxy_html.rb | 25 + cookbooks/apache2/recipes/mod_proxy_http.rb | 22 + cookbooks/apache2/recipes/mod_proxy_scgi.rb | 20 + .../apache2/recipes/mod_proxy_wstunnel.rb | 20 + cookbooks/apache2/recipes/mod_python.rb | 44 + cookbooks/apache2/recipes/mod_ratelimit.rb | 20 + cookbooks/apache2/recipes/mod_reflector.rb | 20 + cookbooks/apache2/recipes/mod_remoteip.rb | 20 + cookbooks/apache2/recipes/mod_reqtimeout.rb | 22 + cookbooks/apache2/recipes/mod_request.rb | 20 + cookbooks/apache2/recipes/mod_rewrite.rb | 20 + cookbooks/apache2/recipes/mod_sed.rb | 20 + cookbooks/apache2/recipes/mod_session.rb | 20 + .../apache2/recipes/mod_session_cookie.rb | 20 + .../apache2/recipes/mod_session_crypto.rb | 20 + cookbooks/apache2/recipes/mod_session_dbd.rb | 20 + cookbooks/apache2/recipes/mod_setenvif.rb | 22 + .../apache2/recipes/mod_slotmem_plain.rb | 20 + cookbooks/apache2/recipes/mod_slotmem_shm.rb | 20 + cookbooks/apache2/recipes/mod_socache_dbm.rb | 20 + .../apache2/recipes/mod_socache_memcache.rb | 20 + .../apache2/recipes/mod_socache_shmcb.rb | 20 + cookbooks/apache2/recipes/mod_speling.rb | 20 + cookbooks/apache2/recipes/mod_ssl.rb | 49 + cookbooks/apache2/recipes/mod_status.rb | 22 + cookbooks/apache2/recipes/mod_substitute.rb | 20 + cookbooks/apache2/recipes/mod_suexec.rb | 20 + cookbooks/apache2/recipes/mod_systemd.rb | 20 + cookbooks/apache2/recipes/mod_unique_id.rb | 20 + cookbooks/apache2/recipes/mod_unixd.rb | 25 + cookbooks/apache2/recipes/mod_userdir.rb | 22 + cookbooks/apache2/recipes/mod_usertrack.rb | 20 + cookbooks/apache2/recipes/mod_vhost_alias.rb | 20 + cookbooks/apache2/recipes/mod_wsgi.rb | 34 + cookbooks/apache2/recipes/mod_xml2enc.rb | 20 + cookbooks/apache2/recipes/mod_xsendfile.rb | 38 + cookbooks/apache2/recipes/mpm_event.rb | 27 + cookbooks/apache2/recipes/mpm_prefork.rb | 27 + cookbooks/apache2/recipes/mpm_worker.rb | 27 + .../apache2/templates/default/a2disconf.erb | 532 + .../apache2/templates/default/a2dismod.erb | 532 + .../apache2/templates/default/a2dissite.erb | 532 + .../apache2/templates/default/a2enconf.erb | 532 + .../apache2/templates/default/a2enmod.erb | 532 + .../apache2/templates/default/a2ensite.erb | 532 + .../templates/default/apache2.conf.erb | 260 + .../templates/default/charset.conf.erb | 6 + .../templates/default/default-site.conf.erb | 71 + .../apache2/templates/default/envvars.erb | 43 + .../templates/default/etc-sysconfig-httpd.erb | 35 + .../apache2/templates/default/mods/README | 2 + .../templates/default/mods/actions.conf.erb | 9 + .../templates/default/mods/alias.conf.erb | 27 + .../templates/default/mods/auth_cas.conf.erb | 1 + .../templates/default/mods/auth_cas.load.erb | 1 + .../default/mods/authopenid.load.erb | 1 + .../templates/default/mods/autoindex.conf.erb | 100 + .../default/mods/cache_disk.conf.erb | 23 + .../templates/default/mods/cgid.conf.erb | 3 + .../templates/default/mods/dav_fs.conf.erb | 1 + .../templates/default/mods/deflate.conf.erb | 18 + .../templates/default/mods/dir.conf.erb | 3 + .../templates/default/mods/fastcgi.conf.erb | 5 + .../templates/default/mods/fcgid.conf.erb | 10 + .../templates/default/mods/include.conf.erb | 4 + .../templates/default/mods/include.erb | 4 + .../templates/default/mods/info.conf.erb | 19 + .../templates/default/mods/ldap.conf.erb | 4 + .../templates/default/mods/mime.conf.erb | 199 + .../default/mods/mime_magic.conf.erb | 3 + .../templates/default/mods/mpm_event.conf.erb | 32 + .../default/mods/mpm_prefork.conf.erb | 27 + .../default/mods/mpm_worker.conf.erb | 20 + .../default/mods/negotiation.conf.erb | 17 + .../templates/default/mods/pagespeed.conf.erb | 293 + .../templates/default/mods/php5.conf.erb | 37 + .../templates/default/mods/proxy.conf.erb | 23 + .../default/mods/proxy_balancer.conf.erb | 18 + .../templates/default/mods/proxy_ftp.conf.erb | 4 + .../default/mods/reqtimeout.conf.erb | 22 + .../templates/default/mods/setenvif.conf.erb | 28 + .../templates/default/mods/ssl.conf.erb | 108 + .../templates/default/mods/status.conf.erb | 42 + .../templates/default/mods/userdir.conf.erb | 17 + .../apache2/templates/default/port_apache.erb | 3 + .../apache2/templates/default/ports.conf.erb | 11 + .../templates/default/security.conf.erb | 45 + .../templates/default/web_app.conf.erb | 61 + cookbooks/application/CHANGELOG.md | 103 + cookbooks/application/README.md | 206 + cookbooks/application/libraries/default.rb | 189 + cookbooks/application/libraries/matchers.rb | 11 + cookbooks/application/metadata.json | 30 + cookbooks/application/providers/default.rb | 191 + cookbooks/application/recipes/default.rb | 19 + cookbooks/application/resources/default.rb | 178 + .../templates/default/deploy-ssh-wrapper.erb | 8 + cookbooks/application_nodejs/LICENSE | 201 + cookbooks/application_nodejs/README.md | 64 + cookbooks/application_nodejs/metadata.rb | 12 + .../application_nodejs/providers/nodejs.rb | 132 + .../application_nodejs/resources/nodejs.rb | 25 + .../default/nodejs.systemd.service.erb | 4 + .../templates/default/nodejs.upstart.conf.erb | 19 + cookbooks/apt/.gitignore | 15 + cookbooks/apt/.kitchen.cloud.yml | 47 + cookbooks/apt/.kitchen.yml | 62 + cookbooks/apt/.rubocop.yml | 37 + cookbooks/apt/.travis.yml | 44 + cookbooks/apt/Berksfile | 8 + cookbooks/apt/CHANGELOG.md | 217 + cookbooks/apt/CONTRIBUTING | 29 + cookbooks/apt/Gemfile | 37 + cookbooks/apt/Guardfile | 35 + cookbooks/apt/LICENSE | 201 + cookbooks/apt/README.md | 281 + cookbooks/apt/Rakefile | 59 + cookbooks/apt/TESTING.md | 187 + cookbooks/apt/attributes/default.rb | 48 + cookbooks/apt/files/default/15update-stamp | 1 + cookbooks/apt/files/default/apt-proxy-v2.conf | 50 + cookbooks/apt/libraries/helpers.rb | 49 + cookbooks/apt/libraries/matchers.rb | 17 + cookbooks/apt/libraries/network.rb | 31 + cookbooks/apt/metadata.json | 121 + cookbooks/apt/metadata.rb | 38 + cookbooks/apt/providers/preference.rb | 69 + cookbooks/apt/providers/repository.rb | 203 + cookbooks/apt/recipes/cacher-client.rb | 83 + cookbooks/apt/recipes/cacher-ng.rb | 43 + cookbooks/apt/recipes/default.rb | 112 + cookbooks/apt/recipes/unattended-upgrades.rb | 47 + cookbooks/apt/resources/preference.rb | 37 + cookbooks/apt/resources/repository.rb | 55 + .../apt/templates/debian-6.0/acng.conf.erb | 173 + cookbooks/apt/templates/default/01proxy.erb | 9 + .../apt/templates/default/20auto-upgrades.erb | 2 + .../default/50unattended-upgrades.erb | 68 + cookbooks/apt/templates/default/acng.conf.erb | 275 + .../default/unattended-upgrades.seed.erb | 1 + .../apt/templates/ubuntu-10.04/acng.conf.erb | 269 + cookbooks/ark/CHANGELOG.md | 135 + cookbooks/ark/README.md | 305 + cookbooks/ark/attributes/default.rb | 16 + cookbooks/ark/files/default/foo.tar.gz | Bin 0 -> 219 bytes cookbooks/ark/files/default/foo.tbz | Bin 0 -> 163 bytes cookbooks/ark/files/default/foo.tgz | Bin 0 -> 152 bytes cookbooks/ark/files/default/foo.txz | Bin 0 -> 200 bytes cookbooks/ark/files/default/foo.zip | Bin 0 -> 976 bytes cookbooks/ark/files/default/foo_sub.tar.gz | Bin 0 -> 193 bytes cookbooks/ark/files/default/foo_sub.zip | Bin 0 -> 614 bytes .../default/tests/minitest/default_test.rb | 0 .../default/tests/minitest/support/helpers.rb | 0 .../files/default/tests/minitest/test_test.rb | 102 + cookbooks/ark/libraries/default.rb | 234 + cookbooks/ark/metadata.json | 38 + cookbooks/ark/metadata.rb | 16 + cookbooks/ark/providers/default.rb | 396 + cookbooks/ark/recipes/default.rb | 27 + cookbooks/ark/recipes/test.rb | 152 + cookbooks/ark/resources/default.rb | 55 + .../ark/templates/default/add_to_path.sh.erb | 1 + cookbooks/bluepill/CHANGELOG.md | 70 + cookbooks/bluepill/README.md | 87 + cookbooks/bluepill/attributes/default.rb | 35 + cookbooks/bluepill/metadata.json | 31 + cookbooks/bluepill/metadata.rb | 10 + cookbooks/bluepill/providers/service.rb | 175 + cookbooks/bluepill/recipes/default.rb | 48 + cookbooks/bluepill/recipes/rsyslog.rb | 28 + cookbooks/bluepill/resources/service.rb | 27 + .../default/bluepill_init.fedora.erb | 32 + .../default/bluepill_init.freebsd.erb | 31 + .../templates/default/bluepill_init.lsb.erb | 34 + .../templates/default/bluepill_init.rhel.erb | 32 + .../default/bluepill_rsyslog.conf.erb | 1 + cookbooks/build-essential/.envrc | 3 + cookbooks/build-essential/.gitignore | 16 + cookbooks/build-essential/.kitchen.cloud.yml | 165 + cookbooks/build-essential/.kitchen.yml | 24 + cookbooks/build-essential/.rubocop.yml | 30 + cookbooks/build-essential/.travis.yml | 40 + cookbooks/build-essential/Berksfile | 8 + cookbooks/build-essential/CHANGELOG.md | 125 + cookbooks/build-essential/CONTRIBUTING | 29 + cookbooks/build-essential/Gemfile | 38 + cookbooks/build-essential/Guardfile | 35 + cookbooks/build-essential/LICENSE | 201 + cookbooks/build-essential/README.md | 108 + cookbooks/build-essential/Rakefile | 65 + .../build-essential/attributes/default.rb | 20 + .../build-essential/libraries/matchers.rb | 5 + cookbooks/build-essential/libraries/timing.rb | 124 + .../libraries/xcode_command_line_tools.rb | 211 + cookbooks/build-essential/matrix | 10 + cookbooks/build-essential/metadata.json | 64 + cookbooks/build-essential/metadata.rb | 29 + cookbooks/build-essential/recipes/_debian.rb | 28 + cookbooks/build-essential/recipes/_fedora.rb | 31 + cookbooks/build-essential/recipes/_freebsd.rb | 24 + .../build-essential/recipes/_mac_os_x.rb | 22 + cookbooks/build-essential/recipes/_omnios.rb | 33 + cookbooks/build-essential/recipes/_rhel.rb | 36 + cookbooks/build-essential/recipes/_smartos.rb | 27 + .../build-essential/recipes/_solaris2.rb | 48 + cookbooks/build-essential/recipes/_suse.rb | 29 + cookbooks/build-essential/recipes/default.rb | 29 + cookbooks/chef-solo-search/.gitignore | 2 + cookbooks/chef-solo-search/.travis.yml | 9 + cookbooks/chef-solo-search/CHANGELOG | 31 + cookbooks/chef-solo-search/Gemfile | 8 + cookbooks/chef-solo-search/LICENSE | 202 + cookbooks/chef-solo-search/NOTICE | 23 + cookbooks/chef-solo-search/README.md | 148 + cookbooks/chef-solo-search/Rakefile | 10 + .../chef-solo-search/libraries/search.rb | 74 + .../libraries/search/overrides.rb | 100 + .../libraries/search/parser.rb | 222 + .../vendor/chef/solr_query/lucene.treetop | 150 + .../vendor/chef/solr_query/lucene_nodes.rb | 285 + .../vendor/chef/solr_query/query_transform.rb | 65 + cookbooks/chef-solo-search/metadata.json | 35 + cookbooks/chef-solo-search/metadata.rb | 11 + cookbooks/chef-solo-search/recipes/default.rb | 2 + .../tests/data/data_bags/node/alpha.json | 10 + .../tests/data/data_bags/node/beta.json | 10 + .../data_bags/node/without_json_class.json | 7 + .../tests/data/data_bags/users/jerry.json | 8 + .../tests/data/data_bags/users/lea.json | 10 + .../tests/data/data_bags/users/mike.json | 13 + .../tests/data/data_bags/users/tom.json | 10 + .../tests/gemfiles/Gemfile.10 | 4 + .../tests/gemfiles/Gemfile.11 | 5 + .../chef-solo-search/tests/test_data_bags.rb | 45 + .../chef-solo-search/tests/test_search.rb | 244 + cookbooks/chef-sugar/CHANGELOG.md | 159 + cookbooks/chef-sugar/README.md | 464 + cookbooks/chef-sugar/metadata.json | 29 + cookbooks/chef-sugar/recipes/default.rb | 27 + cookbooks/chef_handler/CHANGELOG.md | 56 + cookbooks/chef_handler/README.md | 126 + cookbooks/chef_handler/attributes/default.rb | 30 + .../files/default/handlers/README | 1 + cookbooks/chef_handler/libraries/helpers.rb | 88 + cookbooks/chef_handler/libraries/matchers.rb | 38 + cookbooks/chef_handler/metadata.json | 1 + cookbooks/chef_handler/providers/default.rb | 86 + cookbooks/chef_handler/recipes/default.rb | 33 + cookbooks/chef_handler/recipes/json_file.rb | 28 + cookbooks/chef_handler/resources/default.rb | 34 + cookbooks/database/CHANGELOG.md | 207 + cookbooks/database/README.md | 647 + cookbooks/database/libraries/matchers.rb | 151 + .../libraries/provider_database_mysql.rb | 158 + .../libraries/provider_database_mysql_user.rb | 193 + .../libraries/provider_database_postgresql.rb | 144 + .../provider_database_postgresql_schema.rb | 74 + .../provider_database_postgresql_user.rb | 103 + .../libraries/provider_database_sql_server.rb | 111 + .../provider_database_sql_server_user.rb | 152 + .../database/libraries/resource_database.rb | 118 + .../libraries/resource_database_user.rb | 105 + .../libraries/resource_mysql_database.rb | 32 + .../libraries/resource_mysql_database_user.rb | 32 + .../libraries/resource_postgresql_database.rb | 33 + .../resource_postgresql_database_schema.rb | 43 + .../resource_postgresql_database_user.rb | 89 + .../libraries/resource_sql_server_database.rb | 32 + .../resource_sql_server_database_user.rb | 63 + cookbooks/database/metadata.json | 1 + cookbooks/database/recipes/postgresql.rb | 20 + .../templates/default/app_grants.sql.erb | 8 + .../database/templates/default/aws_config.erb | 3 + .../chef-solo-database-snapshot.cron.erb | 6 + .../chef-solo-database-snapshot.json.erb | 1 + .../chef-solo-database-snapshot.rb.erb | 6 + .../templates/default/ebs-backup-cron.erb | 2 + .../templates/default/ebs-db-backup.sh.erb | 8 + .../templates/default/ebs-db-restore.sh.erb | 10 + .../database/templates/default/s3cfg.erb | 27 + cookbooks/firewall/CHANGELOG.md | 121 + cookbooks/firewall/README.md | 189 + cookbooks/firewall/attributes/default.rb | 1 + cookbooks/firewall/attributes/ufw.rb | 12 + cookbooks/firewall/libraries/helpers.rb | 13 + cookbooks/firewall/libraries/matchers.rb | 32 + .../libraries/provider_firewall_firewalld.rb | 92 + .../libraries/provider_firewall_iptables.rb | 110 + .../provider_firewall_rule_firewalld.rb | 213 + .../provider_firewall_rule_iptables.rb | 231 + .../libraries/provider_firewall_rule_ufw.rb | 241 + .../libraries/provider_firewall_ufw.rb | 77 + .../firewall/libraries/resource_firewall.rb | 10 + .../libraries/resource_firewall_rule.rb | 36 + .../firewall/libraries/z_provider_mapping.rb | 22 + cookbooks/firewall/metadata.json | 1 + cookbooks/firewall/recipes/default.rb | 29 + .../templates/default/ufw/default.erb | 13 + cookbooks/homebrew/CHANGELOG.md | 140 + cookbooks/homebrew/README.md | 167 + cookbooks/homebrew/attributes/default.rb | 26 + .../homebrew/libraries/homebrew_mixin.rb | 66 + .../homebrew/libraries/homebrew_package.rb | 115 + cookbooks/homebrew/libraries/matchers.rb | 43 + cookbooks/homebrew/metadata.json | 33 + cookbooks/homebrew/metadata.rb | 10 + cookbooks/homebrew/providers/cask.rb | 36 + cookbooks/homebrew/providers/tap.rb | 54 + cookbooks/homebrew/recipes/cask.rb | 39 + cookbooks/homebrew/recipes/default.rb | 49 + cookbooks/homebrew/recipes/install_casks.rb | 24 + .../homebrew/recipes/install_formulas.rb | 24 + cookbooks/homebrew/recipes/install_taps.rb | 24 + cookbooks/homebrew/resources/cask.rb | 19 + cookbooks/homebrew/resources/tap.rb | 35 + cookbooks/hostname/.gitignore | 15 + cookbooks/hostname/.kitchen.yml | 57 + cookbooks/hostname/.rubocop.yml | 9 + cookbooks/hostname/.travis.yml | 13 + cookbooks/hostname/Berksfile | 8 + cookbooks/hostname/CHANGELOG.md | 46 + cookbooks/hostname/Gemfile | 14 + cookbooks/hostname/README.md | 44 + cookbooks/hostname/Strainerfile | 4 + cookbooks/hostname/TESTING.md | 46 + cookbooks/hostname/Thorfile | 41 + cookbooks/hostname/attributes/default.rb | 4 + cookbooks/hostname/chefignore | 96 + cookbooks/hostname/metadata.rb | 15 + cookbooks/hostname/recipes/default.rb | 122 + cookbooks/hostname/recipes/vmware.rb | 36 + cookbooks/hostsfile/CHANGELOG.md | 73 + cookbooks/hostsfile/README.md | 234 + cookbooks/hostsfile/attributes/default.rb | 22 + cookbooks/hostsfile/libraries/entry.rb | 183 + cookbooks/hostsfile/libraries/manipulator.rb | 293 + cookbooks/hostsfile/libraries/matchers.rb | 21 + cookbooks/hostsfile/metadata.json | 29 + cookbooks/hostsfile/metadata.rb | 7 + cookbooks/hostsfile/providers/entry.rb | 138 + cookbooks/hostsfile/resources/entry.rb | 36 + cookbooks/iis/CHANGELOG.md | 183 + cookbooks/iis/README.md | 458 + cookbooks/iis/attributes/default.rb | 27 + cookbooks/iis/libraries/helper.rb | 87 + cookbooks/iis/libraries/matcher.rb | 68 + cookbooks/iis/metadata.json | 1 + cookbooks/iis/providers/app.rb | 146 + cookbooks/iis/providers/config.rb | 33 + cookbooks/iis/providers/module.rb | 93 + cookbooks/iis/providers/pool.rb | 287 + cookbooks/iis/providers/section.rb | 71 + cookbooks/iis/providers/site.rb | 221 + cookbooks/iis/providers/vdir.rb | 155 + cookbooks/iis/recipes/default.rb | 34 + .../recipes/mod_application_initialization.rb | 29 + cookbooks/iis/recipes/mod_aspnet.rb | 34 + cookbooks/iis/recipes/mod_aspnet45.rb | 34 + cookbooks/iis/recipes/mod_auth_anonymous.rb | 26 + cookbooks/iis/recipes/mod_auth_basic.rb | 36 + cookbooks/iis/recipes/mod_auth_digest.rb | 36 + cookbooks/iis/recipes/mod_auth_windows.rb | 36 + cookbooks/iis/recipes/mod_cgi.rb | 31 + cookbooks/iis/recipes/mod_compress_dynamic.rb | 31 + cookbooks/iis/recipes/mod_compress_static.rb | 31 + cookbooks/iis/recipes/mod_ftp.rb | 33 + .../iis/recipes/mod_iis6_metabase_compat.rb | 33 + cookbooks/iis/recipes/mod_isapi.rb | 33 + cookbooks/iis/recipes/mod_logging.rb | 31 + cookbooks/iis/recipes/mod_management.rb | 33 + cookbooks/iis/recipes/mod_security.rb | 33 + cookbooks/iis/recipes/mod_tracing.rb | 31 + cookbooks/iis/recipes/remove_default_site.rb | 27 + cookbooks/iis/resources/app.rb | 29 + cookbooks/iis/resources/config.rb | 25 + cookbooks/iis/resources/module.rb | 29 + cookbooks/iis/resources/pool.rb | 78 + cookbooks/iis/resources/section.rb | 27 + cookbooks/iis/resources/site.rb | 37 + cookbooks/iis/resources/vdir.rb | 32 + cookbooks/mariadb/CHANGELOG.md | 120 + cookbooks/mariadb/README.md | 207 + cookbooks/mariadb/attributes/default.rb | 156 + cookbooks/mariadb/libraries/mariadb_helper.rb | 78 + cookbooks/mariadb/libraries/matchers.rb | 21 + cookbooks/mariadb/metadata.json | 36 + cookbooks/mariadb/providers/configuration.rb | 38 + cookbooks/mariadb/providers/replication.rb | 105 + cookbooks/mariadb/recipes/_audit_plugin.rb | 53 + cookbooks/mariadb/recipes/_debian_galera.rb | 55 + cookbooks/mariadb/recipes/_debian_server.rb | 55 + cookbooks/mariadb/recipes/_redhat_galera.rb | 49 + cookbooks/mariadb/recipes/_redhat_server.rb | 45 + .../mariadb/recipes/_redhat_server_native.rb | 45 + cookbooks/mariadb/recipes/client.rb | 89 + cookbooks/mariadb/recipes/config.rb | 96 + cookbooks/mariadb/recipes/default.rb | 20 + cookbooks/mariadb/recipes/galera.rb | 141 + cookbooks/mariadb/recipes/plugins.rb | 3 + cookbooks/mariadb/recipes/repository.rb | 42 + cookbooks/mariadb/recipes/server.rb | 130 + cookbooks/mariadb/resources/configuration.rb | 13 + cookbooks/mariadb/resources/replication.rb | 22 + .../templates/default/conf.d.generic.erb | 19 + .../mariadb/templates/default/debian.cnf.erb | 12 + .../templates/default/mariadb-server.seed.erb | 13 + .../templates/default/mariadb_grants.erb | 25 + .../mariadb/templates/default/my.cnf.erb | 191 + cookbooks/mediawiki/.gitignore | 20 + cookbooks/mediawiki/.kitchen.yml | 22 + cookbooks/mediawiki/Berksfile | 4 + cookbooks/mediawiki/CHANGELOG.md | 9 + cookbooks/mediawiki/Gemfile | 18 + cookbooks/mediawiki/LICENSE | 3 + cookbooks/mediawiki/README.md | 88 + cookbooks/mediawiki/Thorfile | 12 + cookbooks/mediawiki/Vagrantfile | 88 + cookbooks/mediawiki/attributes/default.rb | 16 + cookbooks/mediawiki/chefignore | 94 + cookbooks/mediawiki/metadata.rb | 56 + cookbooks/mediawiki/recipes/default.rb | 100 + .../templates/default/web_app.conf.erb | 49 + cookbooks/mysql/CHANGELOG.md | 562 + cookbooks/mysql/README.md | 560 + cookbooks/mysql/libraries/helpers.rb | 416 + cookbooks/mysql/libraries/matchers.rb | 28 + .../mysql/libraries/provider_mysql_client.rb | 38 + .../mysql/libraries/provider_mysql_config.rb | 58 + .../mysql/libraries/provider_mysql_service.rb | 251 + .../libraries/provider_mysql_service_smf.rb | 85 + .../provider_mysql_service_systemd.rb | 121 + .../provider_mysql_service_sysvinit.rb | 87 + .../provider_mysql_service_upstart.rb | 107 + .../mysql/libraries/resource_mysql_client.rb | 16 + .../mysql/libraries/resource_mysql_config.rb | 20 + .../mysql/libraries/resource_mysql_service.rb | 25 + .../mysql/libraries/z_provider_mapping.rb | 47 + cookbooks/mysql/metadata.json | 1 + .../apparmor/usr.sbin.mysqld-instance.erb | 13 + .../apparmor/usr.sbin.mysqld-local.erb | 1 + .../default/apparmor/usr.sbin.mysqld.erb | 45 + cookbooks/mysql/templates/default/my.cnf.erb | 54 + .../default/smf/svc.method.mysqld.erb | 28 + .../default/systemd/mysqld-wait-ready.erb | 30 + .../default/systemd/mysqld.service.erb | 16 + .../templates/default/sysvinit/mysqld.erb | 266 + .../templates/default/tmpfiles.d.conf.erb | 1 + .../default/upstart/mysqld-wait-ready.erb | 22 + .../templates/default/upstart/mysqld.erb | 26 + cookbooks/mysql2_chef_gem/CHANGELOG.md | 26 + cookbooks/mysql2_chef_gem/README.md | 108 + .../mysql2_chef_gem/libraries/matchers.rb | 5 + .../provider_mysql2_chef_gem_mariadb.rb | 37 + .../provider_mysql2_chef_gem_mysql.rb | 37 + .../libraries/resource_mysql2_chef_gem.rb | 15 + .../libraries/z_provider_mapping.rb | 17 + cookbooks/mysql2_chef_gem/metadata.json | 39 + cookbooks/nginx/CHANGELOG.md | 435 + cookbooks/nginx/README.md | 521 + cookbooks/nginx/attributes/auth_request.rb | 23 + cookbooks/nginx/attributes/default.rb | 131 + cookbooks/nginx/attributes/devel.rb | 24 + cookbooks/nginx/attributes/echo.rb | 24 + cookbooks/nginx/attributes/geoip.rb | 31 + cookbooks/nginx/attributes/headers_more.rb | 24 + cookbooks/nginx/attributes/lua.rb | 28 + cookbooks/nginx/attributes/naxsi.rb | 24 + cookbooks/nginx/attributes/openssl_source.rb | 23 + cookbooks/nginx/attributes/pagespeed.rb | 9 + cookbooks/nginx/attributes/passenger.rb | 58 + cookbooks/nginx/attributes/rate_limiting.rb | 23 + cookbooks/nginx/attributes/repo.rb | 35 + cookbooks/nginx/attributes/set_misc.rb | 8 + cookbooks/nginx/attributes/socketproxy.rb | 13 + cookbooks/nginx/attributes/source.rb | 42 + cookbooks/nginx/attributes/status.rb | 22 + cookbooks/nginx/attributes/syslog.rb | 24 + cookbooks/nginx/attributes/upload_progress.rb | 26 + cookbooks/nginx/definitions/nginx_site.rb | 50 + cookbooks/nginx/files/default/mime.types | 78 + .../nginx/files/default/naxsi_core.rules | 82 + cookbooks/nginx/libraries/matchers.rb | 20 + cookbooks/nginx/metadata.json | 351 + cookbooks/nginx/metadata.rb | 125 + cookbooks/nginx/recipes/authorized_ips.rb | 32 + cookbooks/nginx/recipes/commons.rb | 24 + cookbooks/nginx/recipes/commons_conf.rb | 42 + cookbooks/nginx/recipes/commons_dir.rb | 57 + cookbooks/nginx/recipes/commons_script.rb | 29 + cookbooks/nginx/recipes/default.rb | 31 + .../nginx/recipes/headers_more_module.rb | 50 + .../nginx/recipes/http_auth_request_module.rb | 52 + cookbooks/nginx/recipes/http_echo_module.rb | 46 + cookbooks/nginx/recipes/http_geoip_module.rb | 113 + .../nginx/recipes/http_gzip_static_module.rb | 30 + cookbooks/nginx/recipes/http_mp4_module.rb | 2 + cookbooks/nginx/recipes/http_perl_module.rb | 23 + cookbooks/nginx/recipes/http_realip_module.rb | 38 + cookbooks/nginx/recipes/http_spdy_module.rb | 23 + cookbooks/nginx/recipes/http_ssl_module.rb | 23 + .../nginx/recipes/http_stub_status_module.rb | 36 + cookbooks/nginx/recipes/ipv6.rb | 23 + cookbooks/nginx/recipes/lua.rb | 47 + cookbooks/nginx/recipes/naxsi_module.rb | 52 + cookbooks/nginx/recipes/ngx_devel_module.rb | 44 + cookbooks/nginx/recipes/ngx_lua_module.rb | 47 + cookbooks/nginx/recipes/ohai_plugin.rb | 35 + cookbooks/nginx/recipes/openssl_source.rb | 45 + cookbooks/nginx/recipes/package.rb | 52 + cookbooks/nginx/recipes/pagespeed_module.rb | 62 + cookbooks/nginx/recipes/passenger.rb | 56 + cookbooks/nginx/recipes/repo.rb | 41 + cookbooks/nginx/recipes/repo_passenger.rb | 39 + cookbooks/nginx/recipes/set_misc.rb | 30 + cookbooks/nginx/recipes/socketproxy.rb | 26 + cookbooks/nginx/recipes/source.rb | 205 + cookbooks/nginx/recipes/syslog_module.rb | 69 + .../nginx/recipes/upload_progress_module.rb | 53 + .../nginx/templates/debian/nginx.init.erb | 97 + .../nginx/templates/default/default-site.erb | 11 + .../default/modules/authorized_ip.erb | 6 + .../default/modules/http_geoip.conf.erb | 4 + .../default/modules/http_gzip_static.conf.erb | 1 + .../default/modules/http_realip.conf.erb | 7 + .../default/modules/nginx_status.erb | 14 + .../default/modules/passenger.conf.erb | 13 + .../default/modules/socketproxy.conf.erb | 89 + .../default/modules/upload_progress.erb | 4 + .../templates/default/nginx-upstart.conf.erb | 39 + .../nginx/templates/default/nginx.conf.erb | 103 + .../nginx/templates/default/nginx.init.erb | 111 + .../nginx/templates/default/nginx.pill.erb | 15 + .../templates/default/nginx.sysconfig.erb | 1 + .../nginx/templates/default/nxdissite.erb | 29 + .../nginx/templates/default/nxensite.erb | 38 + .../templates/default/plugins/nginx.rb.erb | 66 + .../templates/default/sv-nginx-log-run.erb | 2 + .../nginx/templates/default/sv-nginx-run.erb | 4 + .../nginx/templates/gentoo/nginx.init.erb | 87 + cookbooks/nginx/templates/suse/nginx.init.erb | 115 + .../nginx/templates/ubuntu/nginx.init.erb | 97 + cookbooks/nodejs/CHANGELOG.md | 66 + cookbooks/nodejs/README.md | 145 + cookbooks/nodejs/attributes/default.rb | 45 + cookbooks/nodejs/attributes/npm.rb | 3 + cookbooks/nodejs/attributes/packages.rb | 14 + cookbooks/nodejs/attributes/repo.rb | 13 + cookbooks/nodejs/libraries/matchers.rb | 9 + cookbooks/nodejs/libraries/nodejs_helper.rb | 33 + cookbooks/nodejs/metadata.json | 42 + cookbooks/nodejs/providers/npm.rb | 55 + cookbooks/nodejs/recipes/default.rb | 23 + cookbooks/nodejs/recipes/install.rb | 21 + cookbooks/nodejs/recipes/iojs.rb | 23 + cookbooks/nodejs/recipes/nodejs.rb | 23 + .../nodejs/recipes/nodejs_from_binary.rb | 58 + .../nodejs/recipes/nodejs_from_package.rb | 35 + .../nodejs/recipes/nodejs_from_source.rb | 52 + cookbooks/nodejs/recipes/npm.rb | 28 + cookbooks/nodejs/recipes/npm_from_source.rb | 34 + cookbooks/nodejs/recipes/npm_packages.rb | 10 + cookbooks/nodejs/recipes/repo.rb | 16 + cookbooks/nodejs/resources/npm.rb | 33 + cookbooks/ohai/CHANGELOG.md | 51 + cookbooks/ohai/README.md | 68 + cookbooks/ohai/attributes/default.rb | 31 + cookbooks/ohai/files/default/plugins/README | 1 + cookbooks/ohai/metadata.json | 46 + cookbooks/ohai/metadata.rb | 23 + cookbooks/ohai/providers/hint.rb | 35 + cookbooks/ohai/recipes/default.rb | 55 + cookbooks/ohai/resources/hint.rb | 6 + cookbooks/omnibus_updater/CHANGELOG.md | 84 + cookbooks/omnibus_updater/README.md | 88 + .../omnibus_updater/attributes/default.rb | 31 + .../libraries/omnibus_checker.rb | 30 + .../omnibus_updater/libraries/omnitrucker.rb | 92 + cookbooks/omnibus_updater/metadata.json | 29 + cookbooks/omnibus_updater/metadata.rb | 7 + cookbooks/omnibus_updater/recipes/default.rb | 31 + .../omnibus_updater/recipes/downloader.rb | 71 + .../omnibus_updater/recipes/installer.rb | 70 + .../recipes/old_package_cleaner.rb | 34 + .../recipes/remove_chef_system_gem.rb | 29 + cookbooks/openssl/CHANGELOG.md | 30 + cookbooks/openssl/README.md | 115 + cookbooks/openssl/attributes/default.rb | 21 + .../openssl/libraries/secure_password.rb | 37 + cookbooks/openssl/metadata.json | 31 + cookbooks/openssl/providers/x509.rb | 94 + cookbooks/openssl/recipes/default.rb | 18 + cookbooks/openssl/recipes/upgrade.rb | 39 + cookbooks/openssl/resources/x509.rb | 16 + cookbooks/packagecloud/.gitignore | 19 + cookbooks/packagecloud/.kitchen.yml | 79 + cookbooks/packagecloud/.rubocop.yml | 28 + cookbooks/packagecloud/.travis.yml | 7 + .../0001-chef-on-amazon-2014.patch | 65 + cookbooks/packagecloud/Berksfile | 5 + cookbooks/packagecloud/CHANGELOG.md | 12 + cookbooks/packagecloud/Gemfile | 15 + cookbooks/packagecloud/LICENSE | 13 + cookbooks/packagecloud/README.md | 64 + cookbooks/packagecloud/Rakefile | 47 + cookbooks/packagecloud/THANKS | 5 + cookbooks/packagecloud/Thorfile | 5 + cookbooks/packagecloud/Vagrantfile | 85 + cookbooks/packagecloud/attributes/default.rb | 7 + cookbooks/packagecloud/chefignore | 98 + cookbooks/packagecloud/libraries/helper.rb | 43 + cookbooks/packagecloud/libraries/matcher.rb | 7 + cookbooks/packagecloud/metadata.json | 41 + cookbooks/packagecloud/metadata.rb | 7 + cookbooks/packagecloud/providers/repo.rb | 207 + cookbooks/packagecloud/resources/repo.rb | 10 + .../packagecloud/templates/default/apt.erb | 2 + .../packagecloud/templates/default/yum.erb | 15 + cookbooks/partial_search/CHANGELOG.md | 31 + cookbooks/partial_search/README.md | 82 + .../libraries/partial_search.rb | 147 + cookbooks/partial_search/metadata.json | 29 + cookbooks/partial_search/metadata.rb | 7 + cookbooks/partial_search/recipes/default.rb | 1 + cookbooks/php/CHANGELOG.md | 139 + cookbooks/php/README.md | 347 + cookbooks/php/attributes/default.rb | 126 + cookbooks/php/files/windows/go-pear.phar | 98480 ++++++++++++++++ cookbooks/php/libraries/helpers.rb | 23 + cookbooks/php/metadata.json | 57 + cookbooks/php/metadata.rb | 31 + cookbooks/php/providers/pear.rb | 280 + cookbooks/php/providers/pear_channel.rb | 93 + cookbooks/php/recipes/default.rb | 33 + cookbooks/php/recipes/ini.rb | 30 + cookbooks/php/recipes/module_apc.rb | 37 + cookbooks/php/recipes/module_curl.rb | 29 + cookbooks/php/recipes/module_fpdf.rb | 35 + cookbooks/php/recipes/module_gd.rb | 32 + cookbooks/php/recipes/module_ldap.rb | 32 + cookbooks/php/recipes/module_memcache.rb | 37 + cookbooks/php/recipes/module_mysql.rb | 32 + cookbooks/php/recipes/module_pgsql.rb | 32 + cookbooks/php/recipes/module_sqlite3.rb | 29 + cookbooks/php/recipes/package.rb | 66 + cookbooks/php/recipes/source.rb | 85 + cookbooks/php/resources/pear.rb | 30 + cookbooks/php/resources/pear_channel.rb | 29 + cookbooks/php/templates/centos/php.ini.erb | 1225 + cookbooks/php/templates/debian/php.ini.erb | 1857 + .../php/templates/default/extension.ini.erb | 7 + cookbooks/php/templates/default/php.ini.erb | 1900 + cookbooks/php/templates/redhat/php.ini.erb | 1225 + cookbooks/php/templates/ubuntu/php.ini.erb | 1857 + .../php/templates/windows/pear-options.erb | 18 + cookbooks/php/templates/windows/php.ini.erb | 1935 + cookbooks/poise/CHANGELOG.md | 78 + cookbooks/poise/README.md | 198 + cookbooks/poise/files/halite_gem/poise.rb | 71 + .../poise/files/halite_gem/poise/error.rb | 24 + .../poise/files/halite_gem/poise/helpers.rb | 33 + .../poise/helpers/chefspec_matchers.rb | 88 + .../halite_gem/poise/helpers/defined_in.rb | 111 + .../files/halite_gem/poise/helpers/fused.rb | 127 + .../poise/helpers/include_recipe.rb | 62 + .../halite_gem/poise/helpers/inversion.rb | 374 + .../helpers/inversion/options_provider.rb | 41 + .../helpers/inversion/options_resource.rb | 101 + .../halite_gem/poise/helpers/lazy_default.rb | 62 + .../halite_gem/poise/helpers/lwrp_polyfill.rb | 96 + .../poise/helpers/notifying_block.rb | 78 + .../poise/helpers/option_collector.rb | 131 + .../halite_gem/poise/helpers/resource_name.rb | 99 + .../poise/helpers/subcontext_block.rb | 58 + .../halite_gem/poise/helpers/subresources.rb | 29 + .../poise/helpers/subresources/child.rb | 217 + .../poise/helpers/subresources/container.rb | 165 + .../subresources/default_containers.rb | 73 + .../poise/helpers/template_content.rb | 165 + .../poise/files/halite_gem/poise/provider.rb | 55 + .../poise/files/halite_gem/poise/resource.rb | 75 + .../files/halite_gem/poise/subcontext.rb | 27 + .../poise/subcontext/resource_collection.rb | 56 + .../halite_gem/poise/subcontext/runner.rb | 50 + .../poise/files/halite_gem/poise/utils.rb | 70 + .../poise/utils/resource_provider_mixin.rb | 65 + .../poise/files/halite_gem/poise/version.rb | 20 + cookbooks/poise/libraries/default.rb | 18 + cookbooks/poise/metadata.json | 1 + cookbooks/postfix/CHANGELOG.md | 167 + cookbooks/postfix/README.md | 289 + cookbooks/postfix/attributes/default.rb | 137 + .../default/tests/minitest/support/helpers.rb | 25 + cookbooks/postfix/metadata.json | 89 + cookbooks/postfix/metadata.rb | 64 + cookbooks/postfix/recipes/_attributes.rb | 60 + cookbooks/postfix/recipes/_common.rb | 128 + cookbooks/postfix/recipes/access.rb | 28 + cookbooks/postfix/recipes/aliases.rb | 30 + cookbooks/postfix/recipes/client.rb | 42 + cookbooks/postfix/recipes/default.rb | 45 + cookbooks/postfix/recipes/sasl_auth.rb | 59 + cookbooks/postfix/recipes/server.rb | 25 + cookbooks/postfix/recipes/transports.rb | 28 + cookbooks/postfix/recipes/virtual_aliases.rb | 29 + .../recipes/virtual_aliases_domains.rb | 29 + .../postfix/templates/default/access.erb | 10 + .../postfix/templates/default/aliases.erb | 11 + .../postfix/templates/default/main.cf.erb | 13 + .../default/manifest-postfix.xml.erb | 84 + .../postfix/templates/default/master.cf.erb | 81 + .../postfix/templates/default/port_smtp.erb | 2 + .../postfix/templates/default/sasl_passwd.erb | 2 + .../templates/default/sender_canonical.erb | 10 + .../templates/default/smtp_generic.erb | 10 + .../postfix/templates/default/transport.erb | 10 + .../templates/default/virtual_aliases.erb | 10 + .../default/virtual_aliases_domains.erb | 10 + cookbooks/postgresql/CHANGELOG.md | 220 + cookbooks/postgresql/README.md | 464 + cookbooks/postgresql/attributes/default.rb | 549 + .../minitest/apt_pgdg_postgresql_test.rb | 39 + .../default/tests/minitest/default_test.rb | 27 + .../files/default/tests/minitest/ruby_test.rb | 28 + .../default/tests/minitest/server_test.rb | 43 + .../default/tests/minitest/support/helpers.rb | 29 + cookbooks/postgresql/libraries/default.rb | 377 + cookbooks/postgresql/metadata.json | 56 + cookbooks/postgresql/metadata.rb | 28 + .../postgresql/recipes/apt_pgdg_postgresql.rb | 18 + cookbooks/postgresql/recipes/client.rb | 32 + cookbooks/postgresql/recipes/config_initdb.rb | 148 + cookbooks/postgresql/recipes/config_pgtune.rb | 284 + cookbooks/postgresql/recipes/contrib.rb | 44 + cookbooks/postgresql/recipes/default.rb | 18 + cookbooks/postgresql/recipes/ruby.rb | 117 + cookbooks/postgresql/recipes/server.rb | 89 + cookbooks/postgresql/recipes/server_conf.rb | 34 + cookbooks/postgresql/recipes/server_debian.rb | 38 + cookbooks/postgresql/recipes/server_redhat.rb | 100 + .../postgresql/recipes/yum_pgdg_postgresql.rb | 45 + .../templates/default/pg_hba.conf.erb | 35 + .../templates/default/pgsql.sysconfig.erb | 4 + .../templates/default/postgresql.conf.erb | 21 + cookbooks/rbac/README.md | 82 + cookbooks/rbac/libraries/rbac.rb | 15 + cookbooks/rbac/metadata.json | 42 + cookbooks/rbac/metadata.rb | 10 + cookbooks/rbac/providers/auth.rb | 20 + cookbooks/rbac/providers/default.rb | 27 + cookbooks/rbac/providers/user.rb | 22 + cookbooks/rbac/recipes/default.rb | 6 + cookbooks/rbac/resources/auth.rb | 14 + cookbooks/rbac/resources/default.rb | 6 + cookbooks/rbac/resources/user.rb | 6 + cookbooks/redis/.gitignore | 9 + cookbooks/redis/.kitchen.yml | 20 + cookbooks/redis/.rubocop.yml | 18 + cookbooks/redis/.ruby-gemset | 1 + cookbooks/redis/.ruby-version | 1 + cookbooks/redis/.travis.yml | 19 + cookbooks/redis/Berksfile | 7 + cookbooks/redis/Gemfile | 20 + cookbooks/redis/Guardfile | 5 + cookbooks/redis/LICENSE.txt | 20 + cookbooks/redis/README.md | 155 + cookbooks/redis/Rakefile | 31 + cookbooks/redis/attributes/default.rb | 77 + cookbooks/redis/config/license_finder.yml | 11 + .../redis/doc/license_finder/dependencies.csv | 105 + .../redis/doc/license_finder/dependencies.db | Bin 0 -> 90112 bytes .../doc/license_finder/dependencies.html | 2509 + .../redis/doc/license_finder/dependencies.md | 991 + .../license_finder/dependencies_detailed.csv | 234 + .../default/tests/minitest/client_test.rb | 7 + .../default/tests/minitest/default_test.rb | 19 + .../default/tests/minitest/server_test.rb | 48 + .../default/tests/minitest/test_helper.rb | 5 + cookbooks/redis/metadata.rb | 15 + cookbooks/redis/recipes/client.rb | 10 + cookbooks/redis/recipes/default.rb | 11 + cookbooks/redis/recipes/server.rb | 38 + cookbooks/redis/spec/client_spec.rb | 27 + cookbooks/redis/spec/default_spec.rb | 19 + cookbooks/redis/spec/server_spec.rb | 69 + cookbooks/redis/spec/spec_helper.rb | 20 + .../default/default_redis-server.erb | 12 + .../redis/templates/default/redis.conf.erb | 556 + cookbooks/redis/test/.chef/knife.rb | 2 + .../default/serverspec/default_spec.rb | 41 + cookbooks/redis/test/support/keys/README.md | 17 + cookbooks/redis/test/support/keys/vagrant | 27 + cookbooks/redis/test/support/keys/vagrant.pub | 1 + .../redis/test/support/rubocop/disabled.yml | 25 + .../redis/test/support/rubocop/enabled.yml | 652 + cookbooks/rsyslog/.gitignore | 41 + cookbooks/rsyslog/.kitchen.busted.yml | 86 + cookbooks/rsyslog/.kitchen.cloud.yml | 92 + cookbooks/rsyslog/.kitchen.yml | 44 + cookbooks/rsyslog/.rubocop.yml | 17 + cookbooks/rsyslog/.travis.yml | 75 + cookbooks/rsyslog/Berksfile | 7 + cookbooks/rsyslog/CHANGELOG.md | 167 + cookbooks/rsyslog/CONTRIBUTING.md | 196 + cookbooks/rsyslog/Gemfile | 37 + cookbooks/rsyslog/Guardfile | 35 + cookbooks/rsyslog/LICENSE | 201 + cookbooks/rsyslog/README.md | 247 + cookbooks/rsyslog/Rakefile | 59 + cookbooks/rsyslog/TESTING.md | 187 + cookbooks/rsyslog/attributes/default.rb | 124 + cookbooks/rsyslog/chefignore | 100 + cookbooks/rsyslog/libraries/helpers.rb | 18 + cookbooks/rsyslog/metadata.json | 375 + cookbooks/rsyslog/metadata.rb | 131 + cookbooks/rsyslog/providers/file_input.rb | 39 + cookbooks/rsyslog/recipes/client.rb | 72 + cookbooks/rsyslog/recipes/default.rb | 89 + cookbooks/rsyslog/recipes/server.rb | 44 + cookbooks/rsyslog/resources/file_input.rb | 28 + .../default/35-server-per-host.conf.erb | 62 + .../templates/default/49-relp.conf.erb | 10 + .../templates/default/49-remote.conf.erb | 28 + .../templates/default/50-default.conf.erb | 6 + .../templates/default/file-input.conf.erb | 15 + .../templates/default/omnios-manifest.xml.erb | 30 + .../templates/default/rsyslog.conf.erb | 106 + .../templates/smartos/50-default.conf.erb | 18 + cookbooks/runit/CHANGELOG.md | 192 + cookbooks/runit/README.md | 421 + cookbooks/runit/attributes/default.rb | 62 + cookbooks/runit/files/default/runit.seed | 1 + cookbooks/runit/files/default/runsvdir | 0 cookbooks/runit/files/ubuntu-6.10/runsvdir | 6 + cookbooks/runit/files/ubuntu-7.04/runsvdir | 7 + cookbooks/runit/files/ubuntu-7.10/runsvdir | 7 + cookbooks/runit/files/ubuntu-8.04/runsvdir | 7 + cookbooks/runit/libraries/default.rb | 0 cookbooks/runit/libraries/helpers.rb | 45 + cookbooks/runit/libraries/matchers.rb | 67 + .../runit/libraries/provider_runit_service.rb | 550 + .../runit/libraries/resource_runit_service.rb | 249 + cookbooks/runit/metadata.json | 40 + cookbooks/runit/recipes/default.rb | 85 + cookbooks/runit/templates/debian/init.d.erb | 66 + .../runit/templates/default/log-config.erb | 24 + .../runit/templates/gentoo/runit-start.sh.erb | 32 + cookbooks/smf/README.md | 370 + cookbooks/smf/libraries/helper.rb | 9 + cookbooks/smf/libraries/matchers.rb | 9 + cookbooks/smf/libraries/rbac_helper.rb | 31 + cookbooks/smf/libraries/xml_builder.rb | 209 + cookbooks/smf/metadata.json | 32 + cookbooks/smf/metadata.rb | 13 + cookbooks/smf/providers/default.rb | 143 + cookbooks/smf/recipes/SMFServicesOK.rb | 25 + cookbooks/smf/recipes/default.rb | 7 + cookbooks/smf/resources/default.rb | 124 + .../templates/default/SMFServicesOK.sh.erb | 13 + .../default/SMFServicesOK.snmpd.conf.erb | 1 + cookbooks/ssh_known_hosts/CHANGELOG.md | 73 + cookbooks/ssh_known_hosts/README.md | 202 + .../ssh_known_hosts/attributes/default.rb | 23 + .../ssh_known_hosts/libraries/matchers.rb | 7 + cookbooks/ssh_known_hosts/metadata.json | 31 + cookbooks/ssh_known_hosts/providers/entry.rb | 78 + cookbooks/ssh_known_hosts/recipes/cacher.rb | 60 + cookbooks/ssh_known_hosts/recipes/default.rb | 87 + cookbooks/ssh_known_hosts/resources/entry.rb | 30 + cookbooks/sudo/CHANGELOG.md | 119 + cookbooks/sudo/README.md | 307 + cookbooks/sudo/attributes/default.rb | 35 + cookbooks/sudo/files/default/README | 17 + cookbooks/sudo/libraries/matchers.rb | 9 + cookbooks/sudo/metadata.json | 71 + cookbooks/sudo/metadata.rb | 46 + cookbooks/sudo/providers/default.rb | 148 + cookbooks/sudo/recipes/default.rb | 55 + cookbooks/sudo/resources/default.rb | 50 + cookbooks/sudo/templates/default/sudoer.erb | 14 + cookbooks/sudo/templates/default/sudoers.erb | 27 + cookbooks/sudo/templates/mac_os_x/sudoers.erb | 23 + cookbooks/ufw/CHANGELOG.md | 30 + cookbooks/ufw/README.md | 144 + cookbooks/ufw/attributes/default.rb | 2 + cookbooks/ufw/metadata.json | 40 + cookbooks/ufw/metadata.rb | 21 + cookbooks/ufw/recipes/databag.rb | 58 + cookbooks/ufw/recipes/default.rb | 90 + cookbooks/ufw/recipes/disable.rb | 23 + cookbooks/ufw/recipes/recipes.rb | 41 + cookbooks/ufw/recipes/securitylevel.rb | 41 + cookbooks/unattended-upgrades/CHANGELOG.md | 22 + cookbooks/unattended-upgrades/README.md | 95 + .../unattended-upgrades/attributes/default.rb | 24 + .../files/default/test/default_test.rb | 45 + .../files/default/test/support/helpers.rb | 9 + cookbooks/unattended-upgrades/metadata.json | 32 + cookbooks/unattended-upgrades/metadata.rb | 14 + .../unattended-upgrades/recipes/default.rb | 63 + .../templates/default/auto-upgrades.conf.erb | 4 + .../default/unattended-upgrades.conf.erb | 67 + cookbooks/users/CHANGELOG.md | 77 + cookbooks/users/README.md | 193 + cookbooks/users/libraries/helpers.rb | 29 + cookbooks/users/libraries/matchers.rb | 15 + cookbooks/users/metadata.json | 40 + cookbooks/users/providers/manage.rb | 180 + cookbooks/users/recipes/default.rb | 20 + cookbooks/users/recipes/sysadmins.rb | 26 + cookbooks/users/resources/manage.rb | 44 + .../templates/default/authorized_keys.erb | 6 + .../users/templates/default/private_key.erb | 1 + .../templates/default/public_key.pub.erb | 1 + cookbooks/windows/CHANGELOG.md | 320 + cookbooks/windows/README.md | 749 + cookbooks/windows/attributes/default.rb | 24 + .../handlers/windows_reboot_handler.rb | 76 + cookbooks/windows/libraries/feature_base.rb | 59 + cookbooks/windows/libraries/matchers.rb | 465 + .../windows/libraries/powershell_helper.rb | 59 + cookbooks/windows/libraries/powershell_out.rb | 79 + .../windows/libraries/registry_helper.rb | 364 + cookbooks/windows/libraries/version.rb | 207 + .../libraries/windows_architecture_helper.rb | 87 + cookbooks/windows/libraries/windows_helper.rb | 148 + .../windows/libraries/windows_package.rb | 224 + .../windows/libraries/windows_privileged.rb | 94 + cookbooks/windows/libraries/wmi_helper.rb | 32 + cookbooks/windows/metadata.json | 31 + cookbooks/windows/providers/auto_run.rb | 33 + cookbooks/windows/providers/batch.rb | 63 + cookbooks/windows/providers/feature_dism.rb | 64 + .../windows/providers/feature_powershell.rb | 38 + .../providers/feature_servermanagercmd.rb | 61 + cookbooks/windows/providers/font.rb | 69 + cookbooks/windows/providers/pagefile.rb | 153 + cookbooks/windows/providers/path.rb | 52 + cookbooks/windows/providers/printer.rb | 101 + cookbooks/windows/providers/printer_port.rb | 103 + cookbooks/windows/providers/reboot.rb | 33 + cookbooks/windows/providers/registry.rb | 75 + cookbooks/windows/providers/shortcut.rb | 56 + cookbooks/windows/providers/task.rb | 167 + cookbooks/windows/providers/zipfile.rb | 93 + cookbooks/windows/recipes/default.rb | 34 + cookbooks/windows/recipes/reboot_handler.rb | 32 + cookbooks/windows/resources/auto_run.rb | 30 + cookbooks/windows/resources/batch.rb | 36 + cookbooks/windows/resources/feature.rb | 44 + cookbooks/windows/resources/font.rb | 25 + cookbooks/windows/resources/pagefile.rb | 29 + cookbooks/windows/resources/path.rb | 28 + cookbooks/windows/resources/printer.rb | 41 + cookbooks/windows/resources/printer_port.rb | 40 + cookbooks/windows/resources/reboot.rb | 29 + cookbooks/windows/resources/registry.rb | 34 + cookbooks/windows/resources/shortcut.rb | 35 + cookbooks/windows/resources/task.rb | 50 + cookbooks/windows/resources/zipfile.rb | 33 + cookbooks/xml/CHANGELOG.md | 73 + cookbooks/xml/README.md | 52 + cookbooks/xml/attributes/default.rb | 35 + cookbooks/xml/metadata.json | 42 + cookbooks/xml/recipes/default.rb | 25 + cookbooks/xml/recipes/ruby.rb | 46 + cookbooks/yum-epel/CHANGELOG.md | 63 + cookbooks/yum-epel/README.md | 162 + cookbooks/yum-epel/attributes/default.rb | 1 + .../yum-epel/attributes/epel-debuginfo.rb | 28 + cookbooks/yum-epel/attributes/epel-source.rb | 28 + .../attributes/epel-testing-debuginfo.rb | 28 + .../attributes/epel-testing-source.rb | 28 + cookbooks/yum-epel/attributes/epel-testing.rb | 28 + cookbooks/yum-epel/attributes/epel.rb | 28 + cookbooks/yum-epel/metadata.json | 34 + cookbooks/yum-epel/recipes/default.rb | 61 + cookbooks/yum-mysql-community/CHANGELOG.md | 98 + cookbooks/yum-mysql-community/README.md | 137 + .../attributes/mysql-connectors-community.rb | 35 + .../attributes/mysql55-community.rb | 33 + .../attributes/mysql56-community.rb | 35 + .../attributes/mysql57-community.rb | 35 + .../files/default/mysql_pubkey.asc | 33 + cookbooks/yum-mysql-community/metadata.json | 30 + .../yum-mysql-community/recipes/connectors.rb | 48 + .../yum-mysql-community/recipes/mysql55.rb | 48 + .../yum-mysql-community/recipes/mysql56.rb | 48 + .../yum-mysql-community/recipes/mysql57.rb | 48 + cookbooks/yum/CHANGELOG.md | 276 + cookbooks/yum/README.md | 280 + cookbooks/yum/attributes/main.rb | 99 + cookbooks/yum/libraries/matchers.rb | 27 + cookbooks/yum/metadata.json | 1 + cookbooks/yum/providers/globalconfig.rb | 41 + cookbooks/yum/providers/repository.rb | 106 + cookbooks/yum/recipes/default.rb | 26 + cookbooks/yum/resources/globalconfig.rb | 108 + cookbooks/yum/resources/repository.rb | 68 + cookbooks/yum/templates/default/main.erb | 269 + cookbooks/yum/templates/default/repo.erb | 125 + data_bags/.gitkeep | 0 .../certificates/wildcard_kosmos_org.json | 15 + data_bags/credentials/hal8000_freenode.json | 15 + data_bags/credentials/schlupp_5apps.json | 15 + data_bags/credentials/smtp.json | 21 + data_bags/users/kare.json | 9 + doc/encrypted_data_bags.md | 2 + environments/.gitkeep | 0 nodes/dev.kosmos.org.json | 11 + roles/.gitkeep | 0 site-cookbooks/kosmos-base/README.md | 4 + site-cookbooks/kosmos-base/metadata.rb | 16 + site-cookbooks/kosmos-base/recipes/default.rb | 49 + .../kosmos-base/recipes/firewall.rb | 19 + site-cookbooks/kosmos-hubot/README.md | 4 + site-cookbooks/kosmos-hubot/metadata.rb | 9 + .../kosmos-hubot/recipes/default.rb | 96 + .../default/nodejs.systemd.service.erb | 17 + site-cookbooks/kosmos-nginx/README.md | 4 + site-cookbooks/kosmos-nginx/metadata.rb | 9 + .../kosmos-nginx/recipes/default.rb | 32 + site-cookbooks/kosmos-nodejs/README.md | 68 + site-cookbooks/kosmos-nodejs/metadata.rb | 7 + .../kosmos-nodejs/recipes/default.rb | 18 + site-cookbooks/kosmos-postfix/README.md | 5 + site-cookbooks/kosmos-postfix/metadata.rb | 9 + .../kosmos-postfix/recipes/default.rb | 23 + site-cookbooks/kosmos-redis/README.md | 4 + site-cookbooks/kosmos-redis/metadata.rb | 9 + .../kosmos-redis/recipes/default.rb | 11 + site-cookbooks/sockethub/CHANGELOG.md | 13 + site-cookbooks/sockethub/README.md | 68 + .../sockethub/attributes/default.rb | 2 + site-cookbooks/sockethub/metadata.rb | 14 + site-cookbooks/sockethub/recipes/default.rb | 32 + site-cookbooks/sockethub/recipes/proxy.rb | 50 + .../default/nginx_conf_sockethub.erb | 55 + .../default/nodejs.systemd.service.erb | 16 + 1151 files changed, 185163 insertions(+) create mode 100644 .chef/knife.rb create mode 100644 Batali create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 README.md create mode 100644 cookbooks/7-zip/CHANGELOG.md create mode 100644 cookbooks/7-zip/README.md create mode 100644 cookbooks/7-zip/attributes/default.rb create mode 100644 cookbooks/7-zip/metadata.json create mode 100644 cookbooks/7-zip/metadata.rb create mode 100644 cookbooks/7-zip/recipes/default.rb create mode 100644 cookbooks/apache2/CHANGELOG.md create mode 100644 cookbooks/apache2/README.md create mode 100644 cookbooks/apache2/attributes/default.rb create mode 100644 cookbooks/apache2/attributes/mod_auth_cas.rb create mode 100644 cookbooks/apache2/attributes/mod_auth_openid.rb create mode 100644 cookbooks/apache2/attributes/mod_fastcgi.rb create mode 100644 cookbooks/apache2/attributes/mod_pagespeed.rb create mode 100644 cookbooks/apache2/attributes/mod_php5.rb create mode 100644 cookbooks/apache2/attributes/mod_ssl.rb create mode 100644 cookbooks/apache2/definitions/apache_conf.rb create mode 100644 cookbooks/apache2/definitions/apache_config.rb create mode 100644 cookbooks/apache2/definitions/apache_mod.rb create mode 100644 cookbooks/apache2/definitions/apache_module.rb create mode 100644 cookbooks/apache2/definitions/apache_site.rb create mode 100644 cookbooks/apache2/definitions/web_app.rb create mode 100644 cookbooks/apache2/files/default/apache2_module_conf_generate.pl create mode 100644 cookbooks/apache2/metadata.json create mode 100644 cookbooks/apache2/recipes/default.rb create mode 100644 cookbooks/apache2/recipes/mod_access_compat.rb create mode 100644 cookbooks/apache2/recipes/mod_actions.rb create mode 100644 cookbooks/apache2/recipes/mod_alias.rb create mode 100644 cookbooks/apache2/recipes/mod_allowmethods.rb create mode 100644 cookbooks/apache2/recipes/mod_apreq2.rb create mode 100644 cookbooks/apache2/recipes/mod_asis.rb create mode 100644 cookbooks/apache2/recipes/mod_auth_basic.rb create mode 100644 cookbooks/apache2/recipes/mod_auth_cas.rb create mode 100644 cookbooks/apache2/recipes/mod_auth_digest.rb create mode 100644 cookbooks/apache2/recipes/mod_auth_form.rb create mode 100644 cookbooks/apache2/recipes/mod_auth_openid.rb create mode 100644 cookbooks/apache2/recipes/mod_authn_anon.rb create mode 100644 cookbooks/apache2/recipes/mod_authn_core.rb create mode 100644 cookbooks/apache2/recipes/mod_authn_dbd.rb create mode 100644 cookbooks/apache2/recipes/mod_authn_dbm.rb create mode 100644 cookbooks/apache2/recipes/mod_authn_file.rb create mode 100644 cookbooks/apache2/recipes/mod_authn_socache.rb create mode 100644 cookbooks/apache2/recipes/mod_authnz_ldap.rb create mode 100644 cookbooks/apache2/recipes/mod_authz_core.rb create mode 100644 cookbooks/apache2/recipes/mod_authz_dbd.rb create mode 100644 cookbooks/apache2/recipes/mod_authz_dbm.rb create mode 100644 cookbooks/apache2/recipes/mod_authz_default.rb create mode 100644 cookbooks/apache2/recipes/mod_authz_groupfile.rb create mode 100644 cookbooks/apache2/recipes/mod_authz_host.rb create mode 100644 cookbooks/apache2/recipes/mod_authz_owner.rb create mode 100644 cookbooks/apache2/recipes/mod_authz_user.rb create mode 100644 cookbooks/apache2/recipes/mod_autoindex.rb create mode 100644 cookbooks/apache2/recipes/mod_buffer.rb create mode 100644 cookbooks/apache2/recipes/mod_cache.rb create mode 100644 cookbooks/apache2/recipes/mod_cache_disk.rb create mode 100644 cookbooks/apache2/recipes/mod_cache_socache.rb create mode 100644 cookbooks/apache2/recipes/mod_cgi.rb create mode 100644 cookbooks/apache2/recipes/mod_cgid.rb create mode 100644 cookbooks/apache2/recipes/mod_charset_lite.rb create mode 100644 cookbooks/apache2/recipes/mod_cloudflare.rb create mode 100644 cookbooks/apache2/recipes/mod_data.rb create mode 100644 cookbooks/apache2/recipes/mod_dav.rb create mode 100644 cookbooks/apache2/recipes/mod_dav_fs.rb create mode 100644 cookbooks/apache2/recipes/mod_dav_lock.rb create mode 100644 cookbooks/apache2/recipes/mod_dav_svn.rb create mode 100644 cookbooks/apache2/recipes/mod_dbd.rb create mode 100644 cookbooks/apache2/recipes/mod_deflate.rb create mode 100644 cookbooks/apache2/recipes/mod_dialup.rb create mode 100644 cookbooks/apache2/recipes/mod_dir.rb create mode 100644 cookbooks/apache2/recipes/mod_dump_io.rb create mode 100644 cookbooks/apache2/recipes/mod_echo.rb create mode 100644 cookbooks/apache2/recipes/mod_env.rb create mode 100644 cookbooks/apache2/recipes/mod_expires.rb create mode 100644 cookbooks/apache2/recipes/mod_ext_filter.rb create mode 100644 cookbooks/apache2/recipes/mod_fastcgi.rb create mode 100644 cookbooks/apache2/recipes/mod_fcgid.rb create mode 100644 cookbooks/apache2/recipes/mod_file_cache.rb create mode 100644 cookbooks/apache2/recipes/mod_filter.rb create mode 100644 cookbooks/apache2/recipes/mod_headers.rb create mode 100644 cookbooks/apache2/recipes/mod_heartbeat.rb create mode 100644 cookbooks/apache2/recipes/mod_heartmonitor.rb create mode 100644 cookbooks/apache2/recipes/mod_include.rb create mode 100644 cookbooks/apache2/recipes/mod_info.rb create mode 100644 cookbooks/apache2/recipes/mod_jk.rb create mode 100644 cookbooks/apache2/recipes/mod_lbmethod_bybusyness.rb create mode 100644 cookbooks/apache2/recipes/mod_lbmethod_byrequests.rb create mode 100644 cookbooks/apache2/recipes/mod_lbmethod_bytraffic.rb create mode 100644 cookbooks/apache2/recipes/mod_lbmethod_heartbeat.rb create mode 100644 cookbooks/apache2/recipes/mod_ldap.rb create mode 100644 cookbooks/apache2/recipes/mod_log_config.rb create mode 100644 cookbooks/apache2/recipes/mod_log_debug.rb create mode 100644 cookbooks/apache2/recipes/mod_log_forensic.rb create mode 100644 cookbooks/apache2/recipes/mod_logio.rb create mode 100644 cookbooks/apache2/recipes/mod_lua.rb create mode 100644 cookbooks/apache2/recipes/mod_macro.rb create mode 100644 cookbooks/apache2/recipes/mod_mime.rb create mode 100644 cookbooks/apache2/recipes/mod_mime_magic.rb create mode 100644 cookbooks/apache2/recipes/mod_negotiation.rb create mode 100644 cookbooks/apache2/recipes/mod_pagespeed.rb create mode 100644 cookbooks/apache2/recipes/mod_perl.rb create mode 100644 cookbooks/apache2/recipes/mod_php5.rb create mode 100644 cookbooks/apache2/recipes/mod_proxy.rb create mode 100644 cookbooks/apache2/recipes/mod_proxy_ajp.rb create mode 100644 cookbooks/apache2/recipes/mod_proxy_balancer.rb create mode 100644 cookbooks/apache2/recipes/mod_proxy_connect.rb create mode 100644 cookbooks/apache2/recipes/mod_proxy_express.rb create mode 100644 cookbooks/apache2/recipes/mod_proxy_fcgi.rb create mode 100644 cookbooks/apache2/recipes/mod_proxy_fdpass.rb create mode 100644 cookbooks/apache2/recipes/mod_proxy_ftp.rb create mode 100644 cookbooks/apache2/recipes/mod_proxy_html.rb create mode 100644 cookbooks/apache2/recipes/mod_proxy_http.rb create mode 100644 cookbooks/apache2/recipes/mod_proxy_scgi.rb create mode 100644 cookbooks/apache2/recipes/mod_proxy_wstunnel.rb create mode 100644 cookbooks/apache2/recipes/mod_python.rb create mode 100644 cookbooks/apache2/recipes/mod_ratelimit.rb create mode 100644 cookbooks/apache2/recipes/mod_reflector.rb create mode 100644 cookbooks/apache2/recipes/mod_remoteip.rb create mode 100644 cookbooks/apache2/recipes/mod_reqtimeout.rb create mode 100644 cookbooks/apache2/recipes/mod_request.rb create mode 100644 cookbooks/apache2/recipes/mod_rewrite.rb create mode 100644 cookbooks/apache2/recipes/mod_sed.rb create mode 100644 cookbooks/apache2/recipes/mod_session.rb create mode 100644 cookbooks/apache2/recipes/mod_session_cookie.rb create mode 100644 cookbooks/apache2/recipes/mod_session_crypto.rb create mode 100644 cookbooks/apache2/recipes/mod_session_dbd.rb create mode 100644 cookbooks/apache2/recipes/mod_setenvif.rb create mode 100644 cookbooks/apache2/recipes/mod_slotmem_plain.rb create mode 100644 cookbooks/apache2/recipes/mod_slotmem_shm.rb create mode 100644 cookbooks/apache2/recipes/mod_socache_dbm.rb create mode 100644 cookbooks/apache2/recipes/mod_socache_memcache.rb create mode 100644 cookbooks/apache2/recipes/mod_socache_shmcb.rb create mode 100644 cookbooks/apache2/recipes/mod_speling.rb create mode 100644 cookbooks/apache2/recipes/mod_ssl.rb create mode 100644 cookbooks/apache2/recipes/mod_status.rb create mode 100644 cookbooks/apache2/recipes/mod_substitute.rb create mode 100644 cookbooks/apache2/recipes/mod_suexec.rb create mode 100644 cookbooks/apache2/recipes/mod_systemd.rb create mode 100644 cookbooks/apache2/recipes/mod_unique_id.rb create mode 100644 cookbooks/apache2/recipes/mod_unixd.rb create mode 100644 cookbooks/apache2/recipes/mod_userdir.rb create mode 100644 cookbooks/apache2/recipes/mod_usertrack.rb create mode 100644 cookbooks/apache2/recipes/mod_vhost_alias.rb create mode 100644 cookbooks/apache2/recipes/mod_wsgi.rb create mode 100644 cookbooks/apache2/recipes/mod_xml2enc.rb create mode 100644 cookbooks/apache2/recipes/mod_xsendfile.rb create mode 100644 cookbooks/apache2/recipes/mpm_event.rb create mode 100644 cookbooks/apache2/recipes/mpm_prefork.rb create mode 100644 cookbooks/apache2/recipes/mpm_worker.rb create mode 100644 cookbooks/apache2/templates/default/a2disconf.erb create mode 100644 cookbooks/apache2/templates/default/a2dismod.erb create mode 100644 cookbooks/apache2/templates/default/a2dissite.erb create mode 100644 cookbooks/apache2/templates/default/a2enconf.erb create mode 100644 cookbooks/apache2/templates/default/a2enmod.erb create mode 100644 cookbooks/apache2/templates/default/a2ensite.erb create mode 100644 cookbooks/apache2/templates/default/apache2.conf.erb create mode 100644 cookbooks/apache2/templates/default/charset.conf.erb create mode 100644 cookbooks/apache2/templates/default/default-site.conf.erb create mode 100644 cookbooks/apache2/templates/default/envvars.erb create mode 100644 cookbooks/apache2/templates/default/etc-sysconfig-httpd.erb create mode 100644 cookbooks/apache2/templates/default/mods/README create mode 100644 cookbooks/apache2/templates/default/mods/actions.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/alias.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/auth_cas.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/auth_cas.load.erb create mode 100644 cookbooks/apache2/templates/default/mods/authopenid.load.erb create mode 100644 cookbooks/apache2/templates/default/mods/autoindex.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/cache_disk.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/cgid.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/dav_fs.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/deflate.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/dir.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/fastcgi.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/fcgid.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/include.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/include.erb create mode 100644 cookbooks/apache2/templates/default/mods/info.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/ldap.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/mime.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/mime_magic.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/mpm_event.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/mpm_prefork.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/mpm_worker.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/negotiation.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/pagespeed.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/php5.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/proxy.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/proxy_balancer.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/proxy_ftp.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/reqtimeout.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/setenvif.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/ssl.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/status.conf.erb create mode 100644 cookbooks/apache2/templates/default/mods/userdir.conf.erb create mode 100644 cookbooks/apache2/templates/default/port_apache.erb create mode 100644 cookbooks/apache2/templates/default/ports.conf.erb create mode 100644 cookbooks/apache2/templates/default/security.conf.erb create mode 100644 cookbooks/apache2/templates/default/web_app.conf.erb create mode 100644 cookbooks/application/CHANGELOG.md create mode 100644 cookbooks/application/README.md create mode 100644 cookbooks/application/libraries/default.rb create mode 100644 cookbooks/application/libraries/matchers.rb create mode 100644 cookbooks/application/metadata.json create mode 100644 cookbooks/application/providers/default.rb create mode 100644 cookbooks/application/recipes/default.rb create mode 100644 cookbooks/application/resources/default.rb create mode 100644 cookbooks/application/templates/default/deploy-ssh-wrapper.erb create mode 100644 cookbooks/application_nodejs/LICENSE create mode 100644 cookbooks/application_nodejs/README.md create mode 100644 cookbooks/application_nodejs/metadata.rb create mode 100644 cookbooks/application_nodejs/providers/nodejs.rb create mode 100644 cookbooks/application_nodejs/resources/nodejs.rb create mode 100644 cookbooks/application_nodejs/templates/default/nodejs.systemd.service.erb create mode 100644 cookbooks/application_nodejs/templates/default/nodejs.upstart.conf.erb create mode 100644 cookbooks/apt/.gitignore create mode 100644 cookbooks/apt/.kitchen.cloud.yml create mode 100644 cookbooks/apt/.kitchen.yml create mode 100644 cookbooks/apt/.rubocop.yml create mode 100644 cookbooks/apt/.travis.yml create mode 100644 cookbooks/apt/Berksfile create mode 100644 cookbooks/apt/CHANGELOG.md create mode 100644 cookbooks/apt/CONTRIBUTING create mode 100644 cookbooks/apt/Gemfile create mode 100644 cookbooks/apt/Guardfile create mode 100644 cookbooks/apt/LICENSE create mode 100644 cookbooks/apt/README.md create mode 100644 cookbooks/apt/Rakefile create mode 100644 cookbooks/apt/TESTING.md create mode 100644 cookbooks/apt/attributes/default.rb create mode 100644 cookbooks/apt/files/default/15update-stamp create mode 100644 cookbooks/apt/files/default/apt-proxy-v2.conf create mode 100644 cookbooks/apt/libraries/helpers.rb create mode 100644 cookbooks/apt/libraries/matchers.rb create mode 100644 cookbooks/apt/libraries/network.rb create mode 100644 cookbooks/apt/metadata.json create mode 100644 cookbooks/apt/metadata.rb create mode 100644 cookbooks/apt/providers/preference.rb create mode 100644 cookbooks/apt/providers/repository.rb create mode 100644 cookbooks/apt/recipes/cacher-client.rb create mode 100644 cookbooks/apt/recipes/cacher-ng.rb create mode 100644 cookbooks/apt/recipes/default.rb create mode 100644 cookbooks/apt/recipes/unattended-upgrades.rb create mode 100644 cookbooks/apt/resources/preference.rb create mode 100644 cookbooks/apt/resources/repository.rb create mode 100644 cookbooks/apt/templates/debian-6.0/acng.conf.erb create mode 100644 cookbooks/apt/templates/default/01proxy.erb create mode 100644 cookbooks/apt/templates/default/20auto-upgrades.erb create mode 100644 cookbooks/apt/templates/default/50unattended-upgrades.erb create mode 100644 cookbooks/apt/templates/default/acng.conf.erb create mode 100644 cookbooks/apt/templates/default/unattended-upgrades.seed.erb create mode 100644 cookbooks/apt/templates/ubuntu-10.04/acng.conf.erb create mode 100644 cookbooks/ark/CHANGELOG.md create mode 100644 cookbooks/ark/README.md create mode 100644 cookbooks/ark/attributes/default.rb create mode 100644 cookbooks/ark/files/default/foo.tar.gz create mode 100644 cookbooks/ark/files/default/foo.tbz create mode 100644 cookbooks/ark/files/default/foo.tgz create mode 100644 cookbooks/ark/files/default/foo.txz create mode 100644 cookbooks/ark/files/default/foo.zip create mode 100644 cookbooks/ark/files/default/foo_sub.tar.gz create mode 100644 cookbooks/ark/files/default/foo_sub.zip create mode 100644 cookbooks/ark/files/default/tests/minitest/default_test.rb create mode 100644 cookbooks/ark/files/default/tests/minitest/support/helpers.rb create mode 100644 cookbooks/ark/files/default/tests/minitest/test_test.rb create mode 100644 cookbooks/ark/libraries/default.rb create mode 100644 cookbooks/ark/metadata.json create mode 100644 cookbooks/ark/metadata.rb create mode 100644 cookbooks/ark/providers/default.rb create mode 100644 cookbooks/ark/recipes/default.rb create mode 100644 cookbooks/ark/recipes/test.rb create mode 100644 cookbooks/ark/resources/default.rb create mode 100644 cookbooks/ark/templates/default/add_to_path.sh.erb create mode 100644 cookbooks/bluepill/CHANGELOG.md create mode 100644 cookbooks/bluepill/README.md create mode 100644 cookbooks/bluepill/attributes/default.rb create mode 100644 cookbooks/bluepill/metadata.json create mode 100644 cookbooks/bluepill/metadata.rb create mode 100644 cookbooks/bluepill/providers/service.rb create mode 100644 cookbooks/bluepill/recipes/default.rb create mode 100644 cookbooks/bluepill/recipes/rsyslog.rb create mode 100644 cookbooks/bluepill/resources/service.rb create mode 100644 cookbooks/bluepill/templates/default/bluepill_init.fedora.erb create mode 100644 cookbooks/bluepill/templates/default/bluepill_init.freebsd.erb create mode 100644 cookbooks/bluepill/templates/default/bluepill_init.lsb.erb create mode 100644 cookbooks/bluepill/templates/default/bluepill_init.rhel.erb create mode 100644 cookbooks/bluepill/templates/default/bluepill_rsyslog.conf.erb create mode 100644 cookbooks/build-essential/.envrc create mode 100644 cookbooks/build-essential/.gitignore create mode 100644 cookbooks/build-essential/.kitchen.cloud.yml create mode 100644 cookbooks/build-essential/.kitchen.yml create mode 100644 cookbooks/build-essential/.rubocop.yml create mode 100644 cookbooks/build-essential/.travis.yml create mode 100644 cookbooks/build-essential/Berksfile create mode 100644 cookbooks/build-essential/CHANGELOG.md create mode 100644 cookbooks/build-essential/CONTRIBUTING create mode 100644 cookbooks/build-essential/Gemfile create mode 100644 cookbooks/build-essential/Guardfile create mode 100644 cookbooks/build-essential/LICENSE create mode 100644 cookbooks/build-essential/README.md create mode 100644 cookbooks/build-essential/Rakefile create mode 100644 cookbooks/build-essential/attributes/default.rb create mode 100644 cookbooks/build-essential/libraries/matchers.rb create mode 100644 cookbooks/build-essential/libraries/timing.rb create mode 100644 cookbooks/build-essential/libraries/xcode_command_line_tools.rb create mode 100644 cookbooks/build-essential/matrix create mode 100644 cookbooks/build-essential/metadata.json create mode 100644 cookbooks/build-essential/metadata.rb create mode 100644 cookbooks/build-essential/recipes/_debian.rb create mode 100644 cookbooks/build-essential/recipes/_fedora.rb create mode 100644 cookbooks/build-essential/recipes/_freebsd.rb create mode 100644 cookbooks/build-essential/recipes/_mac_os_x.rb create mode 100644 cookbooks/build-essential/recipes/_omnios.rb create mode 100644 cookbooks/build-essential/recipes/_rhel.rb create mode 100644 cookbooks/build-essential/recipes/_smartos.rb create mode 100644 cookbooks/build-essential/recipes/_solaris2.rb create mode 100644 cookbooks/build-essential/recipes/_suse.rb create mode 100644 cookbooks/build-essential/recipes/default.rb create mode 100644 cookbooks/chef-solo-search/.gitignore create mode 100644 cookbooks/chef-solo-search/.travis.yml create mode 100644 cookbooks/chef-solo-search/CHANGELOG create mode 100644 cookbooks/chef-solo-search/Gemfile create mode 100644 cookbooks/chef-solo-search/LICENSE create mode 100644 cookbooks/chef-solo-search/NOTICE create mode 100644 cookbooks/chef-solo-search/README.md create mode 100644 cookbooks/chef-solo-search/Rakefile create mode 100644 cookbooks/chef-solo-search/libraries/search.rb create mode 100644 cookbooks/chef-solo-search/libraries/search/overrides.rb create mode 100644 cookbooks/chef-solo-search/libraries/search/parser.rb create mode 100644 cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/lucene.treetop create mode 100644 cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/lucene_nodes.rb create mode 100644 cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/query_transform.rb create mode 100644 cookbooks/chef-solo-search/metadata.json create mode 100644 cookbooks/chef-solo-search/metadata.rb create mode 100644 cookbooks/chef-solo-search/recipes/default.rb create mode 100644 cookbooks/chef-solo-search/tests/data/data_bags/node/alpha.json create mode 100644 cookbooks/chef-solo-search/tests/data/data_bags/node/beta.json create mode 100644 cookbooks/chef-solo-search/tests/data/data_bags/node/without_json_class.json create mode 100644 cookbooks/chef-solo-search/tests/data/data_bags/users/jerry.json create mode 100644 cookbooks/chef-solo-search/tests/data/data_bags/users/lea.json create mode 100644 cookbooks/chef-solo-search/tests/data/data_bags/users/mike.json create mode 100644 cookbooks/chef-solo-search/tests/data/data_bags/users/tom.json create mode 100644 cookbooks/chef-solo-search/tests/gemfiles/Gemfile.10 create mode 100644 cookbooks/chef-solo-search/tests/gemfiles/Gemfile.11 create mode 100644 cookbooks/chef-solo-search/tests/test_data_bags.rb create mode 100644 cookbooks/chef-solo-search/tests/test_search.rb create mode 100644 cookbooks/chef-sugar/CHANGELOG.md create mode 100644 cookbooks/chef-sugar/README.md create mode 100644 cookbooks/chef-sugar/metadata.json create mode 100644 cookbooks/chef-sugar/recipes/default.rb create mode 100644 cookbooks/chef_handler/CHANGELOG.md create mode 100644 cookbooks/chef_handler/README.md create mode 100644 cookbooks/chef_handler/attributes/default.rb create mode 100644 cookbooks/chef_handler/files/default/handlers/README create mode 100644 cookbooks/chef_handler/libraries/helpers.rb create mode 100644 cookbooks/chef_handler/libraries/matchers.rb create mode 100644 cookbooks/chef_handler/metadata.json create mode 100644 cookbooks/chef_handler/providers/default.rb create mode 100644 cookbooks/chef_handler/recipes/default.rb create mode 100644 cookbooks/chef_handler/recipes/json_file.rb create mode 100644 cookbooks/chef_handler/resources/default.rb create mode 100644 cookbooks/database/CHANGELOG.md create mode 100644 cookbooks/database/README.md create mode 100644 cookbooks/database/libraries/matchers.rb create mode 100644 cookbooks/database/libraries/provider_database_mysql.rb create mode 100644 cookbooks/database/libraries/provider_database_mysql_user.rb create mode 100644 cookbooks/database/libraries/provider_database_postgresql.rb create mode 100644 cookbooks/database/libraries/provider_database_postgresql_schema.rb create mode 100644 cookbooks/database/libraries/provider_database_postgresql_user.rb create mode 100644 cookbooks/database/libraries/provider_database_sql_server.rb create mode 100644 cookbooks/database/libraries/provider_database_sql_server_user.rb create mode 100644 cookbooks/database/libraries/resource_database.rb create mode 100644 cookbooks/database/libraries/resource_database_user.rb create mode 100644 cookbooks/database/libraries/resource_mysql_database.rb create mode 100644 cookbooks/database/libraries/resource_mysql_database_user.rb create mode 100644 cookbooks/database/libraries/resource_postgresql_database.rb create mode 100644 cookbooks/database/libraries/resource_postgresql_database_schema.rb create mode 100644 cookbooks/database/libraries/resource_postgresql_database_user.rb create mode 100644 cookbooks/database/libraries/resource_sql_server_database.rb create mode 100644 cookbooks/database/libraries/resource_sql_server_database_user.rb create mode 100644 cookbooks/database/metadata.json create mode 100644 cookbooks/database/recipes/postgresql.rb create mode 100644 cookbooks/database/templates/default/app_grants.sql.erb create mode 100644 cookbooks/database/templates/default/aws_config.erb create mode 100644 cookbooks/database/templates/default/chef-solo-database-snapshot.cron.erb create mode 100644 cookbooks/database/templates/default/chef-solo-database-snapshot.json.erb create mode 100644 cookbooks/database/templates/default/chef-solo-database-snapshot.rb.erb create mode 100644 cookbooks/database/templates/default/ebs-backup-cron.erb create mode 100644 cookbooks/database/templates/default/ebs-db-backup.sh.erb create mode 100644 cookbooks/database/templates/default/ebs-db-restore.sh.erb create mode 100644 cookbooks/database/templates/default/s3cfg.erb create mode 100644 cookbooks/firewall/CHANGELOG.md create mode 100644 cookbooks/firewall/README.md create mode 100644 cookbooks/firewall/attributes/default.rb create mode 100644 cookbooks/firewall/attributes/ufw.rb create mode 100644 cookbooks/firewall/libraries/helpers.rb create mode 100644 cookbooks/firewall/libraries/matchers.rb create mode 100644 cookbooks/firewall/libraries/provider_firewall_firewalld.rb create mode 100644 cookbooks/firewall/libraries/provider_firewall_iptables.rb create mode 100644 cookbooks/firewall/libraries/provider_firewall_rule_firewalld.rb create mode 100644 cookbooks/firewall/libraries/provider_firewall_rule_iptables.rb create mode 100644 cookbooks/firewall/libraries/provider_firewall_rule_ufw.rb create mode 100644 cookbooks/firewall/libraries/provider_firewall_ufw.rb create mode 100644 cookbooks/firewall/libraries/resource_firewall.rb create mode 100644 cookbooks/firewall/libraries/resource_firewall_rule.rb create mode 100644 cookbooks/firewall/libraries/z_provider_mapping.rb create mode 100644 cookbooks/firewall/metadata.json create mode 100644 cookbooks/firewall/recipes/default.rb create mode 100644 cookbooks/firewall/templates/default/ufw/default.erb create mode 100644 cookbooks/homebrew/CHANGELOG.md create mode 100644 cookbooks/homebrew/README.md create mode 100644 cookbooks/homebrew/attributes/default.rb create mode 100644 cookbooks/homebrew/libraries/homebrew_mixin.rb create mode 100644 cookbooks/homebrew/libraries/homebrew_package.rb create mode 100644 cookbooks/homebrew/libraries/matchers.rb create mode 100644 cookbooks/homebrew/metadata.json create mode 100644 cookbooks/homebrew/metadata.rb create mode 100644 cookbooks/homebrew/providers/cask.rb create mode 100644 cookbooks/homebrew/providers/tap.rb create mode 100644 cookbooks/homebrew/recipes/cask.rb create mode 100644 cookbooks/homebrew/recipes/default.rb create mode 100644 cookbooks/homebrew/recipes/install_casks.rb create mode 100644 cookbooks/homebrew/recipes/install_formulas.rb create mode 100644 cookbooks/homebrew/recipes/install_taps.rb create mode 100644 cookbooks/homebrew/resources/cask.rb create mode 100644 cookbooks/homebrew/resources/tap.rb create mode 100644 cookbooks/hostname/.gitignore create mode 100644 cookbooks/hostname/.kitchen.yml create mode 100644 cookbooks/hostname/.rubocop.yml create mode 100644 cookbooks/hostname/.travis.yml create mode 100644 cookbooks/hostname/Berksfile create mode 100644 cookbooks/hostname/CHANGELOG.md create mode 100644 cookbooks/hostname/Gemfile create mode 100644 cookbooks/hostname/README.md create mode 100644 cookbooks/hostname/Strainerfile create mode 100644 cookbooks/hostname/TESTING.md create mode 100644 cookbooks/hostname/Thorfile create mode 100644 cookbooks/hostname/attributes/default.rb create mode 100644 cookbooks/hostname/chefignore create mode 100644 cookbooks/hostname/metadata.rb create mode 100644 cookbooks/hostname/recipes/default.rb create mode 100644 cookbooks/hostname/recipes/vmware.rb create mode 100644 cookbooks/hostsfile/CHANGELOG.md create mode 100644 cookbooks/hostsfile/README.md create mode 100644 cookbooks/hostsfile/attributes/default.rb create mode 100644 cookbooks/hostsfile/libraries/entry.rb create mode 100644 cookbooks/hostsfile/libraries/manipulator.rb create mode 100644 cookbooks/hostsfile/libraries/matchers.rb create mode 100644 cookbooks/hostsfile/metadata.json create mode 100644 cookbooks/hostsfile/metadata.rb create mode 100644 cookbooks/hostsfile/providers/entry.rb create mode 100644 cookbooks/hostsfile/resources/entry.rb create mode 100644 cookbooks/iis/CHANGELOG.md create mode 100644 cookbooks/iis/README.md create mode 100644 cookbooks/iis/attributes/default.rb create mode 100644 cookbooks/iis/libraries/helper.rb create mode 100644 cookbooks/iis/libraries/matcher.rb create mode 100644 cookbooks/iis/metadata.json create mode 100644 cookbooks/iis/providers/app.rb create mode 100644 cookbooks/iis/providers/config.rb create mode 100644 cookbooks/iis/providers/module.rb create mode 100644 cookbooks/iis/providers/pool.rb create mode 100644 cookbooks/iis/providers/section.rb create mode 100644 cookbooks/iis/providers/site.rb create mode 100644 cookbooks/iis/providers/vdir.rb create mode 100644 cookbooks/iis/recipes/default.rb create mode 100644 cookbooks/iis/recipes/mod_application_initialization.rb create mode 100644 cookbooks/iis/recipes/mod_aspnet.rb create mode 100644 cookbooks/iis/recipes/mod_aspnet45.rb create mode 100644 cookbooks/iis/recipes/mod_auth_anonymous.rb create mode 100644 cookbooks/iis/recipes/mod_auth_basic.rb create mode 100644 cookbooks/iis/recipes/mod_auth_digest.rb create mode 100644 cookbooks/iis/recipes/mod_auth_windows.rb create mode 100644 cookbooks/iis/recipes/mod_cgi.rb create mode 100644 cookbooks/iis/recipes/mod_compress_dynamic.rb create mode 100644 cookbooks/iis/recipes/mod_compress_static.rb create mode 100644 cookbooks/iis/recipes/mod_ftp.rb create mode 100644 cookbooks/iis/recipes/mod_iis6_metabase_compat.rb create mode 100644 cookbooks/iis/recipes/mod_isapi.rb create mode 100644 cookbooks/iis/recipes/mod_logging.rb create mode 100644 cookbooks/iis/recipes/mod_management.rb create mode 100644 cookbooks/iis/recipes/mod_security.rb create mode 100644 cookbooks/iis/recipes/mod_tracing.rb create mode 100644 cookbooks/iis/recipes/remove_default_site.rb create mode 100644 cookbooks/iis/resources/app.rb create mode 100644 cookbooks/iis/resources/config.rb create mode 100644 cookbooks/iis/resources/module.rb create mode 100644 cookbooks/iis/resources/pool.rb create mode 100644 cookbooks/iis/resources/section.rb create mode 100644 cookbooks/iis/resources/site.rb create mode 100644 cookbooks/iis/resources/vdir.rb create mode 100644 cookbooks/mariadb/CHANGELOG.md create mode 100644 cookbooks/mariadb/README.md create mode 100644 cookbooks/mariadb/attributes/default.rb create mode 100644 cookbooks/mariadb/libraries/mariadb_helper.rb create mode 100644 cookbooks/mariadb/libraries/matchers.rb create mode 100644 cookbooks/mariadb/metadata.json create mode 100644 cookbooks/mariadb/providers/configuration.rb create mode 100644 cookbooks/mariadb/providers/replication.rb create mode 100644 cookbooks/mariadb/recipes/_audit_plugin.rb create mode 100644 cookbooks/mariadb/recipes/_debian_galera.rb create mode 100644 cookbooks/mariadb/recipes/_debian_server.rb create mode 100644 cookbooks/mariadb/recipes/_redhat_galera.rb create mode 100644 cookbooks/mariadb/recipes/_redhat_server.rb create mode 100644 cookbooks/mariadb/recipes/_redhat_server_native.rb create mode 100644 cookbooks/mariadb/recipes/client.rb create mode 100644 cookbooks/mariadb/recipes/config.rb create mode 100644 cookbooks/mariadb/recipes/default.rb create mode 100644 cookbooks/mariadb/recipes/galera.rb create mode 100644 cookbooks/mariadb/recipes/plugins.rb create mode 100644 cookbooks/mariadb/recipes/repository.rb create mode 100644 cookbooks/mariadb/recipes/server.rb create mode 100644 cookbooks/mariadb/resources/configuration.rb create mode 100644 cookbooks/mariadb/resources/replication.rb create mode 100644 cookbooks/mariadb/templates/default/conf.d.generic.erb create mode 100644 cookbooks/mariadb/templates/default/debian.cnf.erb create mode 100644 cookbooks/mariadb/templates/default/mariadb-server.seed.erb create mode 100644 cookbooks/mariadb/templates/default/mariadb_grants.erb create mode 100644 cookbooks/mariadb/templates/default/my.cnf.erb create mode 100644 cookbooks/mediawiki/.gitignore create mode 100644 cookbooks/mediawiki/.kitchen.yml create mode 100644 cookbooks/mediawiki/Berksfile create mode 100644 cookbooks/mediawiki/CHANGELOG.md create mode 100644 cookbooks/mediawiki/Gemfile create mode 100644 cookbooks/mediawiki/LICENSE create mode 100644 cookbooks/mediawiki/README.md create mode 100644 cookbooks/mediawiki/Thorfile create mode 100644 cookbooks/mediawiki/Vagrantfile create mode 100644 cookbooks/mediawiki/attributes/default.rb create mode 100644 cookbooks/mediawiki/chefignore create mode 100644 cookbooks/mediawiki/metadata.rb create mode 100644 cookbooks/mediawiki/recipes/default.rb create mode 100644 cookbooks/mediawiki/templates/default/web_app.conf.erb create mode 100644 cookbooks/mysql/CHANGELOG.md create mode 100644 cookbooks/mysql/README.md create mode 100644 cookbooks/mysql/libraries/helpers.rb create mode 100644 cookbooks/mysql/libraries/matchers.rb create mode 100644 cookbooks/mysql/libraries/provider_mysql_client.rb create mode 100644 cookbooks/mysql/libraries/provider_mysql_config.rb create mode 100644 cookbooks/mysql/libraries/provider_mysql_service.rb create mode 100644 cookbooks/mysql/libraries/provider_mysql_service_smf.rb create mode 100644 cookbooks/mysql/libraries/provider_mysql_service_systemd.rb create mode 100644 cookbooks/mysql/libraries/provider_mysql_service_sysvinit.rb create mode 100644 cookbooks/mysql/libraries/provider_mysql_service_upstart.rb create mode 100644 cookbooks/mysql/libraries/resource_mysql_client.rb create mode 100644 cookbooks/mysql/libraries/resource_mysql_config.rb create mode 100644 cookbooks/mysql/libraries/resource_mysql_service.rb create mode 100644 cookbooks/mysql/libraries/z_provider_mapping.rb create mode 100644 cookbooks/mysql/metadata.json create mode 100644 cookbooks/mysql/templates/default/apparmor/usr.sbin.mysqld-instance.erb create mode 100644 cookbooks/mysql/templates/default/apparmor/usr.sbin.mysqld-local.erb create mode 100644 cookbooks/mysql/templates/default/apparmor/usr.sbin.mysqld.erb create mode 100644 cookbooks/mysql/templates/default/my.cnf.erb create mode 100644 cookbooks/mysql/templates/default/smf/svc.method.mysqld.erb create mode 100644 cookbooks/mysql/templates/default/systemd/mysqld-wait-ready.erb create mode 100644 cookbooks/mysql/templates/default/systemd/mysqld.service.erb create mode 100644 cookbooks/mysql/templates/default/sysvinit/mysqld.erb create mode 100644 cookbooks/mysql/templates/default/tmpfiles.d.conf.erb create mode 100644 cookbooks/mysql/templates/default/upstart/mysqld-wait-ready.erb create mode 100644 cookbooks/mysql/templates/default/upstart/mysqld.erb create mode 100644 cookbooks/mysql2_chef_gem/CHANGELOG.md create mode 100644 cookbooks/mysql2_chef_gem/README.md create mode 100644 cookbooks/mysql2_chef_gem/libraries/matchers.rb create mode 100644 cookbooks/mysql2_chef_gem/libraries/provider_mysql2_chef_gem_mariadb.rb create mode 100644 cookbooks/mysql2_chef_gem/libraries/provider_mysql2_chef_gem_mysql.rb create mode 100644 cookbooks/mysql2_chef_gem/libraries/resource_mysql2_chef_gem.rb create mode 100644 cookbooks/mysql2_chef_gem/libraries/z_provider_mapping.rb create mode 100644 cookbooks/mysql2_chef_gem/metadata.json create mode 100644 cookbooks/nginx/CHANGELOG.md create mode 100644 cookbooks/nginx/README.md create mode 100644 cookbooks/nginx/attributes/auth_request.rb create mode 100644 cookbooks/nginx/attributes/default.rb create mode 100644 cookbooks/nginx/attributes/devel.rb create mode 100644 cookbooks/nginx/attributes/echo.rb create mode 100644 cookbooks/nginx/attributes/geoip.rb create mode 100644 cookbooks/nginx/attributes/headers_more.rb create mode 100644 cookbooks/nginx/attributes/lua.rb create mode 100644 cookbooks/nginx/attributes/naxsi.rb create mode 100644 cookbooks/nginx/attributes/openssl_source.rb create mode 100644 cookbooks/nginx/attributes/pagespeed.rb create mode 100644 cookbooks/nginx/attributes/passenger.rb create mode 100644 cookbooks/nginx/attributes/rate_limiting.rb create mode 100644 cookbooks/nginx/attributes/repo.rb create mode 100644 cookbooks/nginx/attributes/set_misc.rb create mode 100644 cookbooks/nginx/attributes/socketproxy.rb create mode 100644 cookbooks/nginx/attributes/source.rb create mode 100644 cookbooks/nginx/attributes/status.rb create mode 100644 cookbooks/nginx/attributes/syslog.rb create mode 100644 cookbooks/nginx/attributes/upload_progress.rb create mode 100644 cookbooks/nginx/definitions/nginx_site.rb create mode 100644 cookbooks/nginx/files/default/mime.types create mode 100644 cookbooks/nginx/files/default/naxsi_core.rules create mode 100644 cookbooks/nginx/libraries/matchers.rb create mode 100644 cookbooks/nginx/metadata.json create mode 100644 cookbooks/nginx/metadata.rb create mode 100644 cookbooks/nginx/recipes/authorized_ips.rb create mode 100644 cookbooks/nginx/recipes/commons.rb create mode 100644 cookbooks/nginx/recipes/commons_conf.rb create mode 100644 cookbooks/nginx/recipes/commons_dir.rb create mode 100644 cookbooks/nginx/recipes/commons_script.rb create mode 100644 cookbooks/nginx/recipes/default.rb create mode 100644 cookbooks/nginx/recipes/headers_more_module.rb create mode 100644 cookbooks/nginx/recipes/http_auth_request_module.rb create mode 100644 cookbooks/nginx/recipes/http_echo_module.rb create mode 100644 cookbooks/nginx/recipes/http_geoip_module.rb create mode 100644 cookbooks/nginx/recipes/http_gzip_static_module.rb create mode 100644 cookbooks/nginx/recipes/http_mp4_module.rb create mode 100644 cookbooks/nginx/recipes/http_perl_module.rb create mode 100644 cookbooks/nginx/recipes/http_realip_module.rb create mode 100644 cookbooks/nginx/recipes/http_spdy_module.rb create mode 100644 cookbooks/nginx/recipes/http_ssl_module.rb create mode 100644 cookbooks/nginx/recipes/http_stub_status_module.rb create mode 100644 cookbooks/nginx/recipes/ipv6.rb create mode 100644 cookbooks/nginx/recipes/lua.rb create mode 100644 cookbooks/nginx/recipes/naxsi_module.rb create mode 100644 cookbooks/nginx/recipes/ngx_devel_module.rb create mode 100644 cookbooks/nginx/recipes/ngx_lua_module.rb create mode 100644 cookbooks/nginx/recipes/ohai_plugin.rb create mode 100644 cookbooks/nginx/recipes/openssl_source.rb create mode 100644 cookbooks/nginx/recipes/package.rb create mode 100644 cookbooks/nginx/recipes/pagespeed_module.rb create mode 100644 cookbooks/nginx/recipes/passenger.rb create mode 100644 cookbooks/nginx/recipes/repo.rb create mode 100644 cookbooks/nginx/recipes/repo_passenger.rb create mode 100644 cookbooks/nginx/recipes/set_misc.rb create mode 100644 cookbooks/nginx/recipes/socketproxy.rb create mode 100644 cookbooks/nginx/recipes/source.rb create mode 100644 cookbooks/nginx/recipes/syslog_module.rb create mode 100644 cookbooks/nginx/recipes/upload_progress_module.rb create mode 100644 cookbooks/nginx/templates/debian/nginx.init.erb create mode 100644 cookbooks/nginx/templates/default/default-site.erb create mode 100644 cookbooks/nginx/templates/default/modules/authorized_ip.erb create mode 100644 cookbooks/nginx/templates/default/modules/http_geoip.conf.erb create mode 100644 cookbooks/nginx/templates/default/modules/http_gzip_static.conf.erb create mode 100644 cookbooks/nginx/templates/default/modules/http_realip.conf.erb create mode 100644 cookbooks/nginx/templates/default/modules/nginx_status.erb create mode 100644 cookbooks/nginx/templates/default/modules/passenger.conf.erb create mode 100644 cookbooks/nginx/templates/default/modules/socketproxy.conf.erb create mode 100644 cookbooks/nginx/templates/default/modules/upload_progress.erb create mode 100644 cookbooks/nginx/templates/default/nginx-upstart.conf.erb create mode 100644 cookbooks/nginx/templates/default/nginx.conf.erb create mode 100644 cookbooks/nginx/templates/default/nginx.init.erb create mode 100644 cookbooks/nginx/templates/default/nginx.pill.erb create mode 100644 cookbooks/nginx/templates/default/nginx.sysconfig.erb create mode 100644 cookbooks/nginx/templates/default/nxdissite.erb create mode 100644 cookbooks/nginx/templates/default/nxensite.erb create mode 100644 cookbooks/nginx/templates/default/plugins/nginx.rb.erb create mode 100644 cookbooks/nginx/templates/default/sv-nginx-log-run.erb create mode 100644 cookbooks/nginx/templates/default/sv-nginx-run.erb create mode 100644 cookbooks/nginx/templates/gentoo/nginx.init.erb create mode 100644 cookbooks/nginx/templates/suse/nginx.init.erb create mode 100644 cookbooks/nginx/templates/ubuntu/nginx.init.erb create mode 100644 cookbooks/nodejs/CHANGELOG.md create mode 100644 cookbooks/nodejs/README.md create mode 100644 cookbooks/nodejs/attributes/default.rb create mode 100644 cookbooks/nodejs/attributes/npm.rb create mode 100644 cookbooks/nodejs/attributes/packages.rb create mode 100644 cookbooks/nodejs/attributes/repo.rb create mode 100644 cookbooks/nodejs/libraries/matchers.rb create mode 100644 cookbooks/nodejs/libraries/nodejs_helper.rb create mode 100644 cookbooks/nodejs/metadata.json create mode 100644 cookbooks/nodejs/providers/npm.rb create mode 100644 cookbooks/nodejs/recipes/default.rb create mode 100644 cookbooks/nodejs/recipes/install.rb create mode 100644 cookbooks/nodejs/recipes/iojs.rb create mode 100644 cookbooks/nodejs/recipes/nodejs.rb create mode 100644 cookbooks/nodejs/recipes/nodejs_from_binary.rb create mode 100644 cookbooks/nodejs/recipes/nodejs_from_package.rb create mode 100644 cookbooks/nodejs/recipes/nodejs_from_source.rb create mode 100644 cookbooks/nodejs/recipes/npm.rb create mode 100644 cookbooks/nodejs/recipes/npm_from_source.rb create mode 100644 cookbooks/nodejs/recipes/npm_packages.rb create mode 100644 cookbooks/nodejs/recipes/repo.rb create mode 100644 cookbooks/nodejs/resources/npm.rb create mode 100644 cookbooks/ohai/CHANGELOG.md create mode 100644 cookbooks/ohai/README.md create mode 100644 cookbooks/ohai/attributes/default.rb create mode 100644 cookbooks/ohai/files/default/plugins/README create mode 100644 cookbooks/ohai/metadata.json create mode 100644 cookbooks/ohai/metadata.rb create mode 100644 cookbooks/ohai/providers/hint.rb create mode 100644 cookbooks/ohai/recipes/default.rb create mode 100644 cookbooks/ohai/resources/hint.rb create mode 100644 cookbooks/omnibus_updater/CHANGELOG.md create mode 100644 cookbooks/omnibus_updater/README.md create mode 100644 cookbooks/omnibus_updater/attributes/default.rb create mode 100644 cookbooks/omnibus_updater/libraries/omnibus_checker.rb create mode 100644 cookbooks/omnibus_updater/libraries/omnitrucker.rb create mode 100644 cookbooks/omnibus_updater/metadata.json create mode 100644 cookbooks/omnibus_updater/metadata.rb create mode 100644 cookbooks/omnibus_updater/recipes/default.rb create mode 100644 cookbooks/omnibus_updater/recipes/downloader.rb create mode 100644 cookbooks/omnibus_updater/recipes/installer.rb create mode 100644 cookbooks/omnibus_updater/recipes/old_package_cleaner.rb create mode 100644 cookbooks/omnibus_updater/recipes/remove_chef_system_gem.rb create mode 100644 cookbooks/openssl/CHANGELOG.md create mode 100644 cookbooks/openssl/README.md create mode 100644 cookbooks/openssl/attributes/default.rb create mode 100644 cookbooks/openssl/libraries/secure_password.rb create mode 100644 cookbooks/openssl/metadata.json create mode 100644 cookbooks/openssl/providers/x509.rb create mode 100644 cookbooks/openssl/recipes/default.rb create mode 100644 cookbooks/openssl/recipes/upgrade.rb create mode 100644 cookbooks/openssl/resources/x509.rb create mode 100644 cookbooks/packagecloud/.gitignore create mode 100644 cookbooks/packagecloud/.kitchen.yml create mode 100644 cookbooks/packagecloud/.rubocop.yml create mode 100644 cookbooks/packagecloud/.travis.yml create mode 100644 cookbooks/packagecloud/0001-chef-on-amazon-2014.patch create mode 100644 cookbooks/packagecloud/Berksfile create mode 100644 cookbooks/packagecloud/CHANGELOG.md create mode 100644 cookbooks/packagecloud/Gemfile create mode 100644 cookbooks/packagecloud/LICENSE create mode 100644 cookbooks/packagecloud/README.md create mode 100644 cookbooks/packagecloud/Rakefile create mode 100644 cookbooks/packagecloud/THANKS create mode 100644 cookbooks/packagecloud/Thorfile create mode 100644 cookbooks/packagecloud/Vagrantfile create mode 100644 cookbooks/packagecloud/attributes/default.rb create mode 100644 cookbooks/packagecloud/chefignore create mode 100644 cookbooks/packagecloud/libraries/helper.rb create mode 100644 cookbooks/packagecloud/libraries/matcher.rb create mode 100644 cookbooks/packagecloud/metadata.json create mode 100644 cookbooks/packagecloud/metadata.rb create mode 100644 cookbooks/packagecloud/providers/repo.rb create mode 100644 cookbooks/packagecloud/resources/repo.rb create mode 100644 cookbooks/packagecloud/templates/default/apt.erb create mode 100644 cookbooks/packagecloud/templates/default/yum.erb create mode 100644 cookbooks/partial_search/CHANGELOG.md create mode 100644 cookbooks/partial_search/README.md create mode 100644 cookbooks/partial_search/libraries/partial_search.rb create mode 100644 cookbooks/partial_search/metadata.json create mode 100644 cookbooks/partial_search/metadata.rb create mode 100644 cookbooks/partial_search/recipes/default.rb create mode 100644 cookbooks/php/CHANGELOG.md create mode 100644 cookbooks/php/README.md create mode 100644 cookbooks/php/attributes/default.rb create mode 100644 cookbooks/php/files/windows/go-pear.phar create mode 100644 cookbooks/php/libraries/helpers.rb create mode 100644 cookbooks/php/metadata.json create mode 100644 cookbooks/php/metadata.rb create mode 100644 cookbooks/php/providers/pear.rb create mode 100644 cookbooks/php/providers/pear_channel.rb create mode 100644 cookbooks/php/recipes/default.rb create mode 100644 cookbooks/php/recipes/ini.rb create mode 100644 cookbooks/php/recipes/module_apc.rb create mode 100644 cookbooks/php/recipes/module_curl.rb create mode 100644 cookbooks/php/recipes/module_fpdf.rb create mode 100644 cookbooks/php/recipes/module_gd.rb create mode 100644 cookbooks/php/recipes/module_ldap.rb create mode 100644 cookbooks/php/recipes/module_memcache.rb create mode 100644 cookbooks/php/recipes/module_mysql.rb create mode 100644 cookbooks/php/recipes/module_pgsql.rb create mode 100644 cookbooks/php/recipes/module_sqlite3.rb create mode 100644 cookbooks/php/recipes/package.rb create mode 100644 cookbooks/php/recipes/source.rb create mode 100644 cookbooks/php/resources/pear.rb create mode 100644 cookbooks/php/resources/pear_channel.rb create mode 100644 cookbooks/php/templates/centos/php.ini.erb create mode 100644 cookbooks/php/templates/debian/php.ini.erb create mode 100644 cookbooks/php/templates/default/extension.ini.erb create mode 100644 cookbooks/php/templates/default/php.ini.erb create mode 100644 cookbooks/php/templates/redhat/php.ini.erb create mode 100644 cookbooks/php/templates/ubuntu/php.ini.erb create mode 100644 cookbooks/php/templates/windows/pear-options.erb create mode 100644 cookbooks/php/templates/windows/php.ini.erb create mode 100644 cookbooks/poise/CHANGELOG.md create mode 100644 cookbooks/poise/README.md create mode 100644 cookbooks/poise/files/halite_gem/poise.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/error.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/chefspec_matchers.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/defined_in.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/fused.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/include_recipe.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/inversion.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/inversion/options_provider.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/inversion/options_resource.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/lazy_default.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/lwrp_polyfill.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/notifying_block.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/option_collector.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/resource_name.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/subcontext_block.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/subresources.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/subresources/child.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/subresources/container.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/subresources/default_containers.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/helpers/template_content.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/provider.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/resource.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/subcontext.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/subcontext/resource_collection.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/subcontext/runner.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/utils.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/utils/resource_provider_mixin.rb create mode 100644 cookbooks/poise/files/halite_gem/poise/version.rb create mode 100644 cookbooks/poise/libraries/default.rb create mode 100644 cookbooks/poise/metadata.json create mode 100644 cookbooks/postfix/CHANGELOG.md create mode 100644 cookbooks/postfix/README.md create mode 100644 cookbooks/postfix/attributes/default.rb create mode 100644 cookbooks/postfix/files/default/tests/minitest/support/helpers.rb create mode 100644 cookbooks/postfix/metadata.json create mode 100644 cookbooks/postfix/metadata.rb create mode 100644 cookbooks/postfix/recipes/_attributes.rb create mode 100644 cookbooks/postfix/recipes/_common.rb create mode 100644 cookbooks/postfix/recipes/access.rb create mode 100644 cookbooks/postfix/recipes/aliases.rb create mode 100644 cookbooks/postfix/recipes/client.rb create mode 100644 cookbooks/postfix/recipes/default.rb create mode 100644 cookbooks/postfix/recipes/sasl_auth.rb create mode 100644 cookbooks/postfix/recipes/server.rb create mode 100644 cookbooks/postfix/recipes/transports.rb create mode 100644 cookbooks/postfix/recipes/virtual_aliases.rb create mode 100644 cookbooks/postfix/recipes/virtual_aliases_domains.rb create mode 100644 cookbooks/postfix/templates/default/access.erb create mode 100644 cookbooks/postfix/templates/default/aliases.erb create mode 100644 cookbooks/postfix/templates/default/main.cf.erb create mode 100644 cookbooks/postfix/templates/default/manifest-postfix.xml.erb create mode 100644 cookbooks/postfix/templates/default/master.cf.erb create mode 100644 cookbooks/postfix/templates/default/port_smtp.erb create mode 100644 cookbooks/postfix/templates/default/sasl_passwd.erb create mode 100644 cookbooks/postfix/templates/default/sender_canonical.erb create mode 100644 cookbooks/postfix/templates/default/smtp_generic.erb create mode 100644 cookbooks/postfix/templates/default/transport.erb create mode 100644 cookbooks/postfix/templates/default/virtual_aliases.erb create mode 100644 cookbooks/postfix/templates/default/virtual_aliases_domains.erb create mode 100644 cookbooks/postgresql/CHANGELOG.md create mode 100644 cookbooks/postgresql/README.md create mode 100644 cookbooks/postgresql/attributes/default.rb create mode 100644 cookbooks/postgresql/files/default/tests/minitest/apt_pgdg_postgresql_test.rb create mode 100644 cookbooks/postgresql/files/default/tests/minitest/default_test.rb create mode 100644 cookbooks/postgresql/files/default/tests/minitest/ruby_test.rb create mode 100644 cookbooks/postgresql/files/default/tests/minitest/server_test.rb create mode 100644 cookbooks/postgresql/files/default/tests/minitest/support/helpers.rb create mode 100644 cookbooks/postgresql/libraries/default.rb create mode 100644 cookbooks/postgresql/metadata.json create mode 100644 cookbooks/postgresql/metadata.rb create mode 100644 cookbooks/postgresql/recipes/apt_pgdg_postgresql.rb create mode 100644 cookbooks/postgresql/recipes/client.rb create mode 100644 cookbooks/postgresql/recipes/config_initdb.rb create mode 100644 cookbooks/postgresql/recipes/config_pgtune.rb create mode 100644 cookbooks/postgresql/recipes/contrib.rb create mode 100644 cookbooks/postgresql/recipes/default.rb create mode 100644 cookbooks/postgresql/recipes/ruby.rb create mode 100644 cookbooks/postgresql/recipes/server.rb create mode 100644 cookbooks/postgresql/recipes/server_conf.rb create mode 100644 cookbooks/postgresql/recipes/server_debian.rb create mode 100644 cookbooks/postgresql/recipes/server_redhat.rb create mode 100644 cookbooks/postgresql/recipes/yum_pgdg_postgresql.rb create mode 100644 cookbooks/postgresql/templates/default/pg_hba.conf.erb create mode 100644 cookbooks/postgresql/templates/default/pgsql.sysconfig.erb create mode 100644 cookbooks/postgresql/templates/default/postgresql.conf.erb create mode 100644 cookbooks/rbac/README.md create mode 100644 cookbooks/rbac/libraries/rbac.rb create mode 100644 cookbooks/rbac/metadata.json create mode 100644 cookbooks/rbac/metadata.rb create mode 100644 cookbooks/rbac/providers/auth.rb create mode 100644 cookbooks/rbac/providers/default.rb create mode 100644 cookbooks/rbac/providers/user.rb create mode 100644 cookbooks/rbac/recipes/default.rb create mode 100644 cookbooks/rbac/resources/auth.rb create mode 100644 cookbooks/rbac/resources/default.rb create mode 100644 cookbooks/rbac/resources/user.rb create mode 100644 cookbooks/redis/.gitignore create mode 100644 cookbooks/redis/.kitchen.yml create mode 100644 cookbooks/redis/.rubocop.yml create mode 100644 cookbooks/redis/.ruby-gemset create mode 100644 cookbooks/redis/.ruby-version create mode 100644 cookbooks/redis/.travis.yml create mode 100644 cookbooks/redis/Berksfile create mode 100644 cookbooks/redis/Gemfile create mode 100644 cookbooks/redis/Guardfile create mode 100644 cookbooks/redis/LICENSE.txt create mode 100644 cookbooks/redis/README.md create mode 100644 cookbooks/redis/Rakefile create mode 100644 cookbooks/redis/attributes/default.rb create mode 100644 cookbooks/redis/config/license_finder.yml create mode 100644 cookbooks/redis/doc/license_finder/dependencies.csv create mode 100644 cookbooks/redis/doc/license_finder/dependencies.db create mode 100644 cookbooks/redis/doc/license_finder/dependencies.html create mode 100644 cookbooks/redis/doc/license_finder/dependencies.md create mode 100644 cookbooks/redis/doc/license_finder/dependencies_detailed.csv create mode 100644 cookbooks/redis/files/default/tests/minitest/client_test.rb create mode 100644 cookbooks/redis/files/default/tests/minitest/default_test.rb create mode 100644 cookbooks/redis/files/default/tests/minitest/server_test.rb create mode 100644 cookbooks/redis/files/default/tests/minitest/test_helper.rb create mode 100644 cookbooks/redis/metadata.rb create mode 100644 cookbooks/redis/recipes/client.rb create mode 100644 cookbooks/redis/recipes/default.rb create mode 100644 cookbooks/redis/recipes/server.rb create mode 100644 cookbooks/redis/spec/client_spec.rb create mode 100644 cookbooks/redis/spec/default_spec.rb create mode 100644 cookbooks/redis/spec/server_spec.rb create mode 100644 cookbooks/redis/spec/spec_helper.rb create mode 100644 cookbooks/redis/templates/default/default_redis-server.erb create mode 100644 cookbooks/redis/templates/default/redis.conf.erb create mode 100644 cookbooks/redis/test/.chef/knife.rb create mode 100644 cookbooks/redis/test/integration/default/serverspec/default_spec.rb create mode 100644 cookbooks/redis/test/support/keys/README.md create mode 100644 cookbooks/redis/test/support/keys/vagrant create mode 100644 cookbooks/redis/test/support/keys/vagrant.pub create mode 100644 cookbooks/redis/test/support/rubocop/disabled.yml create mode 100644 cookbooks/redis/test/support/rubocop/enabled.yml create mode 100644 cookbooks/rsyslog/.gitignore create mode 100644 cookbooks/rsyslog/.kitchen.busted.yml create mode 100644 cookbooks/rsyslog/.kitchen.cloud.yml create mode 100644 cookbooks/rsyslog/.kitchen.yml create mode 100644 cookbooks/rsyslog/.rubocop.yml create mode 100644 cookbooks/rsyslog/.travis.yml create mode 100644 cookbooks/rsyslog/Berksfile create mode 100644 cookbooks/rsyslog/CHANGELOG.md create mode 100644 cookbooks/rsyslog/CONTRIBUTING.md create mode 100644 cookbooks/rsyslog/Gemfile create mode 100644 cookbooks/rsyslog/Guardfile create mode 100644 cookbooks/rsyslog/LICENSE create mode 100644 cookbooks/rsyslog/README.md create mode 100644 cookbooks/rsyslog/Rakefile create mode 100644 cookbooks/rsyslog/TESTING.md create mode 100644 cookbooks/rsyslog/attributes/default.rb create mode 100644 cookbooks/rsyslog/chefignore create mode 100644 cookbooks/rsyslog/libraries/helpers.rb create mode 100644 cookbooks/rsyslog/metadata.json create mode 100644 cookbooks/rsyslog/metadata.rb create mode 100644 cookbooks/rsyslog/providers/file_input.rb create mode 100644 cookbooks/rsyslog/recipes/client.rb create mode 100644 cookbooks/rsyslog/recipes/default.rb create mode 100644 cookbooks/rsyslog/recipes/server.rb create mode 100644 cookbooks/rsyslog/resources/file_input.rb create mode 100644 cookbooks/rsyslog/templates/default/35-server-per-host.conf.erb create mode 100644 cookbooks/rsyslog/templates/default/49-relp.conf.erb create mode 100644 cookbooks/rsyslog/templates/default/49-remote.conf.erb create mode 100644 cookbooks/rsyslog/templates/default/50-default.conf.erb create mode 100644 cookbooks/rsyslog/templates/default/file-input.conf.erb create mode 100644 cookbooks/rsyslog/templates/default/omnios-manifest.xml.erb create mode 100644 cookbooks/rsyslog/templates/default/rsyslog.conf.erb create mode 100644 cookbooks/rsyslog/templates/smartos/50-default.conf.erb create mode 100644 cookbooks/runit/CHANGELOG.md create mode 100644 cookbooks/runit/README.md create mode 100644 cookbooks/runit/attributes/default.rb create mode 100644 cookbooks/runit/files/default/runit.seed create mode 100644 cookbooks/runit/files/default/runsvdir create mode 100644 cookbooks/runit/files/ubuntu-6.10/runsvdir create mode 100644 cookbooks/runit/files/ubuntu-7.04/runsvdir create mode 100644 cookbooks/runit/files/ubuntu-7.10/runsvdir create mode 100644 cookbooks/runit/files/ubuntu-8.04/runsvdir create mode 100644 cookbooks/runit/libraries/default.rb create mode 100644 cookbooks/runit/libraries/helpers.rb create mode 100644 cookbooks/runit/libraries/matchers.rb create mode 100644 cookbooks/runit/libraries/provider_runit_service.rb create mode 100644 cookbooks/runit/libraries/resource_runit_service.rb create mode 100644 cookbooks/runit/metadata.json create mode 100644 cookbooks/runit/recipes/default.rb create mode 100644 cookbooks/runit/templates/debian/init.d.erb create mode 100644 cookbooks/runit/templates/default/log-config.erb create mode 100644 cookbooks/runit/templates/gentoo/runit-start.sh.erb create mode 100644 cookbooks/smf/README.md create mode 100644 cookbooks/smf/libraries/helper.rb create mode 100644 cookbooks/smf/libraries/matchers.rb create mode 100644 cookbooks/smf/libraries/rbac_helper.rb create mode 100644 cookbooks/smf/libraries/xml_builder.rb create mode 100644 cookbooks/smf/metadata.json create mode 100644 cookbooks/smf/metadata.rb create mode 100644 cookbooks/smf/providers/default.rb create mode 100644 cookbooks/smf/recipes/SMFServicesOK.rb create mode 100644 cookbooks/smf/recipes/default.rb create mode 100644 cookbooks/smf/resources/default.rb create mode 100644 cookbooks/smf/templates/default/SMFServicesOK.sh.erb create mode 100644 cookbooks/smf/templates/default/SMFServicesOK.snmpd.conf.erb create mode 100644 cookbooks/ssh_known_hosts/CHANGELOG.md create mode 100644 cookbooks/ssh_known_hosts/README.md create mode 100644 cookbooks/ssh_known_hosts/attributes/default.rb create mode 100644 cookbooks/ssh_known_hosts/libraries/matchers.rb create mode 100644 cookbooks/ssh_known_hosts/metadata.json create mode 100644 cookbooks/ssh_known_hosts/providers/entry.rb create mode 100644 cookbooks/ssh_known_hosts/recipes/cacher.rb create mode 100644 cookbooks/ssh_known_hosts/recipes/default.rb create mode 100644 cookbooks/ssh_known_hosts/resources/entry.rb create mode 100644 cookbooks/sudo/CHANGELOG.md create mode 100644 cookbooks/sudo/README.md create mode 100644 cookbooks/sudo/attributes/default.rb create mode 100644 cookbooks/sudo/files/default/README create mode 100644 cookbooks/sudo/libraries/matchers.rb create mode 100644 cookbooks/sudo/metadata.json create mode 100644 cookbooks/sudo/metadata.rb create mode 100644 cookbooks/sudo/providers/default.rb create mode 100644 cookbooks/sudo/recipes/default.rb create mode 100644 cookbooks/sudo/resources/default.rb create mode 100644 cookbooks/sudo/templates/default/sudoer.erb create mode 100644 cookbooks/sudo/templates/default/sudoers.erb create mode 100644 cookbooks/sudo/templates/mac_os_x/sudoers.erb create mode 100644 cookbooks/ufw/CHANGELOG.md create mode 100644 cookbooks/ufw/README.md create mode 100644 cookbooks/ufw/attributes/default.rb create mode 100644 cookbooks/ufw/metadata.json create mode 100644 cookbooks/ufw/metadata.rb create mode 100644 cookbooks/ufw/recipes/databag.rb create mode 100644 cookbooks/ufw/recipes/default.rb create mode 100644 cookbooks/ufw/recipes/disable.rb create mode 100644 cookbooks/ufw/recipes/recipes.rb create mode 100644 cookbooks/ufw/recipes/securitylevel.rb create mode 100644 cookbooks/unattended-upgrades/CHANGELOG.md create mode 100644 cookbooks/unattended-upgrades/README.md create mode 100644 cookbooks/unattended-upgrades/attributes/default.rb create mode 100644 cookbooks/unattended-upgrades/files/default/test/default_test.rb create mode 100644 cookbooks/unattended-upgrades/files/default/test/support/helpers.rb create mode 100644 cookbooks/unattended-upgrades/metadata.json create mode 100644 cookbooks/unattended-upgrades/metadata.rb create mode 100644 cookbooks/unattended-upgrades/recipes/default.rb create mode 100644 cookbooks/unattended-upgrades/templates/default/auto-upgrades.conf.erb create mode 100644 cookbooks/unattended-upgrades/templates/default/unattended-upgrades.conf.erb create mode 100644 cookbooks/users/CHANGELOG.md create mode 100644 cookbooks/users/README.md create mode 100644 cookbooks/users/libraries/helpers.rb create mode 100644 cookbooks/users/libraries/matchers.rb create mode 100644 cookbooks/users/metadata.json create mode 100644 cookbooks/users/providers/manage.rb create mode 100644 cookbooks/users/recipes/default.rb create mode 100644 cookbooks/users/recipes/sysadmins.rb create mode 100644 cookbooks/users/resources/manage.rb create mode 100644 cookbooks/users/templates/default/authorized_keys.erb create mode 100644 cookbooks/users/templates/default/private_key.erb create mode 100644 cookbooks/users/templates/default/public_key.pub.erb create mode 100644 cookbooks/windows/CHANGELOG.md create mode 100644 cookbooks/windows/README.md create mode 100644 cookbooks/windows/attributes/default.rb create mode 100644 cookbooks/windows/files/default/handlers/windows_reboot_handler.rb create mode 100644 cookbooks/windows/libraries/feature_base.rb create mode 100644 cookbooks/windows/libraries/matchers.rb create mode 100644 cookbooks/windows/libraries/powershell_helper.rb create mode 100644 cookbooks/windows/libraries/powershell_out.rb create mode 100644 cookbooks/windows/libraries/registry_helper.rb create mode 100644 cookbooks/windows/libraries/version.rb create mode 100644 cookbooks/windows/libraries/windows_architecture_helper.rb create mode 100644 cookbooks/windows/libraries/windows_helper.rb create mode 100644 cookbooks/windows/libraries/windows_package.rb create mode 100644 cookbooks/windows/libraries/windows_privileged.rb create mode 100644 cookbooks/windows/libraries/wmi_helper.rb create mode 100644 cookbooks/windows/metadata.json create mode 100644 cookbooks/windows/providers/auto_run.rb create mode 100644 cookbooks/windows/providers/batch.rb create mode 100644 cookbooks/windows/providers/feature_dism.rb create mode 100644 cookbooks/windows/providers/feature_powershell.rb create mode 100644 cookbooks/windows/providers/feature_servermanagercmd.rb create mode 100644 cookbooks/windows/providers/font.rb create mode 100644 cookbooks/windows/providers/pagefile.rb create mode 100644 cookbooks/windows/providers/path.rb create mode 100644 cookbooks/windows/providers/printer.rb create mode 100644 cookbooks/windows/providers/printer_port.rb create mode 100644 cookbooks/windows/providers/reboot.rb create mode 100644 cookbooks/windows/providers/registry.rb create mode 100644 cookbooks/windows/providers/shortcut.rb create mode 100644 cookbooks/windows/providers/task.rb create mode 100644 cookbooks/windows/providers/zipfile.rb create mode 100644 cookbooks/windows/recipes/default.rb create mode 100644 cookbooks/windows/recipes/reboot_handler.rb create mode 100644 cookbooks/windows/resources/auto_run.rb create mode 100644 cookbooks/windows/resources/batch.rb create mode 100644 cookbooks/windows/resources/feature.rb create mode 100644 cookbooks/windows/resources/font.rb create mode 100644 cookbooks/windows/resources/pagefile.rb create mode 100644 cookbooks/windows/resources/path.rb create mode 100644 cookbooks/windows/resources/printer.rb create mode 100644 cookbooks/windows/resources/printer_port.rb create mode 100644 cookbooks/windows/resources/reboot.rb create mode 100644 cookbooks/windows/resources/registry.rb create mode 100644 cookbooks/windows/resources/shortcut.rb create mode 100644 cookbooks/windows/resources/task.rb create mode 100644 cookbooks/windows/resources/zipfile.rb create mode 100644 cookbooks/xml/CHANGELOG.md create mode 100644 cookbooks/xml/README.md create mode 100644 cookbooks/xml/attributes/default.rb create mode 100644 cookbooks/xml/metadata.json create mode 100644 cookbooks/xml/recipes/default.rb create mode 100644 cookbooks/xml/recipes/ruby.rb create mode 100644 cookbooks/yum-epel/CHANGELOG.md create mode 100644 cookbooks/yum-epel/README.md create mode 100644 cookbooks/yum-epel/attributes/default.rb create mode 100644 cookbooks/yum-epel/attributes/epel-debuginfo.rb create mode 100644 cookbooks/yum-epel/attributes/epel-source.rb create mode 100644 cookbooks/yum-epel/attributes/epel-testing-debuginfo.rb create mode 100644 cookbooks/yum-epel/attributes/epel-testing-source.rb create mode 100644 cookbooks/yum-epel/attributes/epel-testing.rb create mode 100644 cookbooks/yum-epel/attributes/epel.rb create mode 100644 cookbooks/yum-epel/metadata.json create mode 100644 cookbooks/yum-epel/recipes/default.rb create mode 100644 cookbooks/yum-mysql-community/CHANGELOG.md create mode 100644 cookbooks/yum-mysql-community/README.md create mode 100644 cookbooks/yum-mysql-community/attributes/mysql-connectors-community.rb create mode 100644 cookbooks/yum-mysql-community/attributes/mysql55-community.rb create mode 100644 cookbooks/yum-mysql-community/attributes/mysql56-community.rb create mode 100644 cookbooks/yum-mysql-community/attributes/mysql57-community.rb create mode 100644 cookbooks/yum-mysql-community/files/default/mysql_pubkey.asc create mode 100644 cookbooks/yum-mysql-community/metadata.json create mode 100644 cookbooks/yum-mysql-community/recipes/connectors.rb create mode 100644 cookbooks/yum-mysql-community/recipes/mysql55.rb create mode 100644 cookbooks/yum-mysql-community/recipes/mysql56.rb create mode 100644 cookbooks/yum-mysql-community/recipes/mysql57.rb create mode 100644 cookbooks/yum/CHANGELOG.md create mode 100644 cookbooks/yum/README.md create mode 100644 cookbooks/yum/attributes/main.rb create mode 100644 cookbooks/yum/libraries/matchers.rb create mode 100644 cookbooks/yum/metadata.json create mode 100644 cookbooks/yum/providers/globalconfig.rb create mode 100644 cookbooks/yum/providers/repository.rb create mode 100644 cookbooks/yum/recipes/default.rb create mode 100644 cookbooks/yum/resources/globalconfig.rb create mode 100644 cookbooks/yum/resources/repository.rb create mode 100644 cookbooks/yum/templates/default/main.erb create mode 100644 cookbooks/yum/templates/default/repo.erb create mode 100644 data_bags/.gitkeep create mode 100644 data_bags/certificates/wildcard_kosmos_org.json create mode 100644 data_bags/credentials/hal8000_freenode.json create mode 100644 data_bags/credentials/schlupp_5apps.json create mode 100644 data_bags/credentials/smtp.json create mode 100644 data_bags/users/kare.json create mode 100644 doc/encrypted_data_bags.md create mode 100644 environments/.gitkeep create mode 100644 nodes/dev.kosmos.org.json create mode 100644 roles/.gitkeep create mode 100644 site-cookbooks/kosmos-base/README.md create mode 100644 site-cookbooks/kosmos-base/metadata.rb create mode 100644 site-cookbooks/kosmos-base/recipes/default.rb create mode 100644 site-cookbooks/kosmos-base/recipes/firewall.rb create mode 100644 site-cookbooks/kosmos-hubot/README.md create mode 100644 site-cookbooks/kosmos-hubot/metadata.rb create mode 100644 site-cookbooks/kosmos-hubot/recipes/default.rb create mode 100644 site-cookbooks/kosmos-hubot/templates/default/nodejs.systemd.service.erb create mode 100644 site-cookbooks/kosmos-nginx/README.md create mode 100644 site-cookbooks/kosmos-nginx/metadata.rb create mode 100644 site-cookbooks/kosmos-nginx/recipes/default.rb create mode 100644 site-cookbooks/kosmos-nodejs/README.md create mode 100644 site-cookbooks/kosmos-nodejs/metadata.rb create mode 100644 site-cookbooks/kosmos-nodejs/recipes/default.rb create mode 100644 site-cookbooks/kosmos-postfix/README.md create mode 100644 site-cookbooks/kosmos-postfix/metadata.rb create mode 100644 site-cookbooks/kosmos-postfix/recipes/default.rb create mode 100644 site-cookbooks/kosmos-redis/README.md create mode 100644 site-cookbooks/kosmos-redis/metadata.rb create mode 100644 site-cookbooks/kosmos-redis/recipes/default.rb create mode 100644 site-cookbooks/sockethub/CHANGELOG.md create mode 100644 site-cookbooks/sockethub/README.md create mode 100644 site-cookbooks/sockethub/attributes/default.rb create mode 100644 site-cookbooks/sockethub/metadata.rb create mode 100644 site-cookbooks/sockethub/recipes/default.rb create mode 100644 site-cookbooks/sockethub/recipes/proxy.rb create mode 100644 site-cookbooks/sockethub/templates/default/nginx_conf_sockethub.erb create mode 100644 site-cookbooks/sockethub/templates/default/nodejs.systemd.service.erb diff --git a/.chef/knife.rb b/.chef/knife.rb new file mode 100644 index 0000000..0691c68 --- /dev/null +++ b/.chef/knife.rb @@ -0,0 +1,8 @@ +current_dir = File.dirname(__FILE__) + +cookbook_path ["#{current_dir}/../cookbooks", "#{current_dir}/../site-cookbooks"] +node_path "nodes" +role_path "roles" +environment_path "environments" +data_bag_path "data_bags" +encrypted_data_bag_secret "#{current_dir}/encrypted_data_bag_secret" diff --git a/Batali b/Batali new file mode 100644 index 0000000..3524397 --- /dev/null +++ b/Batali @@ -0,0 +1,29 @@ +Batali.define do + source 'https://supermarket.chef.io' + + cookbook 'mediawiki', + git: 'https://github.com/67P/mediawiki-cookbook.git', + ref: 'master' + cookbook 'postfix' + cookbook 'unattended-upgrades' + cookbook 'application_nodejs', + git: 'https://github.com/67p/application_nodejs.git', + ref: 'master' + cookbook 'users' + cookbook 'chef-solo-search' + cookbook 'sudo' + cookbook 'hostname' + cookbook 'redis', + git: 'https://github.com/phlipper/chef-redis.git', + ref: 'v0.5.6' + cookbook 'ufw' + cookbook 'ssh_known_hosts' + cookbook 'nginx' + cookbook 'build-essential' + cookbook 'mysql' + cookbook 'database' + cookbook 'mysql2_chef_gem' + cookbook 'omnibus_updater', '~> 1.0.4' +end + +# vim: set filetype=ruby diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7a24fcf --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +gem 'chef', '~> 12.4.1' +gem 'batali', '~> 0.2.10' +gem 'knife-solo' +gem 'knife-solo_data_bag' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..38d3dfd --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,189 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.3.8) + attribute_struct (0.2.16) + batali (0.2.10) + attribute_struct (~> 0.2.14) + bogo (~> 0.1.20) + bogo-cli (~> 0.1.18) + bogo-config (~> 0.1.10) + bogo-ui (~> 0.1.6) + git + grimoire (~> 0.2.3) + http (~> 0.8.2) + rack-cache + bogo (0.1.26) + hashie + multi_json + bogo-cli (0.1.20) + bogo (>= 0.1.6) + bogo-config + bogo-ui + slop (~> 3) + bogo-config (0.1.12) + attribute_struct + bogo (>= 0.1.4, < 1.0) + multi_json + multi_xml + bogo-ui (0.1.10) + bogo + command_line_reporter + paint + builder (3.2.2) + chef (12.4.1) + chef-config (= 12.4.1) + chef-zero (~> 4.2, >= 4.2.2) + diff-lcs (~> 1.2, >= 1.2.4) + erubis (~> 2.7) + ffi-yajl (~> 2.2) + highline (~> 1.6, >= 1.6.9) + mixlib-authentication (~> 1.3) + mixlib-cli (~> 1.4) + mixlib-log (~> 1.3) + mixlib-shellout (>= 2.0.0.rc.0, < 3.0) + net-ssh (~> 2.6) + net-ssh-multi (~> 1.1) + ohai (~> 8.0) + plist (~> 3.1.0) + pry (~> 0.9) + rspec-core (~> 3.2) + rspec-expectations (~> 3.2) + rspec-mocks (~> 3.2) + rspec_junit_formatter (~> 0.2.0) + serverspec (~> 2.7) + specinfra (~> 2.10) + syslog-logger (~> 1.6) + chef-config (12.4.1) + mixlib-config (~> 2.0) + mixlib-shellout (~> 2.0) + chef-zero (4.2.3) + ffi-yajl (>= 1.1, < 3.0) + hashie (~> 2.0) + mixlib-log (~> 1.3) + rack + uuidtools (~> 2.1) + coderay (1.1.0) + colored (1.2) + command_line_reporter (3.3.5) + colored (>= 1.2) + diff-lcs (1.2.5) + domain_name (0.5.24) + unf (>= 0.0.5, < 1.0.0) + erubis (2.7.0) + ffi (1.9.10) + ffi-yajl (2.2.2) + libyajl2 (~> 1.2) + git (1.2.9.1) + grimoire (0.2.6) + attribute_struct (>= 0.1.12) + bogo (~> 0.1.10) + hashie (2.1.2) + highline (1.7.2) + http (0.8.12) + addressable (~> 2.3) + http-cookie (~> 1.0) + http-form_data (~> 1.0.1) + http_parser.rb (~> 0.6.0) + http-cookie (1.0.2) + domain_name (~> 0.5) + http-form_data (1.0.1) + http_parser.rb (0.6.0) + ipaddress (0.8.0) + knife-solo (0.4.2) + chef (>= 10.12) + erubis (~> 2.7.0) + net-ssh (>= 2.2.2, < 3.0) + knife-solo_data_bag (1.1.0) + libyajl2 (1.2.0) + method_source (0.8.2) + mime-types (2.6.1) + mixlib-authentication (1.3.0) + mixlib-log + mixlib-cli (1.5.0) + mixlib-config (2.2.1) + mixlib-log (1.6.0) + mixlib-shellout (2.1.0) + multi_json (1.11.2) + multi_xml (0.5.5) + net-scp (1.2.1) + net-ssh (>= 2.6.5) + net-ssh (2.9.2) + net-ssh-gateway (1.2.0) + net-ssh (>= 2.6.5) + net-ssh-multi (1.2.1) + net-ssh (>= 2.6.5) + net-ssh-gateway (>= 1.2.0) + net-telnet (0.1.1) + ohai (8.5.0) + ffi (~> 1.9) + ffi-yajl (~> 2.2) + ipaddress + mime-types (~> 2.0) + mixlib-cli + mixlib-config (~> 2.0) + mixlib-log + mixlib-shellout (~> 2.0) + rake (~> 10.1) + systemu (~> 2.6.4) + wmi-lite (~> 1.0) + paint (1.0.0) + plist (3.1.0) + pry (0.10.1) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + rack (1.6.4) + rack-cache (1.2) + rack (>= 0.4) + rake (10.4.2) + rspec (3.3.0) + rspec-core (~> 3.3.0) + rspec-expectations (~> 3.3.0) + rspec-mocks (~> 3.3.0) + rspec-core (3.3.2) + rspec-support (~> 3.3.0) + rspec-expectations (3.3.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.3.0) + rspec-its (1.2.0) + rspec-core (>= 3.0.0) + rspec-expectations (>= 3.0.0) + rspec-mocks (3.3.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.3.0) + rspec-support (3.3.0) + rspec_junit_formatter (0.2.3) + builder (< 4) + rspec-core (>= 2, < 4, != 2.12.0) + serverspec (2.19.0) + multi_json + rspec (~> 3.0) + rspec-its + specinfra (~> 2.35) + sfl (2.2) + slop (3.6.0) + specinfra (2.37.8) + net-scp + net-ssh (~> 2.7) + net-telnet + sfl + syslog-logger (1.6.8) + systemu (2.6.5) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.1) + uuidtools (2.1.5) + wmi-lite (1.0.0) + +PLATFORMS + ruby + +DEPENDENCIES + batali (~> 0.2.10) + chef (~> 12.4.1) + knife-solo + knife-solo_data_bag + +BUNDLED WITH + 1.10.5 diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5b11e6 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ + + +## Bootstrap + +``` +knife solo prepare root@dev.kosmos.org +``` + + +## Run Chef + +``` +knife solo cook dev.kosmos.org +``` diff --git a/cookbooks/7-zip/CHANGELOG.md b/cookbooks/7-zip/CHANGELOG.md new file mode 100644 index 0000000..183bcf7 --- /dev/null +++ b/cookbooks/7-zip/CHANGELOG.md @@ -0,0 +1,13 @@ +7-zip Cookbook CHANGELOG +======================== +This file is used to list changes made in each version of the 7-zip cookbook. + + +v1.0.2 +------ +### Improvement +- **[COOK-3476](https://tickets.opscode.com/browse/COOK-3476)** - Upgrade to 7-zip 9.22 + +1.0.0 +----- +- initial release diff --git a/cookbooks/7-zip/README.md b/cookbooks/7-zip/README.md new file mode 100644 index 0000000..4bfd6be --- /dev/null +++ b/cookbooks/7-zip/README.md @@ -0,0 +1,50 @@ +7-zip Cookbook +============== +[7-Zip](http://www.7-zip.org/) is a file archiver with a high compression ratio. This cookbook installs the full 7-zip suite of tools (GUI and CLI). + + +Requirements +------------ +### Platform +- Windows XP +- Windows Vista +- Windows Server 2003 R2 +- Windows 7 +- Windows Server 2008 (R1, R2) +- Windows 8 +- Windows Server 2012 + +### Cookbooks +- windows + + +Attributes +---------- +- `node['7-zip']['home']` - location to install 7-zip files to. default is `%SYSTEMDRIVE%\7-zip` + + +Usage +----- +### default +Downloads and installs 7-zip to the location specified by `node['7-zip']['home']`. Also ensures `node['7-zip']['home']` is in the system path. + + +License & Authors +----------------- +- Author:: Seth Chisamore () + +```text +Copyright:: 2011, Opscode, Inc. + +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/cookbooks/7-zip/attributes/default.rb b/cookbooks/7-zip/attributes/default.rb new file mode 100644 index 0000000..adc1903 --- /dev/null +++ b/cookbooks/7-zip/attributes/default.rb @@ -0,0 +1,31 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: 7-zip +# Attribute:: default +# +# Copyright:: Copyright (c) 2011 Opscode, Inc. +# +# 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. +# + +if kernel['machine'] =~ /x86_64/ + default['7-zip']['url'] = "http://downloads.sourceforge.net/sevenzip/7z922-x64.msi" + default['7-zip']['checksum'] = "f09bf515289eea45185a4cc673e3bbc18ce608c55b4cf96e77833435c9cdf3dc" + default['7-zip']['package_name'] = "7-Zip 9.22 (x64 edition)" +else + default['7-zip']['url'] = "http://downloads.sourceforge.net/sevenzip/7z922.msi" + default['7-zip']['checksum'] = "86df264d22c3dd3ab80cb55a118da2d41bdd95c2db2cd09a6bbdf48f069e3d7a" + default['7-zip']['package_name'] = "7-Zip 9.22" +end + +default['7-zip']['home'] = "#{ENV['SYSTEMDRIVE']}\\7-zip" diff --git a/cookbooks/7-zip/metadata.json b/cookbooks/7-zip/metadata.json new file mode 100644 index 0000000..98ab32d --- /dev/null +++ b/cookbooks/7-zip/metadata.json @@ -0,0 +1,31 @@ +{ + "name": "7-zip", + "version": "1.0.2", + "description": "Installs/Configures the 7-zip file archiver", + "long_description": "7-zip Cookbook\n==============\n[7-Zip](http://www.7-zip.org/) is a file archiver with a high compression ratio. This cookbook installs the full 7-zip suite of tools (GUI and CLI).\n\n\nRequirements\n------------\n### Platform\n- Windows XP\n- Windows Vista\n- Windows Server 2003 R2\n- Windows 7\n- Windows Server 2008 (R1, R2)\n- Windows 8\n- Windows Server 2012\n\n### Cookbooks\n- windows\n\n\nAttributes\n----------\n- `node['7-zip']['home']` - location to install 7-zip files to. default is `%SYSTEMDRIVE%\\7-zip`\n\n\nUsage\n-----\n### default\nDownloads and installs 7-zip to the location specified by `node['7-zip']['home']`. Also ensures `node['7-zip']['home']` is in the system path.\n\n\nLicense & Authors\n-----------------\n- Author:: Seth Chisamore ()\n\n```text\nCopyright:: 2011, Opscode, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "maintainer": "Opscode, Inc.", + "maintainer_email": "cookbooks@opscode.com", + "license": "Apache 2.0", + "platforms": { + "windows": ">= 0.0.0" + }, + "dependencies": { + "windows": ">= 1.2.2" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + } +} \ No newline at end of file diff --git a/cookbooks/7-zip/metadata.rb b/cookbooks/7-zip/metadata.rb new file mode 100644 index 0000000..fa83e9f --- /dev/null +++ b/cookbooks/7-zip/metadata.rb @@ -0,0 +1,10 @@ +name "7-zip" +maintainer "Opscode, Inc." +maintainer_email "cookbooks@opscode.com" +license "Apache 2.0" +description "Installs/Configures the 7-zip file archiver" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version "1.0.2" +supports "windows" + +depends "windows", ">= 1.2.2" diff --git a/cookbooks/7-zip/recipes/default.rb b/cookbooks/7-zip/recipes/default.rb new file mode 100644 index 0000000..155cbb6 --- /dev/null +++ b/cookbooks/7-zip/recipes/default.rb @@ -0,0 +1,31 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: 7-zip +# Recipe:: default +# +# Copyright 2011, Opscode, Inc. +# +# 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. +# + +windows_package node['7-zip']['package_name'] do + source node['7-zip']['url'] + checksum node['7-zip']['checksum'] + options "INSTALLDIR=\"#{node['7-zip']['home']}\"" + action :install +end + +# update path +windows_path node['7-zip']['home'] do + action :add +end diff --git a/cookbooks/apache2/CHANGELOG.md b/cookbooks/apache2/CHANGELOG.md new file mode 100644 index 0000000..e89bc99 --- /dev/null +++ b/cookbooks/apache2/CHANGELOG.md @@ -0,0 +1,414 @@ +apache2 Cookbook Changelog +========================== +This file is used to list changes made in each version of the apache2 cookbook. + +v3.1.0 (2015-05-25) +------------------- + +- [GH-315] Fix `apache.default_site_name` .conf extension references to ensure deletion +- [GH-258] Use `apache.default_site_name` for consistency, minimize hardcoding of filenames +- [GH-259] Add `&& sleep 1` to end of apache restart command on rhel-based systems using apache2.2 +- [GH-271] Remove FreeBSD 9.x, Red Hat and CentOS 5.x and OpenSUSE 11.x Series from tests and focus on newer releases +- [GH-276] Add psych gem to development gems +- [GH-293] Add `apache.mod_fastcgi.install_method` flag to allow install of mod_fastcgi from source (even on Debian family) +- [GH-285] Made `apache.devel_package` configurable based on platform, including support for Amazon Linux. +- [GH-316] Update Opscode references to Chef +- [GH-318] Apply default recipe in all definitions +- [GH-320] Add attribute to adjust `apache.default_site_port` +- [GH-321] Fix issue with default_site name in not_if guards +- [GH-322] Add `apache.mod_ssl.pkg_name` to allow custom mod_ssl package names. Set defaults for supported platforms including Amazon Linux +- [GH-323] Don't create the default site configuration file in `sites-available` unless it is enabled. +- [GH-324] Add `apache.mod_ssl.port` to set the default ssl port to something other than 443 +- [GH-328] Add the ability to pass in a pipe as to log +- [GH-332] `SSLStrictSNIVHostCheck` is only written to config if enabled to avoid breaking apache prior to 2.2.12. +- [GH-334] Removed `iptables`, `god-monitor`, and `logrotate` recipes to avoid having external dependencies. These services should be managed in a wrapper cookbook going forward. +- [GH-339] Allow custom names for php so_filename (`node['apache']['mod_php5']['so_filename']`) + + +v3.0.1 (2015-02-11) +------------------- + +- [GH-310] Ubuntu Apache 2.2 requires the lock_dir to be owned by www-data +- [GH-309] Clarify that apache.version is a string +- [GH-305] Restart service after MPM changes +- [GH-304] Don't install systemd module on Amazon Linux +- [GH-298] Add non-threaded MPM break notice for PHP users +- [GH-296] Create lock_dir automatically + +v3.0.0 (2014-11-30) +------------------- +Major version update because of SSL Improvements and new platform MPM and Version defaults. + +- [GH-286] Refactor MPM and Apache version defaults: default is now apache 2.4 +- Note: set `apache.mpm` to `prefork` if you are using `mod_php` in Ubuntu >=14.04 +- [GH-281] mod_ssl: Disable SSLv3 by default to protect against POODLE attack (CVE-2014-3566) +- [GH-280] mod_ssl: Major update with modern Cipher Suite, and best practices. + Updated to a more modern default `apache.mod_ssl.cipher_suite`. + Added the following additional mod_ssl attributes + * `apache.mod_ssl.honor_cipher_order` + * `apache.mod_ssl.insecure_renegotiation` + * `apache.mod_ssl.strict_sni_vhost_check` + * `apache.mod_ssl.session_cache_timeout` + * `apache.mod_ssl.compression` + * `apache.mod_ssl.use_stapling` + * `apache.mod_ssl.stapling_responder_timeout` + * `apache.mod_ssl.stapling_return_responder_errors` + * `apache.mod_ssl.stapling_cache` + * `apache.mod_ssl.pass_phrase_dialog` + * `apache.mod_ssl.mutex` + * `apache.mod_ssl.directives` +- [GH-278] Improved chefspec tests execution time +- [GH-277] Optimize files watching for Guard on Win32 platform +- [GH-270] Don't attempt start until after configuration is written +- [GH-268] Now uses chefspec 4.1 +- [GH-267] Use Supermarket as the Berkshelf 3 source +- [GH-266] Rubocop based ruby style/syntax improvements +- [GH-264] mod_ssl: Add new attribute for to be ready to any custom directive +- [GH-249] Don't prepend Apache log path when requesting error logging to syslog +- [GH-247] Explicitly include mod_ldap before mod_authnz_ldap +- [GH-243] Expand mpm options for different distros/versions. +- [GH-239] Added `apache.mod_php5.install_method` attribute defaults to `package`. Install packages unless PHP is compiled from source. +- OneHealth Solutions was acquired by Viverae +- Remove ArchLinux pacman as a dependency and handle similar to apt, yum, zypper +- Adjust ubuntu apache 2.4 docroot_dir to match package (from /var/www to /var/www/html) +- [GH-238] Bump service config syntax check guard timeout to 10 seconds +- [GH-235] Removed `apache2::mpm_itk` which is not part of core and therefore should be its own cookbook +- [GH-234] /var/run/httpd/mod_fcgid directory now belongs to apache on Fedora/RHEL systems. +- [GH-233] Default web_app template should return 503 status code when maintenance file is present +- [GH-232] Cookbook now deletes a2* if they are symlinks before dropping template versions +- [GH-222] Set TraceEnable to off by default. +- [GH-213] Adjust chefspec to use the package resource on FreeBSD (previously freebsd_package) +- [GH-212] New attribute apache.locale which sets LANG. defaults to 'C' +- [GH-210] Clarify web_app definition usage around configuration templates. +- [GH-208] `apache_conf` now accepts `source` and `cookbook` parameters. + +v2.0.0 (2014-08-06) +-------------------- +Major version update because of major overhaul to support Apache 2.4 and a2enconf and a2endisconf changes. + +- [GH-204] mod_auth_openid: Added `apache.mod_auth_openid.version` attribute +- FreeBSD support has been improved with the release of chef 11.14.2, portsnap is no longer used in favor of pkgng. +- [GH-157] - Apache will only be started when a configuration test passes, this allows the chef run to fix any broken configuration without failing the chef run. +- `apache.log_dir` directory is now 0755 on all platforms (including the debian platform family) +- [GH-166, GH-173] - `conf.d` is no longer used and replaced by `conf-available` and `conf-enabled` managed via the `a2enconf` and `a2disconf` scripts +- [GH-166, GH-173] - All configuration files need to end in `.conf` for them to be loaded +- [GH-173] - Perl is a required package on all platforms to support the a2* scripts as we now use the debian versions directly. +- [GH-193] - per MPM settings: `maxclients` is now `maxrequestworkers` +- [GH-194] - per MPM settings: `maxrequestsperchild` is now `maxconnectionsperchild` +- [GH-161] - Added support for CentOS 7 +- [GH-180] - Improved SuSE support +- [GH-100] - Apache HTTP 2.4 support + This provides Apache 2.4 support in a backwards compatible way. + It adds the following new attributes: + - `apache.version` - This defaults to `2.2` and if changed to `2.4`; it triggers and assumes 2.4 packages will be installed. + - `apache.mpm` - In 2.4 mode, this specifies which mpm to install. Default is `prefork`. + - `apache.run_dir` + - `apache.lock_dir` + - `apache.libexec_dir` replaces `apache.libexecdir` + - `apache.prefork.maxrequestworkers` replaces `apache.prefork.maxclients` + - `apache.prefork.maxconnectionsperchild` replaces `apache.prefork.maxrequestsperchild` + - `apache.worker.threadlimit` + - `apache.worker.maxrequestworkers` replaces `apache.worker.maxclients` + - `apache.worker.maxconnectionsperchild `replaces `apache.worker.maxrequestsperchild` + - `apache.event.startservers` + - `apache.event.serverlimit` + - `apache.event.minsparethreads` + - `apache.event.maxsparethreads` + - `apache.event.threadlimit` + - `apache.event.threadsperchild` + - `apache.event.maxrequestworkers` + - `apache.event.maxconnectionsperchild` + - `apache.itk.startservers` + - `apache.itk.minspareservers` + - `apache.itk.maxspareservers` + - `apache.itk.maxrequestworkers` + - `apache.itk.maxconnectionsperchild` + + Apache 2.4 Upgrade Notes: + + Since the changes between apache 2.2 and apache 2.4 are pretty significant, we are unable to account for all changes needed for your upgrade. Please take a moment to familiarize yourself with the Apache Software Foundation provided upgrade documentation before attempting to use this cookbook with apache 2.4. See http://httpd.apache.org/docs/current/upgrading.html + + - This cookbook does not automatically specify which version of apache to install. We are at the mercy of the `package` provider. It is important, however, to make sure that you configure the `apache.version` attribute to match. For your convenience, we try to set reasonable defaults based on different platforms in our test suite. + - `mod_proxy` - In 2.4 mode, `apache.proxy.order`, `apache.proxy.deny_from`, `apache.proxy.allow_from` are ignored, as the attributes can not be supported in a backwards compatible way. Please use `apache.proxy.require` instead. + +v1.11.0 (2014-07-25) +-------------------- +- [GH-152] - Checking if server_aliases is defined in example +- [GH-106] - Only turn rewrite on once in web_app.conf.erb +- [GH-156] - Correct mod_basic/digest recipe names in README +- Recipe iptables now includes the iptables::default recipe +- Upgrade test-kitchen to latest version +- Replaced minitest integration tests with serverspec tests +- Added chefspec tests + + +v1.10.4 (2014-04-23) +-------------------- +- [COOK-4249] mod_proxy_http requires mod_proxy + + +v1.10.2 (2014-04-09) +-------------------- +- [COOK-4490] - Fix minitest `apache_configured_ports` helper +- [COOK-4491] - Fix minitest: escape regex interpolation +- [COOK-4492] - Fix service[apache2] CHEF-3694 duplication +- [COOK-4493] - Fix template[ports.conf] CHEF-3694 duplication + +As of 2014-04-04 and per [Community Cookbook Diversification](https://wiki.chef.io/display/chef/Community+Cookbook+Diversification) this cookbook now maintained by OneHealth Solutions. Please be patient as we get into the swing of things. + +v1.10.0 (2014-03-28) +-------------------- +- [COOK-3990] - Fix minitest failures on EL5 +- [COOK-4416] - Support the ability to point to local apache configs +- [COOK-4469] - Use reload instead of restart on RHEL + + +v1.9.6 (2014-02-28) +------------------- +[COOK-4391] - uncommenting the PIDFILE line + + +v1.9.4 (2014-02-27) +------------------- +Bumping version for toolchain + + +v1.9.1 (2014-02-27) +------------------- +[COOK-4348] Allow arbitrary params in sysconfig + + +v1.9.0 (2014-02-21) +------------------- +### Improvement +- **[COOK-4076](https://tickets.chef.io/browse/COOK-4076)** - foodcritic: dependencies are not defined properly +- **[COOK-2572](https://tickets.chef.io/browse/COOK-2572)** - Add mod_pagespeed recipe to apache2 + +### Bug +- **[COOK-4043](https://tickets.chef.io/browse/COOK-4043)** - apache2 cookbook does not depend on 'iptables' +- **[COOK-3919](https://tickets.chef.io/browse/COOK-3919)** - Move the default pidfile for apache2 on Ubuntu 13.10 or greater +- **[COOK-3863](https://tickets.chef.io/browse/COOK-3863)** - Add recipe for mod_jk +- **[COOK-3804](https://tickets.chef.io/browse/COOK-3804)** - Fix incorrect datatype for apache/default_modules, use recipes option in metadata +- **[COOK-3800](https://tickets.chef.io/browse/COOK-3800)** - Cannot load modules that use non-standard module identifiers +- **[COOK-1689](https://tickets.chef.io/browse/COOK-1689)** - The perl package name should be configurable + + +v1.8.14 +------- +Version bump for toolchain sanity + + +v1.8.12 +------- +Fixing various style issues for travis + + +v1.8.10 +------- +fixing metadata version error. locking to 3.0" + + +v1.8.8 +------ +Version bump for toolchain sanity + + +v1.8.6 +------ +Locking yum dependency to '< 3' + + +v1.8.4 +------ +### Bug +- **[COOK-3769](https://tickets.chef.io/browse/COOK-3769)** - Fix a critical bug where the `apache_module` could not enable modules + + +v1.8.2 +------ +### Bug +- **[COOK-3766](https://tickets.chef.io/browse/COOK-3766)** - Fix an issue where the `mod_ssl` recipe fails due to a missing attribute + + +v1.8.0 +------ +### Bug +- **[COOK-3680](https://tickets.chef.io/browse/COOK-3680)** - Update template paths +- **[COOK-3570](https://tickets.chef.io/browse/COOK-3570)** - Apache cookbook breaks on RHEL / CentOS 6 +- **[COOK-2944](https://tickets.chef.io/browse/COOK-2944)** - Fix foodcritic failures +- **[COOK-2893](https://tickets.chef.io/browse/COOK-2893)** - Improve mod_auth_openid recipe with guards and idempotency +- **[COOK-2758](https://tickets.chef.io/browse/COOK-2758)** - Fix use of non-existent attribute + +### New Feature +- **[COOK-3665](https://tickets.chef.io/browse/COOK-3665)** - Add recipe for mod_userdir +- **[COOK-3646](https://tickets.chef.io/browse/COOK-3646)** - Add recipe for mod_cloudflare +- **[COOK-3213](https://tickets.chef.io/browse/COOK-3213)** - Add recipe for mod_info + +### Improvement +- **[COOK-3656](https://tickets.chef.io/browse/COOK-3656)** - Parameterize apache2 binary +- **[COOK-3562](https://tickets.chef.io/browse/COOK-3562)** - Allow mod_proxy settings to be configured as attributes +- **[COOK-3326](https://tickets.chef.io/browse/COOK-3326)** - Fix default_test to use ServerTokens attribute +- **[COOK-2635](https://tickets.chef.io/browse/COOK-2635)** - Add support for SVG mime types +- **[COOK-2598](https://tickets.chef.io/browse/COOK-2598)** - FastCGI Module only works on Debian-based platforms +- **[COOK-1984](https://tickets.chef.io/browse/COOK-1984)** - Add option to configure the address apache listens to + + +v1.7.0 +------ +### Improvement + +- [COOK-3073]: make access.log location configurable per-platform +- [COOK-3074]: don't hardcode the error.log location in the default site config +- [COOK-3268]: don't hardcode DocumentRoot and cgi-bin locations in `default_site` + +### New Feature + +- [COOK-3184]: Add `mod_filter` recipe to Apache2-cookbook +- [COOK-3236]: Add `mod_action` recipe to Apache2-cookbook + +v1.6.6 +------ +1.6.4 had a missed step in the automated release, long live 1.6.6. + +### Bug + +- [COOK-3018]: apache2_module does duplicate delayed restart of apache2 service when conf = true +- [COOK-3027]: Default site enable true, then false, does not disable default site +- [COOK-3109]: fix apache lib_dir arch attribute regexp + +v1.6.2 +------ +- [COOK-2535] - `mod_auth_openid` requires libtool to run autogen.sh +- [COOK-2667] - Typo in usage documentation +- [COOK-2461] - `apache2::mod_auth_openid` fails on some ubuntu systems +- [COOK-2720] - Apache2 minitest helper function `ran_recipe` is not portable + +v1.6.0 +------ +- [COOK-2372] - apache2 mpm_worker: add ServerLimit attribute (default to 16) + +v1.5.0 +------ +The `mod_auth_openid` attributes are changed. The upstream maintainer deprecated the older release versions, and the source repository has releases available at specific SHA1SUM references. The new attribute, `node['apache']['mod_auth_openid']['ref']` is used to set this. + +- [COOK-2198] - `apache::mod_auth_openid` compiles from source, but does not install make on debian/ubuntu +- [COOK-2224] - version conflict between cucumber and other gems +- [COOK-2248] - `apache2::mod_php5` uses `not_if` "which php" without ensuring package 'which' is installed +- [COOK-2269] - Set allow list for mod_status incase external monitor scripts need +- [COOK-2276] - cookbook apache2 documentation regarding listening ports doesn't match default attributes +- [COOK-2296] - `mod_auth_openid` doesn't have tags/releases for the version I need for features and fixes +- [COOK-2323] - Add Oracle linux support + +v1.4.2 +------ +- [COOK-1721] - fix logrotate recipe + +v1.4.0 +------ +- [COOK-1456] - iptables enhancements +- [COOK-1473] - apache2 does not disable default site when setting "`default_site_enabled`" back to false +- [COOK-1824] - the apache2 cookbook needs to specify which binary is used on rhel platform +- [COOK-1916] - Download location wrong for apache2 `mod_auth_openid` >= 0.7 +- [COOK-1917] - Improve `mod_auth_openid` recipe to handle module upgrade more gracefully +- [COOK-2029] - apache2 restarts on every run on RHEL and friends, generate-module-list on every run. +- [COOK-2036] - apache2: Cookbook style + +v1.3.2 +------ +- [COOK-1804] - fix `web_app` definition parameter so site can be disabled. + +v1.3.0 +------ +- [COOK-1738] - Better configuration for `mod_include` and some overrides in `web_app` definition +- [COOK-1470] - Change SSL Ciphers to Mitigate BEAST attack + +v1.2.0 +------ +- [COOK-692] - delete package conf.d files in module recipes, for EL +- [COOK-1693] - Foodcritic finding for unnecessary string interpolation +- [COOK-1757] - platform_family and better style / usage practices + +v1.1.16 +------- +re-releasing as .16 due to error on tag 1.1.14 + +- [COOK-1466] - add `mod_auth_cas` recipe +- [COOK-1609] - apache2 changes ports.conf twice per run when using apache2::mod_ssl + +v1.1.12 +------- +- [COOK-1436] - restore apache2 web_app definition +- [COOK-1356] - allow ExtendedStatus via attribute +- [COOK-1403] - add mod_fastcgi recipe + +v1.1.10 +------- +- [COOK-1315] - allow the default site to not be enabled +- [COOK-1328] - cookbook tests (minitest, cucumber) + +v1.1.8 +------ +- Some platforms with minimal installations that don't have perl won't have a `node['languages']['perl']` attribute, so remove the conditional and rely on the power of idempotence in the package resource. +- [COOK-1214] - address foodcritic warnings +- [COOK-1180] - add `mod_logio` and fix `mod_proxy` + +v1.1.6 +------ +FreeBSD users: This release requires the `freebsd` cookbook. See README.md. + +- [COOK-1025] - freebsd support in mod_php5 recipe + +v1.1.4 +------ +- [COOK-1100] - support amazon linux + +v1.1.2 +------ +- [COOK-996] - apache2::mod_php5 can cause PHP and module API mismatches +- [COOK-1083] - return string for v_f_p and use correct value for default + +v1.1.0 +------ +- [COOK-861] - Add `mod_perl` and apreq2 +- [COOK-941] - fix `mod_auth_openid` on FreeBSD +- [COOK-1021] - add a commented-out LoadModule directive to keep apxs happy +- [COOK-1022] - consistency for icondir attribute +- [COOK-1023] - fix platform test for attributes +- [COOK-1024] - fix a2enmod script so it runs cleanly on !bash +- [COOK-1026] - fix `error_log` location on FreeBSD + +v1.0.8 +------ +- COOK-548 - directory resource doesn't have backup parameter + +v1.0.6 +------ +- COOK-915 - update to `mod_auth_openid` version 0.6, see __Recipes/mod_auth_openid__ below. +- COOK-548 - Add support for FreeBSD. + +v1.0.4 +------ +- COOK-859 - don't hardcode module paths + +v1.0.2 +------ +- Tickets resolved in this release: COOK-788, COOK-782, COOK-780 + +v1.0.0 +------ +- Red Hat family support is greatly improved, all recipes except `god_monitor` converge. +- Recipe `mod_auth_openid` now works on RHEL family distros +- Recipe `mod_php5` will now remove config from package on RHEL family so it doesn't conflict with the cookbook's. +- Added `php5.conf.erb` template for `mod_php5` recipe. +- Create the run state directory for `mod_fcgid` to prevent a startup error on RHEL version 6. +- New attribute `node['apache']['lib_dir']` to handle lib vs lib64 on RHEL family distributions. +- New attribute `node['apache']['group']`. +- Scientific Linux support added. +- Use a file resource instead of the generate-module-list executed perl script on RHEL family. +- "default" site can now be disabled. +- web_app now has an "enable" parameter. +- Support for dav_fs apache module. +- Tickets resolved in this release: COOK-754, COOK-753, COOK-665, COOK-624, COOK-579, COOK-519, COOK-518 +- Fix node references in template for a2dissite +- Use proper user and group attributes on files and templates. +- Replace the anemic README.rdoc with this new and improved superpowered README.md :). diff --git a/cookbooks/apache2/README.md b/cookbooks/apache2/README.md new file mode 100644 index 0000000..6de7c9f --- /dev/null +++ b/cookbooks/apache2/README.md @@ -0,0 +1,727 @@ +apache2 Cookbook +================ +[![Cookbook Version](https://img.shields.io/cookbook/v/apache2.svg?style=flat)](https://supermarket.chef.io/cookbooks/apache2) +[![Build Status](https://travis-ci.org/svanzoest-cookbooks/apache2.svg?branch=master)](https://travis-ci.org/svanzoest-cookbooks/apache2) +[![Dependency Status](http://img.shields.io/gemnasium/svanzoest-cookbooks/apache2.svg?style=flat)](https://gemnasium.com/svanzoest-cookbooks/apache2) +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/svanzoest-cookbooks/apache2?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + +This cookbook provides a complete Debian/Ubuntu style Apache HTTPD +configuration. Non-Debian based distributions such as Red Hat/CentOS, +ArchLinux and others supported by this cookbook will have a +configuration that mimics Debian/Ubuntu style as it is easier to +manage with Chef. + +Debian-style Apache configuration uses scripts to manage modules and +sites (vhosts). The scripts are: + +* a2ensite +* a2dissite +* a2enmod +* a2dismod +* a2enconf +* a2disconf + +This cookbook ships with templates of these scripts for non +Debian/Ubuntu platforms. The scripts are used in the __Definitions__ +below. + +Requirements +============ + +## Ohai and Chef: + +* Ohai: 0.6.12+ +* Chef: 0.10.10+ + +As of v1.2.0, this cookbook makes use of `node['platform_family']` to +simplify platform selection logic. This attribute was introduced in +Ohai v0.6.12. The recipe methods were introduced in Chef v0.10.10. If +you must run an older version of Chef or Ohai, use [version 1.1.16 of +this cookbook](https://supermarket.chef.io/cookbooks/apache2/versions/1.1.16). + +## Cookbooks: + +This cookbook has no direct external dependencies. + +Depending on your OS configuration and security policy, you may need +additional recipes or cookbooks for this cookbook's recipes to +converge on the node. In particular, the following Operating System +settings may affect the behavior of this cookbook: + +* apt cache outdated +* SELinux enabled +* IPtables +* Compile tools +* 3rd party repositories + +On Ubuntu/Debian, use Opscode's `apt` cookbook to ensure the package +cache is updated so Chef can install packages, or consider putting +apt-get in your bootstrap process or +[knife bootstrap template](http://docs.chef.io/knife_bootstrap.html) + +On RHEL, SELinux is enabled by default. The `selinux` cookbook +contains a `permissive` recipe that can be used to set SELinux to +"Permissive" state. Otherwise, additional recipes need to be created +by the user to address SELinux permissions. + +The easiest but **certainly not ideal way** to deal with IPtables is +to flush all rules. Chef Software does provide an `iptables` cookbook but is +migrating from the approach used there to a more robust solution +utilizing a general "firewall" LWRP that would have an "iptables" +provider. Alternately, you can use ufw, with Opscode's `ufw` and +`firewall` cookbooks to set up rules. See those cookbooks' READMEs for +documentation. + +Build/compile tools may not be installed on the system by default. +Some recipes (e.g., `apache2::mod_auth_openid`) build the module from +source. Use Opscode's `build-essential` cookbook to get essential +build packages installed. + +On ArchLinux, if you are using the `apache2::mod_auth_openid` recipe, +you also need the `pacman` cookbook for the `pacman_aur` LWRP. Put +`recipe[pacman]` on the node's expanded run list (on the node or in a +role). This is not an explicit dependency because it is only required +for this single recipe and platform; the pacman default recipe +performs `pacman -Sy` to keep pacman's package cache updated. + +## Platforms: + +The following platforms and versions are tested and supported using +[test-kitchen](http://kitchen.ci/) + +* Ubuntu 12.04, 14.04 +* Debian 7.6 +* CentOS 6.5, 7.0 + +The following platform families are supported in the code, and are +assumed to work based on the successful testing on Ubuntu and CentOS. + +* Red Hat (rhel) +* Fedora +* Amazon Linux + +The following platforms are also supported in the code, have been +tested manually but are not tested under test-kitchen. + +* SUSE/OpenSUSE +* ArchLinux +* FreeBSD + +### Notes for RHEL Family: + +On Red Hat Enterprise Linux and derivatives, the EPEL repository may +be necessary to install packages used in certain recipes. The +`apache2::default` recipe, however, does not require any additional +repositories. Opscode's `yum-epel` cookbook can be used to add the +EPEL repository. See __Examples__ for more information. + +### Notes for FreeBSD: + +Version 2.0 has been had some basic testing against FreeBSD 10.0 using +Chef 11.14.2 which has support for pkgng (CHEF-4637). + +Tests +===== + +This cookbook in the +[source repository](https://github.com/svanzoest-cookbooks/apache2/) +contains chefspec, serverspec and cucumber tests. This is an initial proof of +concept that will be fleshed out with more supporting infrastructure +at a future time. + +Please see the CONTRIBUTING file for information on how to add tests +for your contributions. + +Attributes +========== + +This cookbook uses many attributes, broken up into a few different +kinds. + +Platform specific +----------------- + +In order to support the broadest number of platforms, several +attributes are determined based on the node's platform. See the +attributes/default.rb file for default values in the case statement at +the top of the file. + +* `node['apache']['package']` - Package name for Apache2 +* `node['apache']['perl_pkg']` - Package name for Perl +* `node['apache']['dir']` - Location for the Apache configuration +* `node['apache']['log_dir']` - Location for Apache logs +* `node['apache']['error_log']` - Location for the default error log +* `node['apache']['access_log']` - Location for the default access log +* `node['apache']['user']` - User Apache runs as +* `node['apache']['group']` - Group Apache runs as +* `node['apache']['binary']` - Apache httpd server daemon +* `node['apache']['conf_dir']` - Location for the main config file (e.g apache2.conf or httpd.conf) +* `node['apache']['docroot_dir']` - Location for docroot +* `node['apache']['cgibin_dir']` - Location for cgi-bin +* `node['apache']['icondir']` - Location for icons +* `node['apache']['cache_dir']` - Location for cached files used by Apache itself or recipes +* `node['apache']['pid_file']` - Location of the PID file for Apache httpd +* `node['apache']['lib_dir']` - Location for shared libraries +* `node['apache']['default_site_enabled']` - Default site enabled. Default is false. +* `node['apache']['ext_status']` - if true, enables ExtendedStatus for `mod_status` +* `node['apache']['locale'] - Locale to set in sysconfig or envvars and used for subprocesses and modules (like mod_dav and mod_wsgi). On debian systems Uses system-local if set to 'system', defaults to 'C'. + +General settings +---------------- + +These are general settings used in recipes and templates. Default +values are noted. + +* `node['apache']['version']` - Specifing 2.4 triggers apache 2.4 support. If the platform is known during our test to install 2.4 by default, it will be set to 2.4 for you. Otherwise it falls back to 2.2. This value should be specified as a string. +* `node['apache']['listen_addresses']` - Addresses that httpd should listen on. Default is any ("*"). +* `node['apache']['listen_ports']` - Ports that httpd should listen on. Default is port 80. +* `node['apache']['contact']` - Value for ServerAdmin directive. Default "ops@example.com". +* `node['apache']['timeout']` - Value for the Timeout directive. Default is 300. +* `node['apache']['keepalive']` - Value for the KeepAlive directive. Default is On. +* `node['apache']['keepaliverequests']` - Value for MaxKeepAliveRequests. Default is 100. +* `node['apache']['keepalivetimeout']` - Value for the KeepAliveTimeout directive. Default is 5. +* `node['apache']['sysconfig_additional_params']` - Additionals variables set in sysconfig file. Default is empty. +* `node['apache']['default_modules']` - Array of module names. Can take "mod_FOO" or "FOO" as names, where FOO is the apache module, e.g. "`mod_status`" or "`status`". +* `node['apache']['mpm']` - With apache.version 2.4, specifies what Multi-Processing Module to enable. Default is "prefork". + +The modules listed in `default_modules` will be included as recipes in `recipe[apache::default]`. + +Prefork attributes +------------------ + +Prefork attributes are used for tuning the Apache HTTPD [prefork MPM](http://httpd.apache.org/docs/current/mod/prefork.html) configuration. + +* `node['apache']['prefork']['startservers']` - initial number of server processes to start. Default is 16. +* `node['apache']['prefork']['minspareservers']` - minimum number of spare server processes. Default 16. +* `node['apache']['prefork']['maxspareservers']` - maximum number of spare server processes. Default 32. +* `node['apache']['prefork']['serverlimit']` - upper limit on configurable server processes. Default 400. +* `node['apache']['prefork']['maxrequestworkers']` - Maximum number of connections that will be processed simultaneously +* `node['apache']['prefork']['maxconnectionsperchild']` - Maximum number of request a child process will handle. Default 10000. + +Worker attributes +----------------- + +Worker attributes are used for tuning the Apache HTTPD [worker MPM](http://httpd.apache.org/docs/current/mod/worker.html) +configuration. + +* `node['apache']['worker']['startservers']` - Initial number of server processes to start. Default 4 +* `node['apache']['worker']['serverlimit']` - Upper limit on configurable server processes. Default 16. +* `node['apache']['worker']['minsparethreads']` - Minimum number of spare worker threads. Default 64 +* `node['apache']['worker']['maxsparethreads']` - Maximum number of spare worker threads. Default 192. +* `node['apache']['worker']['maxrequestworkers']` - Maximum number of simultaneous connections. Default 1024. +* `node['apache']['worker']['maxconnectionsperchild']` - Limit on the number of connections that an individual child server will handle during its life. + +Event attributes +---------------- + +Event attributes are used for tuning the Apache HTTPD [event MPM](http://httpd.apache.org/docs/current/mod/event.html) +configuration. + +* `node['apache']['event']['startservers']` - Initial number of child server processes created at startup. Default 4. +* `node['apache']['event']['serverlimit']` - Upper limit on configurable number of processes. Default 16. +* `node['apache']['event']['minsparethreads']` - Minimum number of spare worker threads. Default 64 +* `node['apache']['event']['maxsparethreads']` - Maximum number of spare worker threads. Default 192. +* `node['apache']['event']['threadlimit']` - Upper limit on the configurable number of threads per child process. Default 192. +* `node['apache']['event']['threadsperchild']` - Number of threads created by each child process. Default 64. +* `node['apache']['event']['maxrequestworkers']` - Maximum number of connections that will be processed simultaneously. +* `node['apache']['event']['maxconnectionsperchild']` - Limit on the number of connections that an individual child server will handle during its life. + +Other/Unsupported MPM +--------------------- + +To use the cookbook with an unsupported mpm (other than prefork, event or worker): + +* set `node['apache']['mpm']` to the name of the module (e.g. `itk`) +* in your cookbook, after `include_recipe 'apache2'` use the `apache_module` definition to enable/disable the required module(s) + + +mod\_auth\_openid attributes +---------------------------- + +The following attributes are in the `attributes/mod_auth_openid.rb` +file. Like all Chef attributes files, they are loaded as well, but +they're logistically unrelated to the others, being specific to the +`mod_auth_openid` recipe. + +* `node['apache']['mod_auth_openid']['checksum']` - sha256sum of the tarball containing the source. +* `node['apache']['mod_auth_openid']['ref']` - Any sha, tag, or branch found from https://github.com/bmuller/mod_auth_openid +* `node['apache']['mod_auth_openid']['version']` - directory name version within the tarball +* `node['apache']['mod_auth_openid']['cache_dir']` - the cache directory is where the sqlite3 database is stored. It is separate so it can be managed as a directory resource. +* `node['apache']['mod_auth_openid']['dblocation']` - filename of the sqlite3 database used for directive `AuthOpenIDDBLocation`, stored in the `cache_dir` by default. +* `node['apache']['mod_auth_openid']['configure_flags']` - optional array of configure flags passed to the `./configure` step in the compilation of the module. + +mod\_ssl attributes +------------------- + +For general information on this attributes see http://httpd.apache.org/docs/current/mod/mod_ssl.html + +* `node['apache']['mod_ssl']['cipher_suite']` - sets the SSLCiphersuite value to the specified string. The default is + considered "sane" but you may need to change it for your local security policy, e.g. if you have PCI-DSS requirements. Additional + commentary on the + [original pull request](https://github.com/svanzoest-cookbooks/apache2/pull/15#commitcomment-1605406). +* `node['apache']['mod_ssl']['honor_cipher_order']` - Option to prefer the server's cipher preference order. Default 'On'. +* `node['apache']['mod_ssl']['insecure_renegotiation']` - Option to enable support for insecure renegotiation. Default 'Off'. +* `node['apache']['mod_ssl']['strict_sni_vhost_check']` - Whether to allow non-SNI clients to access a name-based virtual host. Default 'Off'. +* `node['apache']['mod_ssl']['session_cache']` - Configures the OCSP stapling cache. Default `shmcb:/var/run/apache2/ssl_scache` +* `node['apache']['mod_ssl']['session_cache_timeout']` - Number of seconds before an SSL session expires in the Session Cache. Default 300. +* `node['apache']['mod_ssl']['compression']` - Enable compression on the SSL level. Default 'Off'. +* `node['apache']['mod_ssl']['use_stapling']` - Enable stapling of OCSP responses in the TLS handshake. Default 'Off'. +* `node['apache']['mod_ssl']['stapling_responder_timeout']` - Timeout for OCSP stapling queries. Default 5 +* `node['apache']['mod_ssl']['stapling_return_responder_errors']` - Pass stapling related OCSP errors on to client. Default 'Off' +* `node['apache']['mod_ssl']['stapling_cache']` - Configures the OCSP stapling cache. Default `shmcb:/var/run/ocsp(128000)` +* `node['apache']['mod_ssl']['pass_phrase_dialog']` - Configures SSLPassPhraseDialog. Default `builtin` +* `node['apache']['mod_ssl']['mutex']` - Configures SSLMutex. Default `file:/var/run/apache2/ssl_mutex` +* `node['apache']['mod_ssl']['directives']` - Hash for add any custom directive. + +For more information on these directives and how to best secure your site see +- https://bettercrypto.org/ +- https://wiki.mozilla.org/Security/Server_Side_TLS +- https://www.insecure.ws/linux/apache_ssl.html +- https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ +- https://istlsfastyet.com/ +- https://www.ssllabs.com/projects/best-practices/ + +Recipes +======= + +Most of the recipes in the cookbook are for enabling Apache modules. +Where additional configuration or behavior is used, it is documented +below in more detail. + +The following recipes merely enable the specified module: `mod_alias`, +`mod_auth_basic`, `mod_auth_digest`, `mod_authn_file`, `mod_authnz_ldap`, +`mod_authz_default`, `mod_authz_groupfile`, `mod_authz_host`, +`mod_authz_user`, `mod_autoindex`, `mod_cgi`, `mod_dav_fs`, +`mod_dav_svn`, `mod_deflate`, `mod_dir`, `mod_env`, `mod_expires`, +`mod_headers`, `mod_ldap`, `mod_log_config`, `mod_mime`, +`mod_negotiation`, `mod_proxy`, `mod_proxy_ajp`, `mod_proxy_balancer`, +`mod_proxy_connect`, `mod_proxy_http`, `mod_python`, `mod_rewrite`, +`mod_setenvif`, `mod_status`, `mod_wsgi`, `mod_xsendfile`. + +On RHEL Family distributions, certain modules ship with a config file +with the package. The recipes here may delete those configuration +files to ensure they don't conflict with the settings from the +cookbook, which will use per-module configuration in +`/etc/httpd/mods-enabled`. + +default +------- + +The default recipe does a number of things to set up Apache HTTPd. It +also includes a number of modules based on the attribute +`node['apache']['default_modules']` as recipes. + +mod\_auth\_cas +-------------- + +This recipe installs the proper package and enables the `auth_cas` +module. It can install from source or package. Package is the default, +set the attribute `node['apache']['mod_auth_cas']['from_source']` to +true to enable source installation. Modify the version to install by +changing the attribute +`node['apache']['mod_auth_cas']['source_revision']`. It is a version +tag by default, but could be master, or another tag, or branch. + +The module configuration is written out with the `CASCookiePath` set, +otherwise an error loading the module may cause Apache to not start. + +**Note**: This recipe does not work on EL 6 platforms unless +epel-testing repository is enabled (outside the scope of this +cookbook), or the package version 1.0.8.1-3.el6 or higher is otherwise +available to the system due to this bug: + +https://bugzilla.redhat.com/show_bug.cgi?format=multiple&id=708550 + +mod\_auth\_openid +----------------- + +**Changed via COOK-915** + +This recipe compiles the module from source. In addition to +`build-essential`, some other packages are included for installation +like the GNU C++ compiler and development headers. + +To use the module in your own cookbooks to authenticate systems using +OpenIDs, specify an array of OpenIDs that are allowed to authenticate +with the attribute `node['apache']['allowed_openids']`. Use the +following in a vhost to protect with OpenID authentication: + + AuthType OpenID require user <%= node['apache']['allowed_openids'].join(' ') %> + AuthOpenIDDBLocation <%= node['apache']['mod_auth_openid']['dblocation'] %> + +Change the DBLocation with the attribute as required; this file is in +a different location than previous versions, see below. It should be a +sane default for most platforms, though, see +`attributes/mod_auth_openid.rb`. + +### Changes from COOK-915: + +* `AuthType OpenID` instead of `AuthOpenIDEnabled On`. +* `require user` instead of `AuthOpenIDUserProgram`. +* A bug(?) in `mod_auth_openid` causes it to segfault when attempting + to update the database file if the containing directory is not + writable by the HTTPD process owner (e.g., www-data), even if the + file is writable. In order to not interfere with other settings from + the default recipe in this cookbook, the db file is moved. + +mod\_fastcgi +------------ + +Install the fastcgi package and enable the module. + +Only work on Debian/Ubuntu + +mod\_fcgid +---------- + +Installs the fcgi package and enables the module. Requires EPEL on +RHEL family. + +On RHEL family, this recipe will delete the fcgid.conf and on version +6+, create the /var/run/httpd/mod_fcgid` directory, which prevents the +emergency error: + + [emerg] (2)No such file or directory: mod_fcgid: Can't create shared memory for size XX bytes + +mod\_php5 +-------- + +Simply installs the appropriate package on Debian, Ubuntu and +ArchLinux. + +On Red Hat family distributions including Fedora, the php.conf that +comes with the package is removed. On RHEL platforms less than v6, the +`php53` package is used. + +* `node['apache']['mod_php5']['install_method']` - default `package` can be overridden to avoid package installs. + +mod\_ssl +-------- + +Besides installing and enabling `mod_ssl`, this recipe will append +port 443 to the `node['apache']['listen_ports']` attribute array and +update the ports.conf. + +Definitions +=========== + +The cookbook provides a few definitions. At some point in the future +these definitions may be refactored into lightweight resources and +providers as suggested by +[foodcritic rule FC015](http://acrmp.github.com/foodcritic/#FC015). + +apache\_config +------------ + +Sets up configuration file for Apache from a template. The +template should be in the same cookbook where the definition is used. This is used by the `apache_conf` definition and is not often used directly. + +It will use `a2enconf` and `a2disconf` to control the symlinking of configuration files between `conf-available` and `conf-enabled`. + +Enable or disable an Apache config file in +`#{node['apache']['dir']}/conf-available` by calling `a2enmod` or +`a2dismod` to manage the symbolic link in +`#{node['apache']['dir']}/conf-enabled`. These config files should be created in your cookbook, and placed on the system using `apache_conf` + +### Parameters: + +* `name` - Name of the config enabled or disabled with the `a2enconf` or `a2disconf` scripts. +* `source` - The location of a template file. The default `name.erb`. +* `cookbook` - The cookbook in which the configuration template is located (if it is not located in the current cookbook). The default value is the current cookbook. +* `enable` - Default true, which uses `a2enconf` to enable the config. If false, the config will be disabled with `a2disconf`. + +### Examples: + +Enable the example config. + +`````` + apache_config 'example' do + enable true + end +`````` + +Disable a module: + +`````` + apache_config 'disabled_example' do + enable false + end +`````` + +See the recipes directory for many more examples of `apache_config`. + +apache\_conf +------------ + +Writes conf files to the `conf-available` folder, and passes enabled values to `apache_config`. + +This definition should generally be called over `apache_config`. + +### Parameters: + +* `name` - Name of the config placed and enabled or disabled with the `a2enconf` or `a2disconf` scripts. +* `enable` - Default true, which uses `a2enconf` to enable the config. If false, the config will be disabled with `a2disconf`. +* `conf_path` - path to put the config in if you need to override the default `conf-available`. + +### Examples: + +Place and enable the example conf: + +`````` + apache_conf 'example' do + enable true + end +`````` + +Place and disable (or never enable to begin with) the example conf: + +`````` + apache_conf 'example' do + enable false + end +`````` + +Place the example conf, which has a different path than the default (conf-*): + +`````` + apache_conf 'example' do + conf_path '/random/example/path' + enable false + end +`````` + +apache\_mod +------------ + +Sets up configuration file for an Apache module from a template. The +template should be in the same cookbook where the definition is used. +This is used by the `apache_module` definition and is not often used +directly. + +This will use a template resource to write the module's configuration +file in the `mods-available` under the Apache configuration directory +(`node['apache']['dir']`). This is a platform-dependent location. See +__apache\_module__. + +### Parameters: + +* `name` - Name of the template. When used from the `apache_module`, + it will use the same name as the module. + +### Examples: + +Create `#{node['apache']['dir']}/mods-available/alias.conf`. + +`````` + apache_mod "alias" +`````` + +apache\_module +-------------- + +Enable or disable an Apache module in +`#{node['apache']['dir']}/mods-available` by calling `a2enmod` or +`a2dismod` to manage the symbolic link in +`#{node['apache']['dir']}/mods-enabled`. If the module has a +configuration file, a template should be created in the cookbook where +the definition is used. See __Examples__. + +### Parameters: + +* `name` - Name of the module enabled or disabled with the `a2enmod` or `a2dismod` scripts. +* `identifier` - String to identify the module for the `LoadModule` directive. Not typically needed, defaults to `#{name}_module` +* `enable` - Default true, which uses `a2enmod` to enable the module. If false, the module will be disabled with `a2dismod`. +* `conf` - Default false. Set to true if the module has a config file, which will use `apache_mod` for the file. +* `filename` - specify the full name of the file, e.g. + +### Examples: + +Enable the ssl module, which also has a configuration template in `templates/default/mods/ssl.conf.erb`. + +`````` + apache_module "ssl" do + conf true + end +`````` + +Enable the php5 module, which has a different filename than the module default: + +`````` + apache_module "php5" do + filename "libphp5.so" + end +`````` + +Disable a module: + +`````` + apache_module "disabled_module" do + enable false + end +`````` + +See the recipes directory for many more examples of `apache_module`. + +apache\_site +------------ + +Enable or disable a VirtualHost in +`#{node['apache']['dir']}/sites-available` by calling a2ensite or +a2dissite to manage the symbolic link in +`#{node['apache']['dir']}/sites-enabled`. + +The template for the site must be managed as a separate resource. To +combine the template with enabling a site, see `web_app`. + +### Parameters: + +* `name` - Name of the site. +* `enable` - Default true, which uses `a2ensite` to enable the site. If false, the site will be disabled with `a2dissite`. + +web\_app +-------- + +Manage a template resource for a VirtualHost site, and enable it with +`apache_site`. This is commonly done for managing web applications +such as Ruby on Rails, PHP or Django, and the default behavior +reflects that. However it is flexible. + +This definition includes some recipes to make sure the system is +configured to have Apache and some sane default modules: + +* `apache2` +* `apache2::mod_rewrite` +* `apache2::mod_deflate` +* `apache2::mod_headers` + +It will then configure the template (see __Parameters__ and +__Examples__ below), and enable or disable the site per the `enable` +parameter. + +### Parameters: + +Current parameters used by the definition: + +* `name` - The name of the site. The template will be written to + `#{node['apache']['dir']}/sites-available/#{params['name']}.conf` +* `cookbook` - Optional. Cookbook where the source template is. If + this is not defined, Chef will use the named template in the + cookbook where the definition is used. +* `template` - Default `web_app.conf.erb`, source template file. +* `enable` - Default true. Passed to the `apache_site` definition. + +Additional parameters can be defined when the definition is called in +a recipe, see __Examples__. + +### Examples: + +The recommended way to use the `web_app` definition is in a application specific cookbook named "my_app". +The following example would look for a template named 'web_app.conf.erb' in your cookbook containing +the apache httpd directives defining the `VirtualHost` that would serve up "my_app". + +`````` + web_app "my_app" do + template 'web_app.conf.erb' + server_name node['my_app']['hostname'] + end +`````` + +All parameters are passed into the template. You can use whatever you +like. The apache2 cookbook comes with a `web_app.conf.erb` template as +an example. The following parameters are used in the template: + +* `server_name` - ServerName directive. +* `server_aliases` - ServerAlias directive. Must be an array of aliases. +* `docroot` - DocumentRoot directive. +* `application_name` - Used in RewriteLog directive. Will be set to the `name` parameter. +* `directory_index` - Allow overriding the default DirectoryIndex setting, optional +* `directory_options` - Override Options on the docroot, for example to add parameters like Includes or Indexes, optional. +* `allow_override` - Modify the AllowOverride directive on the docroot to support apps that need .htaccess to modify configuration or require authentication. + +To use the default web_app, for example: + +`````` + web_app "my_site" do + server_name node['hostname'] + server_aliases [node['fqdn'], "my-site.example.com"] + docroot "/srv/www/my_site" + cookbook 'apache2' + end +`````` + +The parameters specified will be used as: + +* `@params[:server_name]` +* `@params[:server_aliases]` +* `@params[:docroot]` + +In the template. When you write your own, the `@` is significant. + +For more information about Definitions and parameters, see the +[Chef Wiki](http://docs.chef.io/definitions.html) + +Usage +===== + +Using this cookbook is relatively straightforward. Add the desired +recipes to the run list of a node, or create a role. Depending on your +environment, you may have multiple roles that use different recipes +from this cookbook. Adjust any attributes as desired. For example, to +create a basic role for web servers that provide both HTTP and HTTPS: + +`````` + % cat roles/webserver.rb + name "webserver" + description "Systems that serve HTTP and HTTPS" + run_list( + "recipe[apache2]", + "recipe[apache2::mod_ssl]" + ) + default_attributes( + "apache" => { + "listen_ports" => ["80", "443"] + } + ) +`````` + +For examples of using the definitions in your own recipes, see their +respective sections above. + +License and Authors +=================== + +* Author:: Adam Jacob +* Author:: Joshua Timberman +* Author:: Bryan McLellan +* Author:: Dave Esposito +* Author:: David Abdemoulaie +* Author:: Edmund Haselwanter +* Author:: Eric Rochester +* Author:: Jim Browne +* Author:: Matthew Kent +* Author:: Nathen Harvey +* Author:: Ringo De Smet +* Author:: Sean OMeara +* Author:: Seth Chisamore +* Author:: Gilles Devaux +* Author:: Sander van Zoest +* Author:: Taylor Price + +* Copyright:: 2009-2012, Chef Software, Inc +* Copyright:: 2011, Atriso +* Copyright:: 2011, CustomInk, LLC. +* Copyright:: 2013-2014, OneHealth Solutions, Inc. +* Copyright:: 2014, Viverae, Inc. +* Copyright:: 2015, Alexander van Zoest + +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/cookbooks/apache2/attributes/default.rb b/cookbooks/apache2/attributes/default.rb new file mode 100644 index 0000000..043915e --- /dev/null +++ b/cookbooks/apache2/attributes/default.rb @@ -0,0 +1,354 @@ +# +# Cookbook Name:: apache2 +# Attributes:: default +# +# Copyright 2008-2013, Chef Software, Inc. +# Copyright 2014, Viverae, Inc. +# +# 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. +# + +default['apache']['mpm'] = + case node['platform_family'] + when 'debian' + case node['platform'] + when 'ubuntu' + if node['platform_version'].to_f >= 14.04 + 'event' + elsif node['platform_version'].to_f >= 12.04 + 'worker' + else + 'prefork' + end + when 'debian' + node['platform_version'].to_f >= 7.0 ? 'worker' : 'prefork' + when 'linuxmint' + node['platform_version'].to_i >= 17 ? 'event' : 'prefork' + else + 'prefork' + end + else + 'prefork' + end + +default['apache']['version'] = + case node['platform_family'] + when 'debian' + case node['platform'] + when 'ubuntu' + node['platform_version'].to_f >= 13.10 ? '2.4' : '2.2' + when 'linuxmint' + node['platform_version'].to_i >= 16 ? '2.4' : '2.2' + when 'debian', 'raspbian' + node['platform_version'].to_f >= 8.0 ? '2.4' : '2.2' + else + '2.4' + end + when 'rhel' + case node['platform'] + when 'amazon' + node['platform_version'].to_f >= 2013.09 ? '2.4' : '2.2' + else + node['platform_version'].to_f >= 7.0 ? '2.4' : '2.2' + end + when 'fedora' + node['platform_version'].to_f >= 18 ? '2.4' : '2.2' + when 'suse' + case node['platform'] + when 'opensuse' + node['platform_version'].to_f >= 13.1 ? '2.4' : '2.2' + # FIXME: when "suse" for SLES + else + '2.4' + end + when 'freebsd' + node['platform_version'].to_f >= 10.0 ? '2.4' : '2.2' + else + '2.4' + end + +default['apache']['root_group'] = 'root' +default['apache']['default_site_name'] = 'default' + +# Where the various parts of apache are +case node['platform'] +when 'redhat', 'centos', 'scientific', 'fedora', 'amazon', 'oracle' + default['apache']['package'] = 'httpd' + default['apache']['service_name'] = 'httpd' + default['apache']['devel_package'] = 'httpd-devel' + default['apache']['perl_pkg'] = 'perl' + default['apache']['apachectl'] = '/usr/sbin/apachectl' + default['apache']['dir'] = '/etc/httpd' + default['apache']['log_dir'] = '/var/log/httpd' + default['apache']['error_log'] = 'error.log' + default['apache']['access_log'] = 'access.log' + default['apache']['user'] = 'apache' + default['apache']['group'] = 'apache' + default['apache']['binary'] = '/usr/sbin/httpd' + default['apache']['conf_dir'] = '/etc/httpd/conf' + default['apache']['docroot_dir'] = '/var/www/html' + default['apache']['cgibin_dir'] = '/var/www/cgi-bin' + if node['apache']['version'] == '2.4' + default['apache']['icondir'] = '/usr/share/httpd/icons' + else + default['apache']['icondir'] = '/var/www/icons' + end + default['apache']['cache_dir'] = '/var/cache/httpd' + default['apache']['run_dir'] = '/var/run/httpd' + default['apache']['lock_dir'] = '/var/run/httpd' + if node['platform'] == 'amazon' && node['apache']['version'] == '2.4' + default['apache']['package'] = 'httpd24' + default['apache']['devel_package'] = 'httpd24-devel' + end + if node['platform_version'].to_f >= 6 + default['apache']['pid_file'] = '/var/run/httpd/httpd.pid' + else + default['apache']['pid_file'] = '/var/run/httpd.pid' + end + default['apache']['lib_dir'] = node['kernel']['machine'] =~ /^i[36]86$/ ? '/usr/lib/httpd' : '/usr/lib64/httpd' + default['apache']['libexec_dir'] = "#{node['apache']['lib_dir']}/modules" +when 'suse', 'opensuse' + default['apache']['package'] = 'apache2' + default['apache']['perl_pkg'] = 'perl' + default['apache']['devel_package'] = 'httpd-devel' + default['apache']['apachectl'] = '/usr/sbin/apache2ctl' + default['apache']['dir'] = '/etc/apache2' + default['apache']['log_dir'] = '/var/log/apache2' + default['apache']['error_log'] = 'error.log' + default['apache']['access_log'] = 'access.log' + default['apache']['user'] = 'wwwrun' + default['apache']['group'] = 'www' + default['apache']['binary'] = '/usr/sbin/httpd2' + default['apache']['conf_dir'] = '/etc/apache2' + default['apache']['docroot_dir'] = '/srv/www/htdocs' + default['apache']['cgibin_dir'] = '/srv/www/cgi-bin' + default['apache']['icondir'] = '/usr/share/apache2/icons' + default['apache']['cache_dir'] = '/var/cache/apache2' + default['apache']['run_dir'] = '/var/run/httpd' + default['apache']['lock_dir'] = '/var/run/httpd' + if node['platform_version'].to_f >= 6 + default['apache']['pid_file'] = '/var/run/httpd/httpd.pid' + else + default['apache']['pid_file'] = '/var/run/httpd.pid' + end + default['apache']['lib_dir'] = node['kernel']['machine'] =~ /^i[36]86$/ ? '/usr/lib/apache2' : '/usr/lib64/apache2' + default['apache']['libexec_dir'] = node['apache']['lib_dir'] +when 'debian', 'ubuntu' + default['apache']['package'] = 'apache2' + default['apache']['perl_pkg'] = 'perl' + if node['apache']['mpm'] == 'prefork' + default['apache']['devel_package'] = 'apache2-prefork-dev' + else + default['apache']['devel_package'] = 'apache2-dev' + end + default['apache']['apachectl'] = '/usr/sbin/apache2ctl' + default['apache']['dir'] = '/etc/apache2' + default['apache']['log_dir'] = '/var/log/apache2' + default['apache']['error_log'] = 'error.log' + default['apache']['access_log'] = 'access.log' + default['apache']['user'] = 'www-data' + default['apache']['group'] = 'www-data' + default['apache']['binary'] = '/usr/sbin/apache2' + default['apache']['conf_dir'] = '/etc/apache2' + default['apache']['cgibin_dir'] = '/usr/lib/cgi-bin' + default['apache']['icondir'] = '/usr/share/apache2/icons' + default['apache']['cache_dir'] = '/var/cache/apache2' + default['apache']['run_dir'] = '/var/run/apache2' + default['apache']['lock_dir'] = '/var/lock/apache2' + # this should use COOK-3917 to educate the initscript of the pid location + if node['apache']['version'] == '2.4' + default['apache']['pid_file'] = '/var/run/apache2/apache2.pid' + default['apache']['docroot_dir'] = '/var/www/html' + else + default['apache']['pid_file'] = '/var/run/apache2.pid' + default['apache']['docroot_dir'] = '/var/www' + end + default['apache']['lib_dir'] = '/usr/lib/apache2' + default['apache']['build_dir'] = '/usr/share/apache2' + default['apache']['libexec_dir'] = "#{node['apache']['lib_dir']}/modules" + default['apache']['default_site_name'] = '000-default' +when 'arch' + default['apache']['package'] = 'apache' + default['apache']['perl_pkg'] = 'perl' + # default['apache']['apachectl'] = '/usr/sbin/apachectl' + default['apache']['dir'] = '/etc/httpd' + default['apache']['log_dir'] = '/var/log/httpd' + default['apache']['error_log'] = 'error.log' + default['apache']['access_log'] = 'access.log' + default['apache']['user'] = 'http' + default['apache']['group'] = 'http' + default['apache']['binary'] = '/usr/sbin/httpd' + default['apache']['conf_dir'] = '/etc/httpd' + default['apache']['docroot_dir'] = '/srv/http' + default['apache']['cgibin_dir'] = '/usr/share/httpd/cgi-bin' + default['apache']['icondir'] = '/usr/share/httpd/icons' + default['apache']['cache_dir'] = '/var/cache/httpd' + default['apache']['run_dir'] = '/var/run/httpd' + default['apache']['lock_dir'] = '/var/run/httpd' + default['apache']['pid_file'] = '/var/run/httpd/httpd.pid' + default['apache']['lib_dir'] = '/usr/lib/httpd' + default['apache']['libexec_dir'] = "#{node['apache']['lib_dir']}/modules" +when 'freebsd' + if node['apache']['version'] == '2.4' + default['apache']['package'] = 'apache24' + default['apache']['dir'] = '/usr/local/etc/apache24' + default['apache']['conf_dir'] = '/usr/local/etc/apache24' + default['apache']['docroot_dir'] = '/usr/local/www/apache24/data' + default['apache']['cgibin_dir'] = '/usr/local/www/apache24/cgi-bin' + default['apache']['icondir'] = '/usr/local/www/apache24/icons' + default['apache']['cache_dir'] = '/var/cache/apache24' + default['apache']['run_dir'] = '/var/run' + default['apache']['lock_dir'] = '/var/run' + default['apache']['lib_dir'] = '/usr/local/libexec/apache24' + else + default['apache']['package'] = 'apache22' + default['apache']['dir'] = '/usr/local/etc/apache22' + default['apache']['conf_dir'] = '/usr/local/etc/apache22' + default['apache']['docroot_dir'] = '/usr/local/www/apache22/data' + default['apache']['cgibin_dir'] = '/usr/local/www/apache22/cgi-bin' + default['apache']['icondir'] = '/usr/local/www/apache22/icons' + default['apache']['cache_dir'] = '/var/cache/apache22' + default['apache']['run_dir'] = '/var/run' + default['apache']['lock_dir'] = '/var/run' + default['apache']['lib_dir'] = '/usr/local/libexec/apache22' + end + default['apache']['devel_package'] = 'httpd-devel' + default['apache']['perl_pkg'] = 'perl5' + default['apache']['apachectl'] = '/usr/local/sbin/apachectl' + default['apache']['pid_file'] = '/var/run/httpd.pid' + default['apache']['log_dir'] = '/var/log' + default['apache']['error_log'] = 'httpd-error.log' + default['apache']['access_log'] = 'httpd-access.log' + default['apache']['root_group'] = 'wheel' + default['apache']['user'] = 'www' + default['apache']['group'] = 'www' + default['apache']['binary'] = '/usr/local/sbin/httpd' + default['apache']['libexec_dir'] = node['apache']['lib_dir'] +else + default['apache']['package'] = 'apache2' + default['apache']['devel_package'] = 'apache2-dev' + default['apache']['perl_pkg'] = 'perl' + default['apache']['dir'] = '/etc/apache2' + default['apache']['log_dir'] = '/var/log/apache2' + default['apache']['error_log'] = 'error.log' + default['apache']['access_log'] = 'access.log' + default['apache']['user'] = 'www-data' + default['apache']['group'] = 'www-data' + default['apache']['binary'] = '/usr/sbin/apache2' + default['apache']['conf_dir'] = '/etc/apache2' + default['apache']['docroot_dir'] = '/var/www' + default['apache']['cgibin_dir'] = '/usr/lib/cgi-bin' + default['apache']['icondir'] = '/usr/share/apache2/icons' + default['apache']['cache_dir'] = '/var/cache/apache2' + default['apache']['run_dir'] = 'logs' + default['apache']['lock_dir'] = 'logs' + default['apache']['pid_file'] = 'logs/httpd.pid' + default['apache']['lib_dir'] = '/usr/lib/apache2' + default['apache']['libexec_dir'] = "#{node['apache']['lib_dir']}/modules" +end + +### +# These settings need the unless, since we want them to be tunable, +# and we don't want to override the tunings. +### + +# General settings +if node['apache']['service_name'].nil? + default['apache']['service_name'] = node['apache']['package'] +end +default['apache']['listen_addresses'] = %w(*) +default['apache']['listen_ports'] = %w(80) +default['apache']['contact'] = 'ops@example.com' +default['apache']['timeout'] = 300 +default['apache']['keepalive'] = 'On' +default['apache']['keepaliverequests'] = 100 +default['apache']['keepalivetimeout'] = 5 +default['apache']['locale'] = 'C' +default['apache']['sysconfig_additional_params'] = {} +default['apache']['default_site_enabled'] = false +default['apache']['default_site_port'] = '80' +default['apache']['access_file_name'] = '.htaccess' + +# Security +default['apache']['servertokens'] = 'Prod' +default['apache']['serversignature'] = 'On' +default['apache']['traceenable'] = 'Off' + +# mod_auth_openids +default['apache']['allowed_openids'] = [] + +# mod_status Allow list, space seprated list of allowed entries. +default['apache']['status_allow_list'] = '127.0.0.1 ::1' + +# mod_status ExtendedStatus, set to 'true' to enable +default['apache']['ext_status'] = false + +# mod_info Allow list, space seprated list of allowed entries. +default['apache']['info_allow_list'] = '127.0.0.1 ::1' + +# Supported mpm list +default['apache']['mpm_support'] = %w(prefork worker event) + +# Prefork Attributes +default['apache']['prefork']['startservers'] = 16 +default['apache']['prefork']['minspareservers'] = 16 +default['apache']['prefork']['maxspareservers'] = 32 +default['apache']['prefork']['serverlimit'] = 256 +default['apache']['prefork']['maxrequestworkers'] = 256 +default['apache']['prefork']['maxconnectionsperchild'] = 10_000 + +# Worker Attributes +default['apache']['worker']['startservers'] = 4 +default['apache']['worker']['serverlimit'] = 16 +default['apache']['worker']['minsparethreads'] = 64 +default['apache']['worker']['maxsparethreads'] = 192 +default['apache']['worker']['threadlimit'] = 192 +default['apache']['worker']['threadsperchild'] = 64 +default['apache']['worker']['maxrequestworkers'] = 1024 +default['apache']['worker']['maxconnectionsperchild'] = 0 + +# Event Attributes +default['apache']['event']['startservers'] = 4 +default['apache']['event']['serverlimit'] = 16 +default['apache']['event']['minsparethreads'] = 64 +default['apache']['event']['maxsparethreads'] = 192 +default['apache']['event']['threadlimit'] = 192 +default['apache']['event']['threadsperchild'] = 64 +default['apache']['event']['maxrequestworkers'] = 1024 +default['apache']['event']['maxconnectionsperchild'] = 0 + +# mod_proxy settings +default['apache']['proxy']['require'] = 'all denied' +default['apache']['proxy']['order'] = 'deny,allow' +default['apache']['proxy']['deny_from'] = 'all' +default['apache']['proxy']['allow_from'] = 'none' + +# Default modules to enable via include_recipe +default['apache']['default_modules'] = %w( + status alias auth_basic authn_core authn_file authz_core authz_groupfile + authz_host authz_user autoindex dir env mime negotiation setenvif +) + +%w(log_config logio).each do |log_mod| + default['apache']['default_modules'] << log_mod if %w(rhel fedora suse arch freebsd).include?(node['platform_family']) +end + +if node['apache']['version'] == '2.4' + %w(unixd).each do |unix_mod| + default['apache']['default_modules'] << unix_mod if %w(rhel fedora suse arch freebsd).include?(node['platform_family']) + end + + unless node['platform'] == 'amazon' + default['apache']['default_modules'] << 'systemd' if %w(rhel fedora).include?(node['platform_family']) + end +end diff --git a/cookbooks/apache2/attributes/mod_auth_cas.rb b/cookbooks/apache2/attributes/mod_auth_cas.rb new file mode 100644 index 0000000..76e9fd6 --- /dev/null +++ b/cookbooks/apache2/attributes/mod_auth_cas.rb @@ -0,0 +1,21 @@ +# +# Cookbook Name:: apache2 +# Attributes:: mod_auth_cas +# +# Copyright 2013, Chef Software, Inc. +# +# 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. +# + +default['apache']['mod_auth_cas']['from_source'] = false +default['apache']['mod_auth_cas']['source_revision'] = 'v1.0.8.1' diff --git a/cookbooks/apache2/attributes/mod_auth_openid.rb b/cookbooks/apache2/attributes/mod_auth_openid.rb new file mode 100644 index 0000000..4436d69 --- /dev/null +++ b/cookbooks/apache2/attributes/mod_auth_openid.rb @@ -0,0 +1,34 @@ +# +# Cookbook Name:: apache2 +# Attributes:: mod_auth_cas +# +# Copyright 2013, Chef Software, Inc. +# +# 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. +# + +default['apache']['mod_auth_openid']['ref'] = 'v0.8' +default['apache']['mod_auth_openid']['version'] = '0.8' +default['apache']['mod_auth_openid']['source_url'] = "https://github.com/bmuller/mod_auth_openid/archive/#{node['apache']['mod_auth_openid']['ref']}.tar.gz" +default['apache']['mod_auth_openid']['cache_dir'] = '/var/cache/mod_auth_openid' +default['apache']['mod_auth_openid']['dblocation'] = "#{node['apache']['mod_auth_openid']['cache_dir']}/mod_auth_openid.db" + +case node['platform_family'] +when 'freebsd' + default['apache']['mod_auth_openid']['configure_flags'] = [ + 'CPPFLAGS=-I/usr/local/include', + 'LDFLAGS=-I/usr/local/lib -lsqlite3' + ] +else + default['apache']['mod_auth_openid']['configure_flags'] = [] +end diff --git a/cookbooks/apache2/attributes/mod_fastcgi.rb b/cookbooks/apache2/attributes/mod_fastcgi.rb new file mode 100644 index 0000000..28865b5 --- /dev/null +++ b/cookbooks/apache2/attributes/mod_fastcgi.rb @@ -0,0 +1,21 @@ +# +# Cookbook Name:: apache2 +# Attributes:: mod_fastcgi +# +# Copyright 2013, Chef Software, Inc. +# +# 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. +# + +default['apache']['mod_fastcgi']['download_url'] = 'http://www.fastcgi.com/dist/mod_fastcgi-current.tar.gz' +default['apache']['mod_fastcgi']['install_method'] = 'package' diff --git a/cookbooks/apache2/attributes/mod_pagespeed.rb b/cookbooks/apache2/attributes/mod_pagespeed.rb new file mode 100644 index 0000000..3c23c93 --- /dev/null +++ b/cookbooks/apache2/attributes/mod_pagespeed.rb @@ -0,0 +1,25 @@ +# +# Cookbook Name:: apache2 +# Attributes:: mod_pagespeed +# +# Copyright 2013, ZOZI +# +# 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. +# + +default['apache2']['mod_pagespeed']['package_link'] = + if node['kernel']['machine'] =~ /^i[36']86$/ + 'https://dl-ssl.google.com/dl/linux/direct/mod-pagespeed-stable_current_i386.deb' + else + 'https://dl-ssl.google.com/dl/linux/direct/mod-pagespeed-stable_current_amd64.deb' + end diff --git a/cookbooks/apache2/attributes/mod_php5.rb b/cookbooks/apache2/attributes/mod_php5.rb new file mode 100644 index 0000000..06aea8b --- /dev/null +++ b/cookbooks/apache2/attributes/mod_php5.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: apache2 +# Attributes:: mod_php5 +# +# Copyright 2014, Viverae, Inc. +# +# 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. + +default['apache']['mod_php5']['install_method'] = 'package' +default['apache']['mod_php5']['so_filename'] = 'libphp5.so' + +if node['platform'] == 'amazon' && node['apache']['version'] == '2.4' + default['apache']['mod_php5']['so_filename'] = 'libphp.so' +end diff --git a/cookbooks/apache2/attributes/mod_ssl.rb b/cookbooks/apache2/attributes/mod_ssl.rb new file mode 100644 index 0000000..061664e --- /dev/null +++ b/cookbooks/apache2/attributes/mod_ssl.rb @@ -0,0 +1,59 @@ +# +# Cookbook Name:: apache2 +# Attributes:: mod_ssl +# +# Copyright 2012-2013, Chef Software, Inc. +# Copyright 2014, Viverae, Inc. +# +# 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. +# + +default['apache']['mod_ssl']['port'] = 443 +default['apache']['mod_ssl']['protocol'] = 'All -SSLv2 -SSLv3' +default['apache']['mod_ssl']['cipher_suite'] = 'EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:RC4!aNULL!eNULL!LOW!3DES!MD5!EXP!PSK!SRP!DSS' +default['apache']['mod_ssl']['honor_cipher_order'] = 'On' +default['apache']['mod_ssl']['insecure_renegotiation'] = 'Off' +default['apache']['mod_ssl']['strict_sni_vhost_check'] = 'Off' +default['apache']['mod_ssl']['session_cache'] = 'shmcb:/var/run/apache2/ssl_scache' +default['apache']['mod_ssl']['session_cache_timeout'] = 300 +default['apache']['mod_ssl']['compression'] = 'Off' +default['apache']['mod_ssl']['use_stapling'] = 'Off' +default['apache']['mod_ssl']['stapling_responder_timeout'] = 5 +default['apache']['mod_ssl']['stapling_return_responder_errors'] = 'Off' +default['apache']['mod_ssl']['stapling_cache'] = 'shmcb:/var/run/ocsp(128000)' +default['apache']['mod_ssl']['pass_phrase_dialog'] = 'builtin' +default['apache']['mod_ssl']['mutex'] = 'file:/var/run/apache2/ssl_mutex' +default['apache']['mod_ssl']['directives'] = {} +default['apache']['mod_ssl']['pkg_name'] = 'mod_ssl' + +case node['platform_family'] +when 'debian' + case node['platform'] + when 'ubuntu' + if node['apache']['version'] == '2.4' + default['apache']['mod_ssl']['pass_phrase_dialog'] = 'exec:/usr/share/apache2/ask-for-passphrase' + end + end +when 'freebsd' + default['apache']['mod_ssl']['session_cache'] = 'shmcb:/var/run/ssl_scache(512000)' + default['apache']['mod_ssl']['mutex'] = 'file:/var/run/ssl_mutex' +when 'rhel', 'fedora', 'suse' + case node['platform'] + when 'amazon' + if node['apache']['version'] == '2.4' + default['apache']['mod_ssl']['pkg_name'] = 'mod24_ssl' + end + end + default['apache']['mod_ssl']['session_cache'] = 'shmcb:/var/cache/mod_ssl/scache(512000)' + default['apache']['mod_ssl']['mutex'] = 'default' +end diff --git a/cookbooks/apache2/definitions/apache_conf.rb b/cookbooks/apache2/definitions/apache_conf.rb new file mode 100644 index 0000000..ec8d77e --- /dev/null +++ b/cookbooks/apache2/definitions/apache_conf.rb @@ -0,0 +1,45 @@ +# +# Cookbook Name:: apache2 +# Definition:: apache_conf +# +# Copyright 2008-2013, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +define :apache_conf, :enable => true do + include_recipe 'apache2::default' + + conf_name = "#{params[:name]}.conf" + params[:conf_path] = params[:conf_path] || "#{node['apache']['dir']}/conf-available" + + file "#{params[:conf_path]}/#{params[:name]}" do + action :delete + end + + template "#{params[:conf_path]}/#{conf_name}" do + source params[:source] || "#{conf_name}.erb" + cookbook params[:cookbook] if params[:cookbook] + owner 'root' + group node['apache']['root_group'] + backup false + mode '0644' + notifies :reload, 'service[apache2]', :delayed + end + + if params[:enable] + apache_config params[:name] do + enable true + end + end +end diff --git a/cookbooks/apache2/definitions/apache_config.rb b/cookbooks/apache2/definitions/apache_config.rb new file mode 100644 index 0000000..b28cc3d --- /dev/null +++ b/cookbooks/apache2/definitions/apache_config.rb @@ -0,0 +1,42 @@ +# +# Cookbook Name:: apache2 +# Definition:: apache_config +# +# Copyright 2008-2013, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +define :apache_config, :enable => true do + include_recipe 'apache2::default' + + conf_name = "#{params[:name]}.conf" + params[:conf_path] = params[:conf_path] || "#{node['apache']['dir']}/conf-available" + + if params[:enable] + execute "a2enconf #{conf_name}" do + command "/usr/sbin/a2enconf #{conf_name}" + notifies :reload, 'service[apache2]', :delayed + not_if do + ::File.symlink?("#{node['apache']['dir']}/conf-enabled/#{conf_name}") && + (::File.exist?(params[:conf_path]) ? ::File.symlink?("#{node['apache']['dir']}/conf-enabled/#{conf_name}") : true) + end + end + else + execute "a2disconf #{conf_name}" do + command "/usr/sbin/a2disconf #{conf_name}" + notifies :reload, 'service[apache2]', :delayed + only_if { ::File.symlink?("#{node['apache']['dir']}/conf-enabled/#{conf_name}") } + end + end +end diff --git a/cookbooks/apache2/definitions/apache_mod.rb b/cookbooks/apache2/definitions/apache_mod.rb new file mode 100644 index 0000000..5d7656d --- /dev/null +++ b/cookbooks/apache2/definitions/apache_mod.rb @@ -0,0 +1,28 @@ +# +# Cookbook Name:: apache2 +# Definition:: apache_mod +# +# Copyright 2008-20013, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +define :apache_mod do + include_recipe 'apache2::default' + + template "#{node['apache']['dir']}/mods-available/#{params[:name]}.conf" do + source "mods/#{params[:name]}.conf.erb" + mode '0644' + notifies :reload, 'service[apache2]', :delayed + end +end diff --git a/cookbooks/apache2/definitions/apache_module.rb b/cookbooks/apache2/definitions/apache_module.rb new file mode 100644 index 0000000..6356020 --- /dev/null +++ b/cookbooks/apache2/definitions/apache_module.rb @@ -0,0 +1,58 @@ +# +# Cookbook Name:: apache2 +# Definition:: apache_module +# +# Copyright 2008-2013, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +define :apache_module, :enable => true, :conf => false, :restart => false do + include_recipe 'apache2::default' + + params[:filename] = params[:filename] || "mod_#{params[:name]}.so" + params[:module_path] = params[:module_path] || "#{node['apache']['libexec_dir']}/#{params[:filename]}" + params[:identifier] = params[:identifier] || "#{params[:name]}_module" + + apache_mod params[:name] if params[:conf] + + file "#{node['apache']['dir']}/mods-available/#{params[:name]}.load" do + content "LoadModule #{params[:identifier]} #{params[:module_path]}\n" + mode '0644' + end + + if params[:enable] + execute "a2enmod #{params[:name]}" do + command "/usr/sbin/a2enmod #{params[:name]}" + if params[:restart] + notifies :restart, 'service[apache2]', :delayed + else + notifies :reload, 'service[apache2]', :delayed + end + not_if do + ::File.symlink?("#{node['apache']['dir']}/mods-enabled/#{params[:name]}.load") && + (::File.exist?("#{node['apache']['dir']}/mods-available/#{params[:name]}.conf") ? ::File.symlink?("#{node['apache']['dir']}/mods-enabled/#{params[:name]}.conf") : true) + end + end + else + execute "a2dismod #{params[:name]}" do + command "/usr/sbin/a2dismod #{params[:name]}" + if params[:restart] + notifies :restart, 'service[apache2]', :delayed + else + notifies :reload, 'service[apache2]', :delayed + end + only_if { ::File.symlink?("#{node['apache']['dir']}/mods-enabled/#{params[:name]}.load") } + end + end +end diff --git a/cookbooks/apache2/definitions/apache_site.rb b/cookbooks/apache2/definitions/apache_site.rb new file mode 100644 index 0000000..866076e --- /dev/null +++ b/cookbooks/apache2/definitions/apache_site.rb @@ -0,0 +1,44 @@ +# +# Cookbook Name:: apache2 +# Definition:: apache_site +# +# Copyright 2008-2013, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +define :apache_site, :enable => true do + include_recipe 'apache2::default' + conf_name = "#{params[:name]}.conf" + + if params[:enable] + execute "a2ensite #{conf_name}" do + command "/usr/sbin/a2ensite #{conf_name}" + notifies :reload, 'service[apache2]', :delayed + not_if do + ::File.symlink?("#{node['apache']['dir']}/sites-enabled/#{conf_name}") || + ::File.symlink?("#{node['apache']['dir']}/sites-enabled/000-#{conf_name}") + end + only_if { ::File.exist?("#{node['apache']['dir']}/sites-available/#{conf_name}") } + end + else + execute "a2dissite #{conf_name}" do + command "/usr/sbin/a2dissite #{conf_name}" + notifies :reload, 'service[apache2]', :delayed + only_if do + ::File.symlink?("#{node['apache']['dir']}/sites-enabled/#{conf_name}") || + ::File.symlink?("#{node['apache']['dir']}/sites-enabled/000-#{conf_name}") + end + end + end +end diff --git a/cookbooks/apache2/definitions/web_app.rb b/cookbooks/apache2/definitions/web_app.rb new file mode 100644 index 0000000..6bb9392 --- /dev/null +++ b/cookbooks/apache2/definitions/web_app.rb @@ -0,0 +1,48 @@ +# +# Cookbook Name:: apache2 +# Definition:: web_app +# +# Copyright 2008-2013, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +define :web_app, :template => 'web_app.conf.erb', :local => false, :enable => true do + application_name = params[:name] + + include_recipe 'apache2::default' + include_recipe 'apache2::mod_rewrite' + include_recipe 'apache2::mod_deflate' + include_recipe 'apache2::mod_headers' + + template "#{node['apache']['dir']}/sites-available/#{application_name}.conf" do + source params[:template] + local params[:local] + owner 'root' + group node['apache']['root_group'] + mode '0644' + cookbook params[:cookbook] if params[:cookbook] + variables( + :application_name => application_name, + :params => params + ) + if ::File.exist?("#{node['apache']['dir']}/sites-enabled/#{application_name}.conf") + notifies :reload, 'service[apache2]', :delayed + end + end + + site_enabled = params[:enable] + apache_site params[:name] do + enable site_enabled + end +end diff --git a/cookbooks/apache2/files/default/apache2_module_conf_generate.pl b/cookbooks/apache2/files/default/apache2_module_conf_generate.pl new file mode 100644 index 0000000..e161fbb --- /dev/null +++ b/cookbooks/apache2/files/default/apache2_module_conf_generate.pl @@ -0,0 +1,41 @@ +#!/usr/bin/perl + +=begin + +Generates Ubuntu style module.load files. + +./apache2_module_conf_generate.pl /usr/lib64/httpd/modules /etc/httpd/mods-available + +ARGV[0] is the apache modules directory, ARGV[1] is where you want 'em. + +=cut + +use File::Find; + +use strict; +use warnings; + +die "Must have '/path/to/modules' and '/path/to/modules.load'" + unless $ARGV[0] && $ARGV[1]; + +find( + { + wanted => sub { + return 1 if $File::Find::name !~ /\.so$/; + my $modfile = $_; + $modfile =~ /(lib|mod_)(.+)\.so$/; + my $modname = $2; + my $filename = "$ARGV[1]/$modname.load"; + unless ( -f $filename ) { + open( FILE, ">", $filename ) or die "Cannot open $filename"; + print FILE "LoadModule " . $modname . "_module $File::Find::name\n"; + close(FILE); + } + }, + follow => 1, + }, + $ARGV[0] +); + +exit 0; + diff --git a/cookbooks/apache2/metadata.json b/cookbooks/apache2/metadata.json new file mode 100644 index 0000000..9171794 --- /dev/null +++ b/cookbooks/apache2/metadata.json @@ -0,0 +1 @@ +{"name":"apache2","version":"3.1.0","description":"Installs and configures all aspects of apache2 using Debian style symlinks with helper definitions","long_description":"apache2 Cookbook\n================\n[![Cookbook Version](https://img.shields.io/cookbook/v/apache2.svg?style=flat)](https://supermarket.chef.io/cookbooks/apache2)\n[![Build Status](https://travis-ci.org/svanzoest-cookbooks/apache2.svg?branch=master)](https://travis-ci.org/svanzoest-cookbooks/apache2)\n[![Dependency Status](http://img.shields.io/gemnasium/svanzoest-cookbooks/apache2.svg?style=flat)](https://gemnasium.com/svanzoest-cookbooks/apache2)\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/svanzoest-cookbooks/apache2?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)\n\nThis cookbook provides a complete Debian/Ubuntu style Apache HTTPD\nconfiguration. Non-Debian based distributions such as Red Hat/CentOS,\nArchLinux and others supported by this cookbook will have a\nconfiguration that mimics Debian/Ubuntu style as it is easier to\nmanage with Chef.\n\nDebian-style Apache configuration uses scripts to manage modules and\nsites (vhosts). The scripts are:\n\n* a2ensite\n* a2dissite\n* a2enmod\n* a2dismod\n* a2enconf\n* a2disconf\n\nThis cookbook ships with templates of these scripts for non\nDebian/Ubuntu platforms. The scripts are used in the __Definitions__\nbelow.\n\nRequirements\n============\n\n## Ohai and Chef:\n\n* Ohai: 0.6.12+\n* Chef: 0.10.10+\n\nAs of v1.2.0, this cookbook makes use of `node['platform_family']` to\nsimplify platform selection logic. This attribute was introduced in\nOhai v0.6.12. The recipe methods were introduced in Chef v0.10.10. If\nyou must run an older version of Chef or Ohai, use [version 1.1.16 of\nthis cookbook](https://supermarket.chef.io/cookbooks/apache2/versions/1.1.16).\n\n## Cookbooks:\n\nThis cookbook has no direct external dependencies.\n\nDepending on your OS configuration and security policy, you may need\nadditional recipes or cookbooks for this cookbook's recipes to\nconverge on the node. In particular, the following Operating System\nsettings may affect the behavior of this cookbook:\n\n* apt cache outdated\n* SELinux enabled\n* IPtables\n* Compile tools\n* 3rd party repositories\n\nOn Ubuntu/Debian, use Opscode's `apt` cookbook to ensure the package\ncache is updated so Chef can install packages, or consider putting\napt-get in your bootstrap process or\n[knife bootstrap template](http://docs.chef.io/knife_bootstrap.html)\n\nOn RHEL, SELinux is enabled by default. The `selinux` cookbook\ncontains a `permissive` recipe that can be used to set SELinux to\n\"Permissive\" state. Otherwise, additional recipes need to be created\nby the user to address SELinux permissions.\n\nThe easiest but **certainly not ideal way** to deal with IPtables is\nto flush all rules. Chef Software does provide an `iptables` cookbook but is\nmigrating from the approach used there to a more robust solution\nutilizing a general \"firewall\" LWRP that would have an \"iptables\"\nprovider. Alternately, you can use ufw, with Opscode's `ufw` and\n`firewall` cookbooks to set up rules. See those cookbooks' READMEs for\ndocumentation.\n\nBuild/compile tools may not be installed on the system by default.\nSome recipes (e.g., `apache2::mod_auth_openid`) build the module from\nsource. Use Opscode's `build-essential` cookbook to get essential\nbuild packages installed.\n\nOn ArchLinux, if you are using the `apache2::mod_auth_openid` recipe,\nyou also need the `pacman` cookbook for the `pacman_aur` LWRP. Put\n`recipe[pacman]` on the node's expanded run list (on the node or in a\nrole). This is not an explicit dependency because it is only required\nfor this single recipe and platform; the pacman default recipe\nperforms `pacman -Sy` to keep pacman's package cache updated.\n\n## Platforms:\n\nThe following platforms and versions are tested and supported using\n[test-kitchen](http://kitchen.ci/)\n\n* Ubuntu 12.04, 14.04\n* Debian 7.6\n* CentOS 6.5, 7.0\n\nThe following platform families are supported in the code, and are\nassumed to work based on the successful testing on Ubuntu and CentOS.\n\n* Red Hat (rhel)\n* Fedora\n* Amazon Linux\n\nThe following platforms are also supported in the code, have been\ntested manually but are not tested under test-kitchen.\n\n* SUSE/OpenSUSE\n* ArchLinux\n* FreeBSD\n\n### Notes for RHEL Family:\n\nOn Red Hat Enterprise Linux and derivatives, the EPEL repository may\nbe necessary to install packages used in certain recipes. The\n`apache2::default` recipe, however, does not require any additional\nrepositories. Opscode's `yum-epel` cookbook can be used to add the\nEPEL repository. See __Examples__ for more information.\n\n### Notes for FreeBSD:\n\nVersion 2.0 has been had some basic testing against FreeBSD 10.0 using\nChef 11.14.2 which has support for pkgng (CHEF-4637).\n\nTests\n=====\n\nThis cookbook in the\n[source repository](https://github.com/svanzoest-cookbooks/apache2/)\ncontains chefspec, serverspec and cucumber tests. This is an initial proof of\nconcept that will be fleshed out with more supporting infrastructure\nat a future time.\n\nPlease see the CONTRIBUTING file for information on how to add tests\nfor your contributions.\n\nAttributes\n==========\n\nThis cookbook uses many attributes, broken up into a few different\nkinds.\n\nPlatform specific\n-----------------\n\nIn order to support the broadest number of platforms, several\nattributes are determined based on the node's platform. See the\nattributes/default.rb file for default values in the case statement at\nthe top of the file.\n\n* `node['apache']['package']` - Package name for Apache2\n* `node['apache']['perl_pkg']` - Package name for Perl\n* `node['apache']['dir']` - Location for the Apache configuration\n* `node['apache']['log_dir']` - Location for Apache logs\n* `node['apache']['error_log']` - Location for the default error log\n* `node['apache']['access_log']` - Location for the default access log\n* `node['apache']['user']` - User Apache runs as\n* `node['apache']['group']` - Group Apache runs as\n* `node['apache']['binary']` - Apache httpd server daemon\n* `node['apache']['conf_dir']` - Location for the main config file (e.g apache2.conf or httpd.conf)\n* `node['apache']['docroot_dir']` - Location for docroot\n* `node['apache']['cgibin_dir']` - Location for cgi-bin\n* `node['apache']['icondir']` - Location for icons\n* `node['apache']['cache_dir']` - Location for cached files used by Apache itself or recipes\n* `node['apache']['pid_file']` - Location of the PID file for Apache httpd\n* `node['apache']['lib_dir']` - Location for shared libraries\n* `node['apache']['default_site_enabled']` - Default site enabled. Default is false.\n* `node['apache']['ext_status']` - if true, enables ExtendedStatus for `mod_status`\n* `node['apache']['locale'] - Locale to set in sysconfig or envvars and used for subprocesses and modules (like mod_dav and mod_wsgi). On debian systems Uses system-local if set to 'system', defaults to 'C'.\n\nGeneral settings\n----------------\n\nThese are general settings used in recipes and templates. Default\nvalues are noted.\n\n* `node['apache']['version']` - Specifing 2.4 triggers apache 2.4 support. If the platform is known during our test to install 2.4 by default, it will be set to 2.4 for you. Otherwise it falls back to 2.2. This value should be specified as a string.\n* `node['apache']['listen_addresses']` - Addresses that httpd should listen on. Default is any (\"*\").\n* `node['apache']['listen_ports']` - Ports that httpd should listen on. Default is port 80.\n* `node['apache']['contact']` - Value for ServerAdmin directive. Default \"ops@example.com\".\n* `node['apache']['timeout']` - Value for the Timeout directive. Default is 300.\n* `node['apache']['keepalive']` - Value for the KeepAlive directive. Default is On.\n* `node['apache']['keepaliverequests']` - Value for MaxKeepAliveRequests. Default is 100.\n* `node['apache']['keepalivetimeout']` - Value for the KeepAliveTimeout directive. Default is 5.\n* `node['apache']['sysconfig_additional_params']` - Additionals variables set in sysconfig file. Default is empty.\n* `node['apache']['default_modules']` - Array of module names. Can take \"mod_FOO\" or \"FOO\" as names, where FOO is the apache module, e.g. \"`mod_status`\" or \"`status`\".\n* `node['apache']['mpm']` - With apache.version 2.4, specifies what Multi-Processing Module to enable. Default is \"prefork\".\n\nThe modules listed in `default_modules` will be included as recipes in `recipe[apache::default]`.\n\nPrefork attributes\n------------------\n\nPrefork attributes are used for tuning the Apache HTTPD [prefork MPM](http://httpd.apache.org/docs/current/mod/prefork.html) configuration.\n\n* `node['apache']['prefork']['startservers']` - initial number of server processes to start. Default is 16.\n* `node['apache']['prefork']['minspareservers']` - minimum number of spare server processes. Default 16.\n* `node['apache']['prefork']['maxspareservers']` - maximum number of spare server processes. Default 32.\n* `node['apache']['prefork']['serverlimit']` - upper limit on configurable server processes. Default 400.\n* `node['apache']['prefork']['maxrequestworkers']` - Maximum number of connections that will be processed simultaneously\n* `node['apache']['prefork']['maxconnectionsperchild']` - Maximum number of request a child process will handle. Default 10000.\n\nWorker attributes\n-----------------\n\nWorker attributes are used for tuning the Apache HTTPD [worker MPM](http://httpd.apache.org/docs/current/mod/worker.html)\nconfiguration.\n\n* `node['apache']['worker']['startservers']` - Initial number of server processes to start. Default 4\n* `node['apache']['worker']['serverlimit']` - Upper limit on configurable server processes. Default 16.\n* `node['apache']['worker']['minsparethreads']` - Minimum number of spare worker threads. Default 64\n* `node['apache']['worker']['maxsparethreads']` - Maximum number of spare worker threads. Default 192.\n* `node['apache']['worker']['maxrequestworkers']` - Maximum number of simultaneous connections. Default 1024.\n* `node['apache']['worker']['maxconnectionsperchild']` - Limit on the number of connections that an individual child server will handle during its life.\n\nEvent attributes\n----------------\n\nEvent attributes are used for tuning the Apache HTTPD [event MPM](http://httpd.apache.org/docs/current/mod/event.html)\nconfiguration.\n\n* `node['apache']['event']['startservers']` - Initial number of child server processes created at startup. Default 4.\n* `node['apache']['event']['serverlimit']` - Upper limit on configurable number of processes. Default 16.\n* `node['apache']['event']['minsparethreads']` - Minimum number of spare worker threads. Default 64\n* `node['apache']['event']['maxsparethreads']` - Maximum number of spare worker threads. Default 192.\n* `node['apache']['event']['threadlimit']` - Upper limit on the configurable number of threads per child process. Default 192.\n* `node['apache']['event']['threadsperchild']` - Number of threads created by each child process. Default 64.\n* `node['apache']['event']['maxrequestworkers']` - Maximum number of connections that will be processed simultaneously.\n* `node['apache']['event']['maxconnectionsperchild']` - Limit on the number of connections that an individual child server will handle during its life.\n\nOther/Unsupported MPM\n---------------------\n\nTo use the cookbook with an unsupported mpm (other than prefork, event or worker):\n\n* set `node['apache']['mpm']` to the name of the module (e.g. `itk`)\n* in your cookbook, after `include_recipe 'apache2'` use the `apache_module` definition to enable/disable the required module(s)\n\n\nmod\\_auth\\_openid attributes\n----------------------------\n\nThe following attributes are in the `attributes/mod_auth_openid.rb`\nfile. Like all Chef attributes files, they are loaded as well, but\nthey're logistically unrelated to the others, being specific to the\n`mod_auth_openid` recipe.\n\n* `node['apache']['mod_auth_openid']['checksum']` - sha256sum of the tarball containing the source.\n* `node['apache']['mod_auth_openid']['ref']` - Any sha, tag, or branch found from https://github.com/bmuller/mod_auth_openid\n* `node['apache']['mod_auth_openid']['version']` - directory name version within the tarball\n* `node['apache']['mod_auth_openid']['cache_dir']` - the cache directory is where the sqlite3 database is stored. It is separate so it can be managed as a directory resource.\n* `node['apache']['mod_auth_openid']['dblocation']` - filename of the sqlite3 database used for directive `AuthOpenIDDBLocation`, stored in the `cache_dir` by default.\n* `node['apache']['mod_auth_openid']['configure_flags']` - optional array of configure flags passed to the `./configure` step in the compilation of the module.\n\nmod\\_ssl attributes\n-------------------\n\nFor general information on this attributes see http://httpd.apache.org/docs/current/mod/mod_ssl.html\n\n* `node['apache']['mod_ssl']['cipher_suite']` - sets the SSLCiphersuite value to the specified string. The default is\n considered \"sane\" but you may need to change it for your local security policy, e.g. if you have PCI-DSS requirements. Additional\n commentary on the\n [original pull request](https://github.com/svanzoest-cookbooks/apache2/pull/15#commitcomment-1605406).\n* `node['apache']['mod_ssl']['honor_cipher_order']` - Option to prefer the server's cipher preference order. Default 'On'.\n* `node['apache']['mod_ssl']['insecure_renegotiation']` - Option to enable support for insecure renegotiation. Default 'Off'.\n* `node['apache']['mod_ssl']['strict_sni_vhost_check']` - Whether to allow non-SNI clients to access a name-based virtual host. Default 'Off'.\n* `node['apache']['mod_ssl']['session_cache']` - Configures the OCSP stapling cache. Default `shmcb:/var/run/apache2/ssl_scache`\n* `node['apache']['mod_ssl']['session_cache_timeout']` - Number of seconds before an SSL session expires in the Session Cache. Default 300.\n* `node['apache']['mod_ssl']['compression']` - \tEnable compression on the SSL level. Default 'Off'.\n* `node['apache']['mod_ssl']['use_stapling']` - Enable stapling of OCSP responses in the TLS handshake. Default 'Off'.\n* `node['apache']['mod_ssl']['stapling_responder_timeout']` - \tTimeout for OCSP stapling queries. Default 5\n* `node['apache']['mod_ssl']['stapling_return_responder_errors']` - Pass stapling related OCSP errors on to client. Default 'Off'\n* `node['apache']['mod_ssl']['stapling_cache']` - Configures the OCSP stapling cache. Default `shmcb:/var/run/ocsp(128000)`\n* `node['apache']['mod_ssl']['pass_phrase_dialog']` - Configures SSLPassPhraseDialog. Default `builtin`\n* `node['apache']['mod_ssl']['mutex']` - Configures SSLMutex. Default `file:/var/run/apache2/ssl_mutex`\n* `node['apache']['mod_ssl']['directives']` - Hash for add any custom directive.\n\nFor more information on these directives and how to best secure your site see\n- https://bettercrypto.org/\n- https://wiki.mozilla.org/Security/Server_Side_TLS\n- https://www.insecure.ws/linux/apache_ssl.html\n- https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/\n- https://istlsfastyet.com/\n- https://www.ssllabs.com/projects/best-practices/\n\nRecipes\n=======\n\nMost of the recipes in the cookbook are for enabling Apache modules.\nWhere additional configuration or behavior is used, it is documented\nbelow in more detail.\n\nThe following recipes merely enable the specified module: `mod_alias`,\n`mod_auth_basic`, `mod_auth_digest`, `mod_authn_file`, `mod_authnz_ldap`,\n`mod_authz_default`, `mod_authz_groupfile`, `mod_authz_host`,\n`mod_authz_user`, `mod_autoindex`, `mod_cgi`, `mod_dav_fs`,\n`mod_dav_svn`, `mod_deflate`, `mod_dir`, `mod_env`, `mod_expires`,\n`mod_headers`, `mod_ldap`, `mod_log_config`, `mod_mime`,\n`mod_negotiation`, `mod_proxy`, `mod_proxy_ajp`, `mod_proxy_balancer`,\n`mod_proxy_connect`, `mod_proxy_http`, `mod_python`, `mod_rewrite`,\n`mod_setenvif`, `mod_status`, `mod_wsgi`, `mod_xsendfile`.\n\nOn RHEL Family distributions, certain modules ship with a config file\nwith the package. The recipes here may delete those configuration\nfiles to ensure they don't conflict with the settings from the\ncookbook, which will use per-module configuration in\n`/etc/httpd/mods-enabled`.\n\ndefault\n-------\n\nThe default recipe does a number of things to set up Apache HTTPd. It\nalso includes a number of modules based on the attribute\n`node['apache']['default_modules']` as recipes.\n\nmod\\_auth\\_cas\n--------------\n\nThis recipe installs the proper package and enables the `auth_cas`\nmodule. It can install from source or package. Package is the default,\nset the attribute `node['apache']['mod_auth_cas']['from_source']` to\ntrue to enable source installation. Modify the version to install by\nchanging the attribute\n`node['apache']['mod_auth_cas']['source_revision']`. It is a version\ntag by default, but could be master, or another tag, or branch.\n\nThe module configuration is written out with the `CASCookiePath` set,\notherwise an error loading the module may cause Apache to not start.\n\n**Note**: This recipe does not work on EL 6 platforms unless\nepel-testing repository is enabled (outside the scope of this\ncookbook), or the package version 1.0.8.1-3.el6 or higher is otherwise\navailable to the system due to this bug:\n\nhttps://bugzilla.redhat.com/show_bug.cgi?format=multiple&id=708550\n\nmod\\_auth\\_openid\n-----------------\n\n**Changed via COOK-915**\n\nThis recipe compiles the module from source. In addition to\n`build-essential`, some other packages are included for installation\nlike the GNU C++ compiler and development headers.\n\nTo use the module in your own cookbooks to authenticate systems using\nOpenIDs, specify an array of OpenIDs that are allowed to authenticate\nwith the attribute `node['apache']['allowed_openids']`. Use the\nfollowing in a vhost to protect with OpenID authentication:\n\n AuthType OpenID require user <%= node['apache']['allowed_openids'].join(' ') %>\n AuthOpenIDDBLocation <%= node['apache']['mod_auth_openid']['dblocation'] %>\n\nChange the DBLocation with the attribute as required; this file is in\na different location than previous versions, see below. It should be a\nsane default for most platforms, though, see\n`attributes/mod_auth_openid.rb`.\n\n### Changes from COOK-915:\n\n* `AuthType OpenID` instead of `AuthOpenIDEnabled On`.\n* `require user` instead of `AuthOpenIDUserProgram`.\n* A bug(?) in `mod_auth_openid` causes it to segfault when attempting\n to update the database file if the containing directory is not\n writable by the HTTPD process owner (e.g., www-data), even if the\n file is writable. In order to not interfere with other settings from\n the default recipe in this cookbook, the db file is moved.\n\nmod\\_fastcgi\n------------\n\nInstall the fastcgi package and enable the module.\n\nOnly work on Debian/Ubuntu\n\nmod\\_fcgid\n----------\n\nInstalls the fcgi package and enables the module. Requires EPEL on\nRHEL family.\n\nOn RHEL family, this recipe will delete the fcgid.conf and on version\n6+, create the /var/run/httpd/mod_fcgid` directory, which prevents the\nemergency error:\n\n [emerg] (2)No such file or directory: mod_fcgid: Can't create shared memory for size XX bytes\n\nmod\\_php5\n--------\n\nSimply installs the appropriate package on Debian, Ubuntu and\nArchLinux.\n\nOn Red Hat family distributions including Fedora, the php.conf that\ncomes with the package is removed. On RHEL platforms less than v6, the\n`php53` package is used.\n\n* `node['apache']['mod_php5']['install_method']` - default `package` can be overridden to avoid package installs.\n\nmod\\_ssl\n--------\n\nBesides installing and enabling `mod_ssl`, this recipe will append\nport 443 to the `node['apache']['listen_ports']` attribute array and\nupdate the ports.conf.\n\nDefinitions\n===========\n\nThe cookbook provides a few definitions. At some point in the future\nthese definitions may be refactored into lightweight resources and\nproviders as suggested by\n[foodcritic rule FC015](http://acrmp.github.com/foodcritic/#FC015).\n\napache\\_config\n------------\n\nSets up configuration file for Apache from a template. The\ntemplate should be in the same cookbook where the definition is used. This is used by the `apache_conf` definition and is not often used directly.\n\nIt will use `a2enconf` and `a2disconf` to control the symlinking of configuration files between `conf-available` and `conf-enabled`.\n\nEnable or disable an Apache config file in\n`#{node['apache']['dir']}/conf-available` by calling `a2enmod` or\n`a2dismod` to manage the symbolic link in\n`#{node['apache']['dir']}/conf-enabled`. These config files should be created in your cookbook, and placed on the system using `apache_conf`\n\n### Parameters:\n\n* `name` - Name of the config enabled or disabled with the `a2enconf` or `a2disconf` scripts.\n* `source` - The location of a template file. The default `name.erb`.\n* `cookbook` - The cookbook in which the configuration template is located (if it is not located in the current cookbook). The default value is the current cookbook.\n* `enable` - Default true, which uses `a2enconf` to enable the config. If false, the config will be disabled with `a2disconf`.\n\n### Examples:\n\nEnable the example config.\n\n``````\n apache_config 'example' do\n enable true\n end\n``````\n\nDisable a module:\n\n``````\n apache_config 'disabled_example' do\n enable false\n end\n``````\n\nSee the recipes directory for many more examples of `apache_config`.\n\napache\\_conf\n------------\n\nWrites conf files to the `conf-available` folder, and passes enabled values to `apache_config`.\n\nThis definition should generally be called over `apache_config`.\n\n### Parameters:\n\n* `name` - Name of the config placed and enabled or disabled with the `a2enconf` or `a2disconf` scripts.\n* `enable` - Default true, which uses `a2enconf` to enable the config. If false, the config will be disabled with `a2disconf`.\n* `conf_path` - path to put the config in if you need to override the default `conf-available`.\n\n### Examples:\n\nPlace and enable the example conf:\n\n``````\n apache_conf 'example' do\n enable true\n end\n``````\n\nPlace and disable (or never enable to begin with) the example conf:\n\n``````\n apache_conf 'example' do\n enable false\n end\n``````\n\nPlace the example conf, which has a different path than the default (conf-*):\n\n``````\n apache_conf 'example' do\n conf_path '/random/example/path'\n enable false\n end\n``````\n\napache\\_mod\n------------\n\nSets up configuration file for an Apache module from a template. The\ntemplate should be in the same cookbook where the definition is used.\nThis is used by the `apache_module` definition and is not often used\ndirectly.\n\nThis will use a template resource to write the module's configuration\nfile in the `mods-available` under the Apache configuration directory\n(`node['apache']['dir']`). This is a platform-dependent location. See\n__apache\\_module__.\n\n### Parameters:\n\n* `name` - Name of the template. When used from the `apache_module`,\n it will use the same name as the module.\n\n### Examples:\n\nCreate `#{node['apache']['dir']}/mods-available/alias.conf`.\n\n``````\n apache_mod \"alias\"\n``````\n\napache\\_module\n--------------\n\nEnable or disable an Apache module in\n`#{node['apache']['dir']}/mods-available` by calling `a2enmod` or\n`a2dismod` to manage the symbolic link in\n`#{node['apache']['dir']}/mods-enabled`. If the module has a\nconfiguration file, a template should be created in the cookbook where\nthe definition is used. See __Examples__.\n\n### Parameters:\n\n* `name` - Name of the module enabled or disabled with the `a2enmod` or `a2dismod` scripts.\n* `identifier` - String to identify the module for the `LoadModule` directive. Not typically needed, defaults to `#{name}_module`\n* `enable` - Default true, which uses `a2enmod` to enable the module. If false, the module will be disabled with `a2dismod`.\n* `conf` - Default false. Set to true if the module has a config file, which will use `apache_mod` for the file.\n* `filename` - specify the full name of the file, e.g.\n\n### Examples:\n\nEnable the ssl module, which also has a configuration template in `templates/default/mods/ssl.conf.erb`.\n\n``````\n apache_module \"ssl\" do\n conf true\n end\n``````\n\nEnable the php5 module, which has a different filename than the module default:\n\n``````\n apache_module \"php5\" do\n filename \"libphp5.so\"\n end\n``````\n\nDisable a module:\n\n``````\n apache_module \"disabled_module\" do\n enable false\n end\n``````\n\nSee the recipes directory for many more examples of `apache_module`.\n\napache\\_site\n------------\n\nEnable or disable a VirtualHost in\n`#{node['apache']['dir']}/sites-available` by calling a2ensite or\na2dissite to manage the symbolic link in\n`#{node['apache']['dir']}/sites-enabled`.\n\nThe template for the site must be managed as a separate resource. To\ncombine the template with enabling a site, see `web_app`.\n\n### Parameters:\n\n* `name` - Name of the site.\n* `enable` - Default true, which uses `a2ensite` to enable the site. If false, the site will be disabled with `a2dissite`.\n\nweb\\_app\n--------\n\nManage a template resource for a VirtualHost site, and enable it with\n`apache_site`. This is commonly done for managing web applications\nsuch as Ruby on Rails, PHP or Django, and the default behavior\nreflects that. However it is flexible.\n\nThis definition includes some recipes to make sure the system is\nconfigured to have Apache and some sane default modules:\n\n* `apache2`\n* `apache2::mod_rewrite`\n* `apache2::mod_deflate`\n* `apache2::mod_headers`\n\nIt will then configure the template (see __Parameters__ and\n__Examples__ below), and enable or disable the site per the `enable`\nparameter.\n\n### Parameters:\n\nCurrent parameters used by the definition:\n\n* `name` - The name of the site. The template will be written to\n `#{node['apache']['dir']}/sites-available/#{params['name']}.conf`\n* `cookbook` - Optional. Cookbook where the source template is. If\n this is not defined, Chef will use the named template in the\n cookbook where the definition is used.\n* `template` - Default `web_app.conf.erb`, source template file.\n* `enable` - Default true. Passed to the `apache_site` definition.\n\nAdditional parameters can be defined when the definition is called in\na recipe, see __Examples__.\n\n### Examples:\n\nThe recommended way to use the `web_app` definition is in a application specific cookbook named \"my_app\".\nThe following example would look for a template named 'web_app.conf.erb' in your cookbook containing\nthe apache httpd directives defining the `VirtualHost` that would serve up \"my_app\".\n\n``````\n web_app \"my_app\" do\n template 'web_app.conf.erb'\n server_name node['my_app']['hostname']\n end\n``````\n\nAll parameters are passed into the template. You can use whatever you\nlike. The apache2 cookbook comes with a `web_app.conf.erb` template as\nan example. The following parameters are used in the template:\n\n* `server_name` - ServerName directive.\n* `server_aliases` - ServerAlias directive. Must be an array of aliases.\n* `docroot` - DocumentRoot directive.\n* `application_name` - Used in RewriteLog directive. Will be set to the `name` parameter.\n* `directory_index` - Allow overriding the default DirectoryIndex setting, optional\n* `directory_options` - Override Options on the docroot, for example to add parameters like Includes or Indexes, optional.\n* `allow_override` - Modify the AllowOverride directive on the docroot to support apps that need .htaccess to modify configuration or require authentication.\n\nTo use the default web_app, for example:\n\n``````\n web_app \"my_site\" do\n server_name node['hostname']\n server_aliases [node['fqdn'], \"my-site.example.com\"]\n docroot \"/srv/www/my_site\"\n cookbook 'apache2'\n end\n``````\n\nThe parameters specified will be used as:\n\n* `@params[:server_name]`\n* `@params[:server_aliases]`\n* `@params[:docroot]`\n\nIn the template. When you write your own, the `@` is significant.\n\nFor more information about Definitions and parameters, see the\n[Chef Wiki](http://docs.chef.io/definitions.html)\n\nUsage\n=====\n\nUsing this cookbook is relatively straightforward. Add the desired\nrecipes to the run list of a node, or create a role. Depending on your\nenvironment, you may have multiple roles that use different recipes\nfrom this cookbook. Adjust any attributes as desired. For example, to\ncreate a basic role for web servers that provide both HTTP and HTTPS:\n\n``````\n % cat roles/webserver.rb\n name \"webserver\"\n description \"Systems that serve HTTP and HTTPS\"\n run_list(\n \"recipe[apache2]\",\n \"recipe[apache2::mod_ssl]\"\n )\n default_attributes(\n \"apache\" => {\n \"listen_ports\" => [\"80\", \"443\"]\n }\n )\n``````\n\nFor examples of using the definitions in your own recipes, see their\nrespective sections above.\n\nLicense and Authors\n===================\n\n* Author:: Adam Jacob \n* Author:: Joshua Timberman \n* Author:: Bryan McLellan \n* Author:: Dave Esposito \n* Author:: David Abdemoulaie \n* Author:: Edmund Haselwanter \n* Author:: Eric Rochester \n* Author:: Jim Browne \n* Author:: Matthew Kent \n* Author:: Nathen Harvey \n* Author:: Ringo De Smet \n* Author:: Sean OMeara \n* Author:: Seth Chisamore \n* Author:: Gilles Devaux \n* Author:: Sander van Zoest \n* Author:: Taylor Price \n\n* Copyright:: 2009-2012, Chef Software, Inc\n* Copyright:: 2011, Atriso\n* Copyright:: 2011, CustomInk, LLC.\n* Copyright:: 2013-2014, OneHealth Solutions, Inc.\n* Copyright:: 2014, Viverae, Inc.\n* Copyright:: 2015, Alexander van Zoest\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","maintainer":"Sander van Zoest","maintainer_email":"sander+cookbooks@vanzoest.com","license":"Apache 2.0","platforms":{"debian":">= 0.0.0","ubuntu":">= 0.0.0","redhat":">= 0.0.0","centos":">= 0.0.0","fedora":">= 0.0.0","amazon":">= 0.0.0","scientific":">= 0.0.0","freebsd":">= 0.0.0","suse":">= 0.0.0","opensuse":">= 0.0.0","arch":">= 0.0.0"},"dependencies":{},"recommendations":{},"suggestions":{},"conflicting":{},"providing":{},"replacing":{},"attributes":{},"groupings":{},"recipes":{}} \ No newline at end of file diff --git a/cookbooks/apache2/recipes/default.rb b/cookbooks/apache2/recipes/default.rb new file mode 100644 index 0000000..697577b --- /dev/null +++ b/cookbooks/apache2/recipes/default.rb @@ -0,0 +1,220 @@ +# +# Cookbook Name:: apache2 +# Recipe:: default +# +# Copyright 2008-2013, Chef Software, Inc. +# Copyright 2014-2015, Alexander van Zoest +# +# 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. +# + +package 'apache2' do + package_name node['apache']['package'] +end + +%w(sites-available sites-enabled mods-available mods-enabled conf-available conf-enabled).each do |dir| + directory "#{node['apache']['dir']}/#{dir}" do + mode '0755' + owner 'root' + group node['apache']['root_group'] + end +end + +%w(default default.conf 000-default 000-default.conf).each do |site| + link "#{node['apache']['dir']}/sites-enabled/#{site}" do + action :delete + not_if { site == "#{node['apache']['default_site_name']}.conf" && node['apache']['default_site_enabled'] } + end + + file "#{node['apache']['dir']}/sites-available/#{site}" do + action :delete + backup false + not_if { site == "#{node['apache']['default_site_name']}.conf" && node['apache']['default_site_enabled'] } + end +end + +directory "#{node['apache']['dir']}/conf.d" do + action :delete + recursive true +end + +directory node['apache']['log_dir'] do + mode '0755' +end + +# perl is needed for the a2* scripts +package node['apache']['perl_pkg'] + +%w(a2ensite a2dissite a2enmod a2dismod a2enconf a2disconf).each do |modscript| + link "/usr/sbin/#{modscript}" do + action :delete + only_if { ::File.symlink?("/usr/sbin/#{modscript}") } + end + + template "/usr/sbin/#{modscript}" do + source "#{modscript}.erb" + mode '0700' + owner 'root' + group node['apache']['root_group'] + action :create + end +end + +unless platform_family?('debian') + cookbook_file '/usr/local/bin/apache2_module_conf_generate.pl' do + source 'apache2_module_conf_generate.pl' + mode '0755' + owner 'root' + group node['apache']['root_group'] + end + + execute 'generate-module-list' do + command "/usr/local/bin/apache2_module_conf_generate.pl #{node['apache']['lib_dir']} #{node['apache']['dir']}/mods-available" + action :nothing + end + + # enable mod_deflate for consistency across distributions + include_recipe 'apache2::mod_deflate' +end + +if platform_family?('freebsd') + + directory "#{node['apache']['dir']}/Includes" do + action :delete + recursive true + end + + directory "#{node['apache']['dir']}/extra" do + action :delete + recursive true + end +end + +if platform_family?('suse') + + directory "#{node['apache']['dir']}/vhosts.d" do + action :delete + recursive true + end + + %w(charset.conv default-vhost.conf default-server.conf default-vhost-ssl.conf errors.conf listen.conf mime.types mod_autoindex-defaults.conf mod_info.conf mod_log_config.conf mod_status.conf mod_userdir.conf mod_usertrack.conf uid.conf).each do |file| + file "#{node['apache']['dir']}/#{file}" do + action :delete + backup false + end + end +end + +%W( + #{node['apache']['dir']}/ssl + #{node['apache']['cache_dir']} +).each do |path| + directory path do + mode '0755' + owner 'root' + group node['apache']['root_group'] + end +end + +directory node['apache']['lock_dir'] do + mode '0755' + if node['platform_family'] == 'debian' && node['apache']['version'] == '2.2' + owner node['apache']['user'] + else + owner 'root' + end + group node['apache']['root_group'] +end + +# Set the preferred execution binary - prefork or worker +template "/etc/sysconfig/#{node['apache']['package']}" do + source 'etc-sysconfig-httpd.erb' + owner 'root' + group node['apache']['root_group'] + mode '0644' + notifies :restart, 'service[apache2]', :delayed + only_if { platform_family?('rhel', 'fedora', 'suse') } +end + +template "#{node['apache']['dir']}/envvars" do + source 'envvars.erb' + owner 'root' + group node['apache']['root_group'] + mode '0644' + notifies :reload, 'service[apache2]', :delayed + only_if { platform_family?('debian') } +end + +template 'apache2.conf' do + if platform_family?('rhel', 'fedora', 'arch', 'freebsd') + path "#{node['apache']['conf_dir']}/httpd.conf" + elsif platform_family?('debian') + path "#{node['apache']['conf_dir']}/apache2.conf" + elsif platform_family?('suse') + path "#{node['apache']['conf_dir']}/httpd.conf" + end + action :create + source 'apache2.conf.erb' + owner 'root' + group node['apache']['root_group'] + mode '0644' + notifies :reload, 'service[apache2]', :delayed +end + +%w(security charset).each do |conf| + apache_conf conf do + enable true + end +end + +apache_conf 'ports' do + enable false + conf_path node['apache']['dir'] +end + +if node['apache']['version'] == '2.4' && !platform_family?('freebsd') + # on freebsd the prefork mpm is staticly compiled in + if node['apache']['mpm_support'].include?(node['apache']['mpm']) + include_recipe "apache2::mpm_#{node['apache']['mpm']}" + else + Chef::Log.warn("apache2: #{node['apache']['mpm']} module is not supported and must be handled separately!") + end +end + +node['apache']['default_modules'].each do |mod| + module_recipe_name = mod =~ /^mod_/ ? mod : "mod_#{mod}" + include_recipe "apache2::#{module_recipe_name}" +end + +if node['apache']['default_site_enabled'] + web_app node['apache']['default_site_name'] do + template 'default-site.conf.erb' + enable node['apache']['default_site_enabled'] + end +end + +service 'apache2' do + service_name node['apache']['service_name'] + case node['platform_family'] + when 'rhel' + restart_command '/sbin/service httpd restart && sleep 1' if node['apache']['version'] == '2.2' + reload_command '/sbin/service httpd graceful' + when 'debian' + provider Chef::Provider::Service::Debian + when 'arch' + service_name 'httpd' + end + supports [:start, :restart, :reload, :status] + action [:enable, :start] + only_if "#{node['apache']['binary']} -t", :environment => { 'APACHE_LOG_DIR' => node['apache']['log_dir'] }, :timeout => 10 +end diff --git a/cookbooks/apache2/recipes/mod_access_compat.rb b/cookbooks/apache2/recipes/mod_access_compat.rb new file mode 100644 index 0000000..bfb6eed --- /dev/null +++ b/cookbooks/apache2/recipes/mod_access_compat.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_access_compat +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'access_compat' diff --git a/cookbooks/apache2/recipes/mod_actions.rb b/cookbooks/apache2/recipes/mod_actions.rb new file mode 100644 index 0000000..98b8f41 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_actions.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_actions +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'actions' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_alias.rb b/cookbooks/apache2/recipes/mod_alias.rb new file mode 100644 index 0000000..60ac195 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_alias.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_alias +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'alias' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_allowmethods.rb b/cookbooks/apache2/recipes/mod_allowmethods.rb new file mode 100644 index 0000000..12f9fde --- /dev/null +++ b/cookbooks/apache2/recipes/mod_allowmethods.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_allowmethods +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'allowmethods' diff --git a/cookbooks/apache2/recipes/mod_apreq2.rb b/cookbooks/apache2/recipes/mod_apreq2.rb new file mode 100644 index 0000000..005c72c --- /dev/null +++ b/cookbooks/apache2/recipes/mod_apreq2.rb @@ -0,0 +1,50 @@ +# +# Cookbook Name:: apache2 +# Recipe:: apreq2 +# +# modified from the python recipe by Jeremy Bingham +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +include_recipe 'apache2::default' + +case node['platform_family'] +when 'debian' + package 'libapache2-mod-apreq2' +when 'suse' + package 'apache2-mod_apreq2' do + notifies :run, 'execute[generate-module-list]', :immediately + end +when 'rhel', 'fedora' + package 'libapreq2' do + notifies :run, 'execute[generate-module-list]', :immediately + end + + # seems that the apreq lib is weirdly broken or something - it needs to be + # loaded as 'apreq', but on RHEL & derivitatives the file needs a symbolic + # link to mod_apreq.so. + link "#{node['apache']['libexec_dir']}/mod_apreq.so" do + to "#{node['apache']['libexec_dir']}/mod_apreq2.so" + only_if "test -f #{node['apache']['libexec_dir']}/mod_apreq2.so" + end +end + +file "#{node['apache']['dir']}/conf.d/apreq.conf" do + action :delete + backup false +end + +apache_module 'apreq' diff --git a/cookbooks/apache2/recipes/mod_asis.rb b/cookbooks/apache2/recipes/mod_asis.rb new file mode 100644 index 0000000..bba792e --- /dev/null +++ b/cookbooks/apache2/recipes/mod_asis.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_asis +# +# Copyright 2008-2009, Chef Software, Inc. +# +# 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. +# + +apache_module 'asis' diff --git a/cookbooks/apache2/recipes/mod_auth_basic.rb b/cookbooks/apache2/recipes/mod_auth_basic.rb new file mode 100644 index 0000000..8906a25 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_auth_basic.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_auth_basic +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'auth_basic' diff --git a/cookbooks/apache2/recipes/mod_auth_cas.rb b/cookbooks/apache2/recipes/mod_auth_cas.rb new file mode 100644 index 0000000..45c9058 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_auth_cas.rb @@ -0,0 +1,68 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_auth_cas +# +# Copyright 2013, Chef Software, Inc. +# +# 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. +# + +include_recipe 'apache2::default' + +if node['apache']['mod_auth_cas']['from_source'] + package node['apache']['devel_package'] + + git '/tmp/mod_auth_cas' do + repository 'git://github.com/Jasig/mod_auth_cas.git' + revision node['apache']['mod_auth_cas']['source_revision'] + notifies :run, 'execute[compile mod_auth_cas]', :immediately + end + + execute 'compile mod_auth_cas' do + command './configure && make && make install' + cwd '/tmp/mod_auth_cas' + not_if "test -f #{node['apache']['libexec_dir']}/mod_auth_cas.so" + end + + template "#{node['apache']['dir']}/mods-available/auth_cas.load" do + source 'mods/auth_cas.load.erb' + owner 'root' + group node['apache']['root_group'] + mode '0644' + end +else + case node['platform_family'] + when 'debian' + package 'libapache2-mod-auth-cas' + + when 'rhel', 'fedora' + yum_package 'mod_auth_cas' do + notifies :run, 'execute[generate-module-list]', :immediately + end + + file "#{node['apache']['dir']}/conf.d/auth_cas.conf" do + action :delete + backup false + end + end +end + +apache_module 'auth_cas' do + conf true +end + +directory "#{node['apache']['cache_dir']}/mod_auth_cas" do + owner node['apache']['user'] + group node['apache']['group'] + mode '0700' +end diff --git a/cookbooks/apache2/recipes/mod_auth_digest.rb b/cookbooks/apache2/recipes/mod_auth_digest.rb new file mode 100644 index 0000000..ca2e094 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_auth_digest.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_auth_digest +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'auth_digest' diff --git a/cookbooks/apache2/recipes/mod_auth_form.rb b/cookbooks/apache2/recipes/mod_auth_form.rb new file mode 100644 index 0000000..b6bfc88 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_auth_form.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_auth_form +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'auth_form' diff --git a/cookbooks/apache2/recipes/mod_auth_openid.rb b/cookbooks/apache2/recipes/mod_auth_openid.rb new file mode 100644 index 0000000..280fba4 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_auth_openid.rb @@ -0,0 +1,122 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_auth_openid +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +openid_dev_pkgs = value_for_platform_family( + 'debian' => %W(automake make g++ #{node['apache']['devel_package']} libopkele-dev libopkele3 libtool), + 'suse' => %W(automake make g++ #{node['apache']['devel_package']} libopkele-dev libopkele3 libtool), + %w(rhel fedora) => %W(gcc-c++ #{node['apache']['devel_package']} curl-devel libtidy libtidy-devel sqlite-devel pcre-devel openssl-devel make libtool), + 'arch' => %w(libopkele), + 'freebsd' => %w(libopkele pcre sqlite3) +) + +make_cmd = value_for_platform_family( + 'freebsd' => { 'default' => 'gmake' }, + 'default' => 'make' +) + +case node['platform_family'] +when 'arch' + package 'tidyhtml' + + pacman_aur openid_dev_pkgs.first do + action [:build, :install] + end +else + openid_dev_pkgs.each do |pkg| + package pkg + end +end + +case node['platform_family'] +when 'rhel', 'fedora' + remote_file "#{Chef::Config['file_cache_path']}/libopkele-2.0.4.tar.gz" do + source 'http://kin.klever.net/dist/libopkele-2.0.4.tar.gz' + mode '0644' + checksum '57a5bc753b7e80c5ece1e5968b2051b0ce7ed9ce4329d17122c61575a9ea7648' + end + + bash 'install libopkele' do + cwd Chef::Config['file_cache_path'] + # Ruby 1.8.6 does not have rpartition, unfortunately + syslibdir = node['apache']['lib_dir'][0..node['apache']['lib_dir'].rindex('/')] + code <<-EOH + tar zxvf libopkele-2.0.4.tar.gz + cd libopkele-2.0.4 && ./configure --prefix=/usr --libdir=#{syslibdir} + #{make_cmd} && #{make_cmd} install + EOH + creates "#{syslibdir}/libopkele.a" + end +end + +version = node['apache']['mod_auth_openid']['version'] +configure_flags = node['apache']['mod_auth_openid']['configure_flags'] + +remote_file "#{Chef::Config['file_cache_path']}/mod_auth_openid-#{version}.tar.gz" do + source node['apache']['mod_auth_openid']['source_url'] + mode '0644' + action :create_if_missing +end + +directory node['apache']['mod_auth_openid']['cache_dir'] do + owner node['apache']['user'] + group node['apache']['group'] + mode '0700' +end + +bash 'untar mod_auth_openid' do + cwd Chef::Config['file_cache_path'] + code <<-EOH + tar zxvf mod_auth_openid-#{version}.tar.gz + EOH + creates "#{Chef::Config['file_cache_path']}/mod_auth_openid-#{version}/src/types.h" +end + +bash 'compile mod_auth_openid' do + cwd "#{Chef::Config['file_cache_path']}/mod_auth_openid-#{version}" + code <<-EOH + ./autogen.sh + ./configure #{configure_flags.join(' ')} + perl -pi -e "s/-i -a -n 'authopenid'/-i -n 'authopenid'/g" Makefile + #{make_cmd} + EOH + creates "#{Chef::Config['file_cache_path']}/mod_auth_openid-#{version}/src/.libs/mod_auth_openid.so" + notifies :run, 'bash[install-mod_auth_openid]', :immediately + not_if "test -f #{Chef::Config['file_cache_path']}/mod_auth_openid-#{version}/src/.libs/mod_auth_openid.so" +end + +bash 'install-mod_auth_openid' do + cwd "#{Chef::Config['file_cache_path']}/mod_auth_openid-#{version}" + code <<-EOH + #{make_cmd} install + EOH + creates "#{node['apache']['libexec_dir']}/mod_auth_openid.so" + notifies :restart, 'service[apache2]' + not_if "test -f #{node['apache']['libexec_dir']}/mod_auth_openid.so" +end + +template "#{node['apache']['dir']}/mods-available/authopenid.load" do + source 'mods/authopenid.load.erb' + owner 'root' + group node['apache']['root_group'] + mode '0644' +end + +apache_module 'authopenid' do + filename 'mod_auth_openid.so' +end diff --git a/cookbooks/apache2/recipes/mod_authn_anon.rb b/cookbooks/apache2/recipes/mod_authn_anon.rb new file mode 100644 index 0000000..21750f5 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authn_anon.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authn_anon +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'authn_anon' diff --git a/cookbooks/apache2/recipes/mod_authn_core.rb b/cookbooks/apache2/recipes/mod_authn_core.rb new file mode 100644 index 0000000..8b4097b --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authn_core.rb @@ -0,0 +1,23 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authn_core +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# +if node['apache']['version'] == '2.4' + apache_module 'authn_core' +else + Chef::Log.info('Ignoring apache2::mod_authn_core. not available until apache 2.4') +end diff --git a/cookbooks/apache2/recipes/mod_authn_dbd.rb b/cookbooks/apache2/recipes/mod_authn_dbd.rb new file mode 100644 index 0000000..281c702 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authn_dbd.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authn_dbd +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'authn_dbd' diff --git a/cookbooks/apache2/recipes/mod_authn_dbm.rb b/cookbooks/apache2/recipes/mod_authn_dbm.rb new file mode 100644 index 0000000..e4c30ff --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authn_dbm.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authn_dbm +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'authn_dbm' diff --git a/cookbooks/apache2/recipes/mod_authn_file.rb b/cookbooks/apache2/recipes/mod_authn_file.rb new file mode 100644 index 0000000..5cb0bff --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authn_file.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authn_file +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'authn_file' diff --git a/cookbooks/apache2/recipes/mod_authn_socache.rb b/cookbooks/apache2/recipes/mod_authn_socache.rb new file mode 100644 index 0000000..9f1c5fe --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authn_socache.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authn_socache +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'authn_socache' diff --git a/cookbooks/apache2/recipes/mod_authnz_ldap.rb b/cookbooks/apache2/recipes/mod_authnz_ldap.rb new file mode 100644 index 0000000..1c7f3ea --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authnz_ldap.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authnz_ldap +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +include_recipe 'apache2::mod_ldap' + +apache_module 'authnz_ldap' diff --git a/cookbooks/apache2/recipes/mod_authz_core.rb b/cookbooks/apache2/recipes/mod_authz_core.rb new file mode 100644 index 0000000..af58f1a --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authz_core.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authz_core +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +if node['apache']['version'] == '2.4' + apache_module 'authz_core' +else + apache_module 'authz_default' +end diff --git a/cookbooks/apache2/recipes/mod_authz_dbd.rb b/cookbooks/apache2/recipes/mod_authz_dbd.rb new file mode 100644 index 0000000..290e5d9 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authz_dbd.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authz_dbd +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'authz_dbd' diff --git a/cookbooks/apache2/recipes/mod_authz_dbm.rb b/cookbooks/apache2/recipes/mod_authz_dbm.rb new file mode 100644 index 0000000..0b3ee9b --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authz_dbm.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authz_dbm +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'authz_dbm' diff --git a/cookbooks/apache2/recipes/mod_authz_default.rb b/cookbooks/apache2/recipes/mod_authz_default.rb new file mode 100644 index 0000000..7a551a0 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authz_default.rb @@ -0,0 +1,21 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authz_default +# +# Copyright 2013, Chef Software, Inc. +# +# 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. +# +# +log 'apache2::mod_authz_default is deprecated in favor of apache2::mod_authz_core. Please adjust your cookbooks' +include_recipe 'apache2::mod_authz_core' diff --git a/cookbooks/apache2/recipes/mod_authz_groupfile.rb b/cookbooks/apache2/recipes/mod_authz_groupfile.rb new file mode 100644 index 0000000..f035838 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authz_groupfile.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authz_groupfile +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'authz_groupfile' diff --git a/cookbooks/apache2/recipes/mod_authz_host.rb b/cookbooks/apache2/recipes/mod_authz_host.rb new file mode 100644 index 0000000..e388190 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authz_host.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authz_host +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'authz_host' diff --git a/cookbooks/apache2/recipes/mod_authz_owner.rb b/cookbooks/apache2/recipes/mod_authz_owner.rb new file mode 100644 index 0000000..4123fc8 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authz_owner.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authz_owner +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'authz_owner' diff --git a/cookbooks/apache2/recipes/mod_authz_user.rb b/cookbooks/apache2/recipes/mod_authz_user.rb new file mode 100644 index 0000000..e646ed4 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_authz_user.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_authz_user +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'authz_user' diff --git a/cookbooks/apache2/recipes/mod_autoindex.rb b/cookbooks/apache2/recipes/mod_autoindex.rb new file mode 100644 index 0000000..da2f6a6 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_autoindex.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_autoindex +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'autoindex' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_buffer.rb b/cookbooks/apache2/recipes/mod_buffer.rb new file mode 100644 index 0000000..939f7d9 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_buffer.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_buffer +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'buffer' diff --git a/cookbooks/apache2/recipes/mod_cache.rb b/cookbooks/apache2/recipes/mod_cache.rb new file mode 100644 index 0000000..9ce0cb9 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_cache.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_cache +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'cache' diff --git a/cookbooks/apache2/recipes/mod_cache_disk.rb b/cookbooks/apache2/recipes/mod_cache_disk.rb new file mode 100644 index 0000000..a26d13e --- /dev/null +++ b/cookbooks/apache2/recipes/mod_cache_disk.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_cache_disk +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'cache_disk' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_cache_socache.rb b/cookbooks/apache2/recipes/mod_cache_socache.rb new file mode 100644 index 0000000..110b0f8 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_cache_socache.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_cache_socache +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'cache_socache' diff --git a/cookbooks/apache2/recipes/mod_cgi.rb b/cookbooks/apache2/recipes/mod_cgi.rb new file mode 100644 index 0000000..7b6f8b4 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_cgi.rb @@ -0,0 +1,26 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_cgi +# +# Copyright 2008-2013, Chef Software, Inc. +# Copyright 2014, Viverae, Inc. +# +# 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. +# + +if node['apache']['mpm'] == 'prefork' + apache_module 'cgi' +else + Chef::Log.warn "apache::mod_cgi. Your MPM #{node['apache']['mpm']} seems to be threaded. Selecting cgid instead of cgi." + apache_module 'cgid' +end diff --git a/cookbooks/apache2/recipes/mod_cgid.rb b/cookbooks/apache2/recipes/mod_cgid.rb new file mode 100644 index 0000000..103f169 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_cgid.rb @@ -0,0 +1,23 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_cgid +# +# Copyright 2013, OneHealth Solutions, Inc. +# Copyright 2014, Viverae, Inc. +# +# 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. +# + +apache_module 'cgid' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_charset_lite.rb b/cookbooks/apache2/recipes/mod_charset_lite.rb new file mode 100644 index 0000000..a1db83c --- /dev/null +++ b/cookbooks/apache2/recipes/mod_charset_lite.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_charset_lite +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'charset_lite' diff --git a/cookbooks/apache2/recipes/mod_cloudflare.rb b/cookbooks/apache2/recipes/mod_cloudflare.rb new file mode 100644 index 0000000..f6ed37e --- /dev/null +++ b/cookbooks/apache2/recipes/mod_cloudflare.rb @@ -0,0 +1,30 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_cloudflare +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apt_repository 'cloudflare' do + uri 'http://pkg.cloudflare.com' + distribution node['lsb']['codename'] + components ['main'] + key 'http://pkg.cloudflare.com/pubkey.gpg' + action :add +end + +package 'libapache2-mod-cloudflare' do + notifies :restart, 'service[apache2]' +end diff --git a/cookbooks/apache2/recipes/mod_data.rb b/cookbooks/apache2/recipes/mod_data.rb new file mode 100644 index 0000000..6ea7c47 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_data.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_data +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'data' diff --git a/cookbooks/apache2/recipes/mod_dav.rb b/cookbooks/apache2/recipes/mod_dav.rb new file mode 100644 index 0000000..2f4f223 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_dav.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_dav +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'dav' diff --git a/cookbooks/apache2/recipes/mod_dav_fs.rb b/cookbooks/apache2/recipes/mod_dav_fs.rb new file mode 100644 index 0000000..cecd43d --- /dev/null +++ b/cookbooks/apache2/recipes/mod_dav_fs.rb @@ -0,0 +1,23 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_dav_fs +# +# Copyright 2011-2013, Atriso +# +# 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. +# + +include_recipe 'apache2::mod_dav' +apache_module 'dav_fs' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_dav_lock.rb b/cookbooks/apache2/recipes/mod_dav_lock.rb new file mode 100644 index 0000000..e73e708 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_dav_lock.rb @@ -0,0 +1,21 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_dav_lock +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +include_recipe 'apache2::mod_dav' +apache_module 'dav_lock' diff --git a/cookbooks/apache2/recipes/mod_dav_svn.rb b/cookbooks/apache2/recipes/mod_dav_svn.rb new file mode 100644 index 0000000..901f167 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_dav_svn.rb @@ -0,0 +1,39 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_dav_svn +# +# Copyright 2008-2009, Chef Software, Inc. +# +# 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. +# + +include_recipe 'apache2::mod_dav' + +package 'libapache2-svn' do + case node['platform_family'] + when 'rhel', 'fedora', 'suse' + package_name 'mod_dav_svn' + else + package_name 'libapache2-svn' + end +end + +case node['platform_family'] +when 'rhel', 'fedora', 'suse' + file "#{node['apache']['dir']}/conf.d/subversion.conf" do + action :delete + backup false + end +end + +apache_module 'dav_svn' diff --git a/cookbooks/apache2/recipes/mod_dbd.rb b/cookbooks/apache2/recipes/mod_dbd.rb new file mode 100644 index 0000000..c5f9f4a --- /dev/null +++ b/cookbooks/apache2/recipes/mod_dbd.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_dbd +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'dbd' diff --git a/cookbooks/apache2/recipes/mod_deflate.rb b/cookbooks/apache2/recipes/mod_deflate.rb new file mode 100644 index 0000000..9ecd619 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_deflate.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_deflate +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'deflate' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_dialup.rb b/cookbooks/apache2/recipes/mod_dialup.rb new file mode 100644 index 0000000..75d271d --- /dev/null +++ b/cookbooks/apache2/recipes/mod_dialup.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_dialup +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'dialup' diff --git a/cookbooks/apache2/recipes/mod_dir.rb b/cookbooks/apache2/recipes/mod_dir.rb new file mode 100644 index 0000000..86fdf9a --- /dev/null +++ b/cookbooks/apache2/recipes/mod_dir.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_dir +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'dir' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_dump_io.rb b/cookbooks/apache2/recipes/mod_dump_io.rb new file mode 100644 index 0000000..46015e4 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_dump_io.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_dump_io +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'dumpio' diff --git a/cookbooks/apache2/recipes/mod_echo.rb b/cookbooks/apache2/recipes/mod_echo.rb new file mode 100644 index 0000000..b8aeb21 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_echo.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_echo +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'echo' diff --git a/cookbooks/apache2/recipes/mod_env.rb b/cookbooks/apache2/recipes/mod_env.rb new file mode 100644 index 0000000..b5b30fc --- /dev/null +++ b/cookbooks/apache2/recipes/mod_env.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_env +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'env' diff --git a/cookbooks/apache2/recipes/mod_expires.rb b/cookbooks/apache2/recipes/mod_expires.rb new file mode 100644 index 0000000..5c475e2 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_expires.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_expires +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'expires' diff --git a/cookbooks/apache2/recipes/mod_ext_filter.rb b/cookbooks/apache2/recipes/mod_ext_filter.rb new file mode 100644 index 0000000..c8b27bb --- /dev/null +++ b/cookbooks/apache2/recipes/mod_ext_filter.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_ext_filter +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'ext_filter' diff --git a/cookbooks/apache2/recipes/mod_fastcgi.rb b/cookbooks/apache2/recipes/mod_fastcgi.rb new file mode 100644 index 0000000..9eb4382 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_fastcgi.rb @@ -0,0 +1,64 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_fastcgi +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +if platform_family?('debian') + if node['apache']['mod_fastcgi']['install_method'] == 'source' + package 'build-essential' + package node['apache']['devel_package'] + else + package 'libapache2-mod-fastcgi' + end +elsif platform_family?('rhel') + %W(gcc make libtool #{node['apache']['devel_package']} apr-devel apr).each do |package| + yum_package package do + action :upgrade + end + end +end + +if platform_family?('rhel') || (platform_family?('debian') && node['apache']['mod_fastcgi']['install_method'] == 'source') + src_filepath = "#{Chef::Config['file_cache_path']}/fastcgi.tar.gz" + remote_file 'download fastcgi source' do + source node['apache']['mod_fastcgi']['download_url'] + path src_filepath + backup false + end + + if platform_family?('debian') + top_dir = node['apache']['build_dir'] + else + top_dir = node['apache']['lib_dir'] + end + include_recipe 'apache2::default' + bash 'compile fastcgi source' do + notifies :run, 'execute[generate-module-list]', :immediately if platform_family?('rhel') + not_if "test -f #{node['apache']['dir']}/mods-available/fastcgi.conf" + cwd ::File.dirname(src_filepath) + code <<-EOH + tar zxf #{::File.basename(src_filepath)} && + cd mod_fastcgi-* && + cp Makefile.AP2 Makefile && + make top_dir=#{top_dir} && make install top_dir=#{top_dir} + EOH + end +end + +apache_module 'fastcgi' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_fcgid.rb b/cookbooks/apache2/recipes/mod_fcgid.rb new file mode 100644 index 0000000..ddfc52e --- /dev/null +++ b/cookbooks/apache2/recipes/mod_fcgid.rb @@ -0,0 +1,55 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_fcgid +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +if platform_family?('debian') + package 'libapache2-mod-fcgid' +elsif platform_family?('rhel', 'fedora') + package 'mod_fcgid' do + notifies :run, 'execute[generate-module-list]', :immediately + end + + file "#{node['apache']['dir']}/conf.d/fcgid.conf" do + action :delete + backup false + end + + directory '/var/run/httpd/mod_fcgid' do + owner node['apache']['user'] + group node['apache']['group'] + recursive true + only_if { node['platform_version'].to_i >= 6 } + end +elsif platform_family?('suse') + apache_lib_path = node['apache']['lib_dir'] + + package node['apache']['devel_package'] + + bash 'install-fcgid' do + code <<-EOH +(cd #{Chef::Config['file_cache_path']}; wget http://superb-east.dl.sourceforge.net/sourceforge/mod-fcgid/mod_fcgid.2.2.tgz) +(cd #{Chef::Config['file_cache_path']}; tar zxvf mod_fcgid.2.2.tgz) +(cd #{Chef::Config['file_cache_path']}; perl -pi -e 's!/usr/local/apache2!#{apache_lib_path}!g' ./mod_fcgid.2.2/Makefile) +(cd #{Chef::Config['file_cache_path']}/mod_fcgid.2.2; make install) +EOH + end +end + +apache_module 'fcgid' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_file_cache.rb b/cookbooks/apache2/recipes/mod_file_cache.rb new file mode 100644 index 0000000..984554e --- /dev/null +++ b/cookbooks/apache2/recipes/mod_file_cache.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_file_cache +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'file_cache' diff --git a/cookbooks/apache2/recipes/mod_filter.rb b/cookbooks/apache2/recipes/mod_filter.rb new file mode 100644 index 0000000..a9b73c5 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_filter.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_filter +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'filter' diff --git a/cookbooks/apache2/recipes/mod_headers.rb b/cookbooks/apache2/recipes/mod_headers.rb new file mode 100644 index 0000000..4ca3628 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_headers.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_headers +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'headers' diff --git a/cookbooks/apache2/recipes/mod_heartbeat.rb b/cookbooks/apache2/recipes/mod_heartbeat.rb new file mode 100644 index 0000000..1e7f8ca --- /dev/null +++ b/cookbooks/apache2/recipes/mod_heartbeat.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_heartbeat +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'heartbeat' diff --git a/cookbooks/apache2/recipes/mod_heartmonitor.rb b/cookbooks/apache2/recipes/mod_heartmonitor.rb new file mode 100644 index 0000000..c017be2 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_heartmonitor.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_heartmonitor +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'heartmonitor' diff --git a/cookbooks/apache2/recipes/mod_include.rb b/cookbooks/apache2/recipes/mod_include.rb new file mode 100644 index 0000000..ffe57a1 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_include.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_include +# +# Copyright 2012-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'include' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_info.rb b/cookbooks/apache2/recipes/mod_info.rb new file mode 100644 index 0000000..c30f704 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_info.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_info +# +# Copyright 2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'info' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_jk.rb b/cookbooks/apache2/recipes/mod_jk.rb new file mode 100644 index 0000000..5d468c5 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_jk.rb @@ -0,0 +1,30 @@ +# +# Cookbook Name:: apache2 +# Recipe:: jk +# +# Copyright 2013, Mike Babineau +# Copyright 2013, Chef Software, Inc. +# +# 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. +# + +package 'libapache2-mod-jk' do + case node['platform_family'] + when 'rhel', 'fedora', 'suse' + package_name 'mod_jk' + else + package_name 'libapache2-mod-jk' + end +end + +apache_module 'jk' diff --git a/cookbooks/apache2/recipes/mod_lbmethod_bybusyness.rb b/cookbooks/apache2/recipes/mod_lbmethod_bybusyness.rb new file mode 100644 index 0000000..ebb1b2f --- /dev/null +++ b/cookbooks/apache2/recipes/mod_lbmethod_bybusyness.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_lbmethod_bybusyness +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'lbmethod_bybusyness' diff --git a/cookbooks/apache2/recipes/mod_lbmethod_byrequests.rb b/cookbooks/apache2/recipes/mod_lbmethod_byrequests.rb new file mode 100644 index 0000000..caa4778 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_lbmethod_byrequests.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_lbmethod_byrequests +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'lbmethod_byrequests' diff --git a/cookbooks/apache2/recipes/mod_lbmethod_bytraffic.rb b/cookbooks/apache2/recipes/mod_lbmethod_bytraffic.rb new file mode 100644 index 0000000..0960078 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_lbmethod_bytraffic.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_lbmethod_bytraffic +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'lbmethod_bytraffic' diff --git a/cookbooks/apache2/recipes/mod_lbmethod_heartbeat.rb b/cookbooks/apache2/recipes/mod_lbmethod_heartbeat.rb new file mode 100644 index 0000000..0d8064a --- /dev/null +++ b/cookbooks/apache2/recipes/mod_lbmethod_heartbeat.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_lbmethod_heartbeat +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'lbmethod_heartbeat' diff --git a/cookbooks/apache2/recipes/mod_ldap.rb b/cookbooks/apache2/recipes/mod_ldap.rb new file mode 100644 index 0000000..2425f5d --- /dev/null +++ b/cookbooks/apache2/recipes/mod_ldap.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_ldap +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'ldap' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_log_config.rb b/cookbooks/apache2/recipes/mod_log_config.rb new file mode 100644 index 0000000..2f7633e --- /dev/null +++ b/cookbooks/apache2/recipes/mod_log_config.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_log_config +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +if platform_family?('rhel', 'fedora', 'suse', 'arch', 'freebsd') + apache_module 'log_config' +else + include_recipe 'apache2::default' +end diff --git a/cookbooks/apache2/recipes/mod_log_debug.rb b/cookbooks/apache2/recipes/mod_log_debug.rb new file mode 100644 index 0000000..17ee6c4 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_log_debug.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_log_debug +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'log_debug' diff --git a/cookbooks/apache2/recipes/mod_log_forensic.rb b/cookbooks/apache2/recipes/mod_log_forensic.rb new file mode 100644 index 0000000..61370e2 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_log_forensic.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_log_forensic +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'log_forensic' diff --git a/cookbooks/apache2/recipes/mod_logio.rb b/cookbooks/apache2/recipes/mod_logio.rb new file mode 100644 index 0000000..ca7a6dc --- /dev/null +++ b/cookbooks/apache2/recipes/mod_logio.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_logio +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +if platform_family?('rhel', 'fedora', 'suse', 'arch', 'freebsd') + apache_module 'logio' +else + include_recipe 'apache2::default' +end diff --git a/cookbooks/apache2/recipes/mod_lua.rb b/cookbooks/apache2/recipes/mod_lua.rb new file mode 100644 index 0000000..6065c79 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_lua.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_lua +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'lua' diff --git a/cookbooks/apache2/recipes/mod_macro.rb b/cookbooks/apache2/recipes/mod_macro.rb new file mode 100644 index 0000000..88563c9 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_macro.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_macro +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'macro' diff --git a/cookbooks/apache2/recipes/mod_mime.rb b/cookbooks/apache2/recipes/mod_mime.rb new file mode 100644 index 0000000..45d840e --- /dev/null +++ b/cookbooks/apache2/recipes/mod_mime.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_mime +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'mime' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_mime_magic.rb b/cookbooks/apache2/recipes/mod_mime_magic.rb new file mode 100644 index 0000000..751d1dd --- /dev/null +++ b/cookbooks/apache2/recipes/mod_mime_magic.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_mime_magic +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'mime_magic' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_negotiation.rb b/cookbooks/apache2/recipes/mod_negotiation.rb new file mode 100644 index 0000000..60aba01 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_negotiation.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_negotiation +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'negotiation' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_pagespeed.rb b/cookbooks/apache2/recipes/mod_pagespeed.rb new file mode 100644 index 0000000..f479d68 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_pagespeed.rb @@ -0,0 +1,37 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_pagespeed +# +# Copyright 2013, ZOZI +# +# 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. +# + +if platform_family?('debian') + remote_file "#{Chef::Config[:file_cache_path]}/mod-pagespeed.deb" do + source node['apache2']['mod_pagespeed']['package_link'] + mode '0644' + action :create_if_missing + end + + package 'mod_pagespeed' do + source "#{Chef::Config[:file_cache_path]}/mod-pagespeed.deb" + action :install + end + + apache_module 'pagespeed' do + conf true + end +else + Chef::Log.warn "apache::mod_pagespeed does not support #{node['platform_family']} yet, and is not being installed" +end diff --git a/cookbooks/apache2/recipes/mod_perl.rb b/cookbooks/apache2/recipes/mod_perl.rb new file mode 100644 index 0000000..1200712 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_perl.rb @@ -0,0 +1,53 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_perl +# +# adapted from the mod_python recipe by Jeremy Bingham +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +case node['platform_family'] +when 'debian' + %w(libapache2-mod-perl2 libapache2-request-perl apache2-mpm-prefork).each do |pkg| + package pkg + end +when 'suse' + package 'apache2-mod_perl' do + notifies :run, 'execute[generate-module-list]', :immediately + end + + package 'perl-Apache2-Request' +when 'rhel', 'fedora' + package 'mod_perl' do + notifies :run, 'execute[generate-module-list]', :immediately + end + + package 'perl-libapreq2' +when 'freebsd' + if node['apache']['version'] == '2.4' + package 'ap24-mod_perl2' + else + package 'ap22-mod_perl2' + end + package 'p5-libapreq2' +end + +file "#{node['apache']['dir']}/conf.d/perl.conf" do + action :delete + backup false +end + +apache_module 'perl' diff --git a/cookbooks/apache2/recipes/mod_php5.rb b/cookbooks/apache2/recipes/mod_php5.rb new file mode 100644 index 0000000..d252cf5 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_php5.rb @@ -0,0 +1,72 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_php5 +# +# Copyright 2008-2013, Chef Software, Inc. +# Copyright 2014, OneHealth Solutions, Inc. +# Copyright 2014, Viverae, Inc. +# +# 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. +# + +if node['apache']['mpm'] != 'prefork' + Chef::Log.warn('apache2::mod_php5 generally is expected to be run under a non-threaded MPM, such as prefork') + Chef::Log.warn('See http://php.net/manual/en/faq.installation.php#faq.installation.apache2') + Chef::Log.warn("Currently the apache2 cookbook is configured to use the '#{node['apache']['mpm']}' MPM") +end + +case node['platform_family'] +when 'debian' + package 'libapache2-mod-php5' +when 'arch' + package 'php-apache' do + notifies :run, 'execute[generate-module-list]', :immediately + end +when 'rhel' + package 'which' + package 'php package' do + if node['platform_version'].to_f < 6.0 + package_name 'php53' + else + package_name 'php' + end + notifies :run, 'execute[generate-module-list]', :immediately + not_if 'which php' + end +when 'fedora' + package 'which' + package 'php' do + notifies :run, 'execute[generate-module-list]', :immediately + not_if 'which php' + end +when 'suse' + package 'which' + package 'php' do + notifies :run, 'execute[generate-module-list]', :immediately + not_if 'which php' + end +when 'freebsd' + %w(php5 mod_php5 libxml2).each do |pkg| + package pkg + end +end unless node['apache']['mod_php5']['install_method'] == 'source' + +file "#{node['apache']['dir']}/conf.d/php.conf" do + action :delete + backup false +end + +apache_module 'php5' do + conf true + filename node['apache']['mod_php5']['so_filename'] +end diff --git a/cookbooks/apache2/recipes/mod_proxy.rb b/cookbooks/apache2/recipes/mod_proxy.rb new file mode 100644 index 0000000..5f0afe3 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_proxy.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_proxy +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'proxy' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_proxy_ajp.rb b/cookbooks/apache2/recipes/mod_proxy_ajp.rb new file mode 100644 index 0000000..786cc55 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_proxy_ajp.rb @@ -0,0 +1,21 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_proxy_ajp +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +include_recipe 'apache2::mod_proxy' +apache_module 'proxy_ajp' diff --git a/cookbooks/apache2/recipes/mod_proxy_balancer.rb b/cookbooks/apache2/recipes/mod_proxy_balancer.rb new file mode 100644 index 0000000..3c1773f --- /dev/null +++ b/cookbooks/apache2/recipes/mod_proxy_balancer.rb @@ -0,0 +1,27 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_proxy_balancer +# +# Copyright 2008-2013, Chef Software, Inc. +# Copyright 2014, OneHealth Solutions, Inc. +# +# 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. +# + +if !platform_family?('freebsd') && node['apache']['version'] == '2.4' + include_recipe 'apache2::mod_slotmem_shm' +end + +apache_module 'proxy_balancer' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_proxy_connect.rb b/cookbooks/apache2/recipes/mod_proxy_connect.rb new file mode 100644 index 0000000..a25514e --- /dev/null +++ b/cookbooks/apache2/recipes/mod_proxy_connect.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_proxy_connect +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'proxy_connect' diff --git a/cookbooks/apache2/recipes/mod_proxy_express.rb b/cookbooks/apache2/recipes/mod_proxy_express.rb new file mode 100644 index 0000000..5f28763 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_proxy_express.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_proxy_express +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'proxy_express' diff --git a/cookbooks/apache2/recipes/mod_proxy_fcgi.rb b/cookbooks/apache2/recipes/mod_proxy_fcgi.rb new file mode 100644 index 0000000..e265546 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_proxy_fcgi.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_proxy_fcgi +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'proxy_fcgi' diff --git a/cookbooks/apache2/recipes/mod_proxy_fdpass.rb b/cookbooks/apache2/recipes/mod_proxy_fdpass.rb new file mode 100644 index 0000000..58d7bb3 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_proxy_fdpass.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_proxy_fdpass +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'proxy_fdpass' diff --git a/cookbooks/apache2/recipes/mod_proxy_ftp.rb b/cookbooks/apache2/recipes/mod_proxy_ftp.rb new file mode 100644 index 0000000..9e3ca10 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_proxy_ftp.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_proxy_ftp +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'proxy_ftp' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_proxy_html.rb b/cookbooks/apache2/recipes/mod_proxy_html.rb new file mode 100644 index 0000000..8f0faa8 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_proxy_html.rb @@ -0,0 +1,25 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_proxy_html +# +# Copyright 2013, OneHealth Solutions, Inc. +# Copyright 2015, Alexander van Zoest +# +# 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. +# + +if node['apache']['version'] != '2.4' && platform_family == 'debian' + package 'libapache2-mod-proxy-html' +end + +apache_module 'proxy_html' diff --git a/cookbooks/apache2/recipes/mod_proxy_http.rb b/cookbooks/apache2/recipes/mod_proxy_http.rb new file mode 100644 index 0000000..358d244 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_proxy_http.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_proxy_http +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +include_recipe 'apache2::mod_proxy' + +apache_module 'proxy_http' diff --git a/cookbooks/apache2/recipes/mod_proxy_scgi.rb b/cookbooks/apache2/recipes/mod_proxy_scgi.rb new file mode 100644 index 0000000..60a84ae --- /dev/null +++ b/cookbooks/apache2/recipes/mod_proxy_scgi.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_proxy_scgi +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'proxy_scgi' diff --git a/cookbooks/apache2/recipes/mod_proxy_wstunnel.rb b/cookbooks/apache2/recipes/mod_proxy_wstunnel.rb new file mode 100644 index 0000000..8e9dcae --- /dev/null +++ b/cookbooks/apache2/recipes/mod_proxy_wstunnel.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_proxy_wstunnel +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'proxy_wstunnel' diff --git a/cookbooks/apache2/recipes/mod_python.rb b/cookbooks/apache2/recipes/mod_python.rb new file mode 100644 index 0000000..f1a3681 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_python.rb @@ -0,0 +1,44 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_python +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +case node['platform_family'] +when 'debian' + package 'libapache2-mod-python' +when 'suse' + package 'apache2-mod_python' do + notifies :run, 'execute[generate-module-list]', :immediately + end +when 'rhel', 'fedora' + package 'mod_python' do + notifies :run, 'execute[generate-module-list]', :immediately + end +when 'freebsd' + if node['apache']['version'] == '2.4' + package 'ap24-mod_python35' + else + package 'ap22-mod_python35' + end +end + +file "#{node['apache']['dir']}/conf.d/python.conf" do + action :delete + backup false +end + +apache_module 'python' diff --git a/cookbooks/apache2/recipes/mod_ratelimit.rb b/cookbooks/apache2/recipes/mod_ratelimit.rb new file mode 100644 index 0000000..d66111d --- /dev/null +++ b/cookbooks/apache2/recipes/mod_ratelimit.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_ratelimit +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'ratelimit' diff --git a/cookbooks/apache2/recipes/mod_reflector.rb b/cookbooks/apache2/recipes/mod_reflector.rb new file mode 100644 index 0000000..3645e78 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_reflector.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_reflector +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'reflector' diff --git a/cookbooks/apache2/recipes/mod_remoteip.rb b/cookbooks/apache2/recipes/mod_remoteip.rb new file mode 100644 index 0000000..fd6d313 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_remoteip.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_remoteip +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'remoteip' diff --git a/cookbooks/apache2/recipes/mod_reqtimeout.rb b/cookbooks/apache2/recipes/mod_reqtimeout.rb new file mode 100644 index 0000000..29f4ee1 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_reqtimeout.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_reqtimeout +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'reqtimeout' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_request.rb b/cookbooks/apache2/recipes/mod_request.rb new file mode 100644 index 0000000..7a036e4 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_request.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_request +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'request' diff --git a/cookbooks/apache2/recipes/mod_rewrite.rb b/cookbooks/apache2/recipes/mod_rewrite.rb new file mode 100644 index 0000000..778cbae --- /dev/null +++ b/cookbooks/apache2/recipes/mod_rewrite.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_rewrite +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'rewrite' diff --git a/cookbooks/apache2/recipes/mod_sed.rb b/cookbooks/apache2/recipes/mod_sed.rb new file mode 100644 index 0000000..ede8049 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_sed.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_sed +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'sed' diff --git a/cookbooks/apache2/recipes/mod_session.rb b/cookbooks/apache2/recipes/mod_session.rb new file mode 100644 index 0000000..92093f5 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_session.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_session +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'session' diff --git a/cookbooks/apache2/recipes/mod_session_cookie.rb b/cookbooks/apache2/recipes/mod_session_cookie.rb new file mode 100644 index 0000000..a416248 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_session_cookie.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_session_cookie +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'session_cookie' diff --git a/cookbooks/apache2/recipes/mod_session_crypto.rb b/cookbooks/apache2/recipes/mod_session_crypto.rb new file mode 100644 index 0000000..002c551 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_session_crypto.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_session_crypto +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'session_crypto' diff --git a/cookbooks/apache2/recipes/mod_session_dbd.rb b/cookbooks/apache2/recipes/mod_session_dbd.rb new file mode 100644 index 0000000..74a7e72 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_session_dbd.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_session_dbd +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'session_dbd' diff --git a/cookbooks/apache2/recipes/mod_setenvif.rb b/cookbooks/apache2/recipes/mod_setenvif.rb new file mode 100644 index 0000000..25c122f --- /dev/null +++ b/cookbooks/apache2/recipes/mod_setenvif.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_setenvif +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'setenvif' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_slotmem_plain.rb b/cookbooks/apache2/recipes/mod_slotmem_plain.rb new file mode 100644 index 0000000..acc1545 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_slotmem_plain.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_slotmem_plain +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'slotmem_plain' diff --git a/cookbooks/apache2/recipes/mod_slotmem_shm.rb b/cookbooks/apache2/recipes/mod_slotmem_shm.rb new file mode 100644 index 0000000..d2c45eb --- /dev/null +++ b/cookbooks/apache2/recipes/mod_slotmem_shm.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_slotmem_shm +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'slotmem_shm' diff --git a/cookbooks/apache2/recipes/mod_socache_dbm.rb b/cookbooks/apache2/recipes/mod_socache_dbm.rb new file mode 100644 index 0000000..37a6ff2 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_socache_dbm.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_socache_dbm +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'socache_dbm' diff --git a/cookbooks/apache2/recipes/mod_socache_memcache.rb b/cookbooks/apache2/recipes/mod_socache_memcache.rb new file mode 100644 index 0000000..6c91060 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_socache_memcache.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_socache_memcache +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'socache_memcache' diff --git a/cookbooks/apache2/recipes/mod_socache_shmcb.rb b/cookbooks/apache2/recipes/mod_socache_shmcb.rb new file mode 100644 index 0000000..eac6522 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_socache_shmcb.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_socache_shmcb +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'socache_shmcb' diff --git a/cookbooks/apache2/recipes/mod_speling.rb b/cookbooks/apache2/recipes/mod_speling.rb new file mode 100644 index 0000000..0c76d04 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_speling.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_speling +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'speling' diff --git a/cookbooks/apache2/recipes/mod_ssl.rb b/cookbooks/apache2/recipes/mod_ssl.rb new file mode 100644 index 0000000..0d749b0 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_ssl.rb @@ -0,0 +1,49 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_ssl +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# +unless node['apache']['listen_ports'].include?(node['apache']['mod_ssl']['port']) + node.default['apache']['listen_ports'] = node['apache']['listen_ports'] + [node['apache']['mod_ssl']['port']] +end + +include_recipe 'apache2::default' + +if platform_family?('rhel', 'fedora', 'suse') + package node['apache']['mod_ssl']['pkg_name'] do + notifies :run, 'execute[generate-module-list]', :immediately + end + + file "#{node['apache']['dir']}/conf.d/ssl.conf" do + action :delete + backup false + end +end + +template 'ssl_ports.conf' do + path "#{node['apache']['dir']}/ports.conf" + source 'ports.conf.erb' + mode '0644' + notifies :restart, 'service[apache2]', :delayed +end + +apache_module 'ssl' do + conf true +end + +if node['apache']['version'] == '2.4' + include_recipe 'apache2::mod_socache_shmcb' +end diff --git a/cookbooks/apache2/recipes/mod_status.rb b/cookbooks/apache2/recipes/mod_status.rb new file mode 100644 index 0000000..60f5f75 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_status.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_status +# +# Copyright 2008-2012, Chef Software, Inc. +# +# 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. +# + +apache_module 'status' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_substitute.rb b/cookbooks/apache2/recipes/mod_substitute.rb new file mode 100644 index 0000000..393e3fc --- /dev/null +++ b/cookbooks/apache2/recipes/mod_substitute.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_substitute +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'substitute' diff --git a/cookbooks/apache2/recipes/mod_suexec.rb b/cookbooks/apache2/recipes/mod_suexec.rb new file mode 100644 index 0000000..75172aa --- /dev/null +++ b/cookbooks/apache2/recipes/mod_suexec.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_suexec +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'suexec' diff --git a/cookbooks/apache2/recipes/mod_systemd.rb b/cookbooks/apache2/recipes/mod_systemd.rb new file mode 100644 index 0000000..d3f979d --- /dev/null +++ b/cookbooks/apache2/recipes/mod_systemd.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_systemd +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'systemd' diff --git a/cookbooks/apache2/recipes/mod_unique_id.rb b/cookbooks/apache2/recipes/mod_unique_id.rb new file mode 100644 index 0000000..cf3fbf4 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_unique_id.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_unique_id +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'unique_id' diff --git a/cookbooks/apache2/recipes/mod_unixd.rb b/cookbooks/apache2/recipes/mod_unixd.rb new file mode 100644 index 0000000..d41654b --- /dev/null +++ b/cookbooks/apache2/recipes/mod_unixd.rb @@ -0,0 +1,25 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_unixd +# +# Copyright 2014, OneHealth Solutions, Inc. +# +# 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. +# + +# on platform_family debian this module is staticly linked into apache2 +if node['apache']['version'] == '2.4' && !platform_family?('debian') + apache_module 'unixd' +else + log 'Ignoring apache2::mod_unixd. Not available until apache 2.4' +end diff --git a/cookbooks/apache2/recipes/mod_userdir.rb b/cookbooks/apache2/recipes/mod_userdir.rb new file mode 100644 index 0000000..7d519d4 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_userdir.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_userdir +# +# Copyright 2013, Chef Software, Inc. +# +# 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. +# + +apache_module 'userdir' do + conf true +end diff --git a/cookbooks/apache2/recipes/mod_usertrack.rb b/cookbooks/apache2/recipes/mod_usertrack.rb new file mode 100644 index 0000000..6462ee3 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_usertrack.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_usertrack +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'usertrack' diff --git a/cookbooks/apache2/recipes/mod_vhost_alias.rb b/cookbooks/apache2/recipes/mod_vhost_alias.rb new file mode 100644 index 0000000..a56ecab --- /dev/null +++ b/cookbooks/apache2/recipes/mod_vhost_alias.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_vhost_alias +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'vhost_alias' diff --git a/cookbooks/apache2/recipes/mod_wsgi.rb b/cookbooks/apache2/recipes/mod_wsgi.rb new file mode 100644 index 0000000..3505e82 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_wsgi.rb @@ -0,0 +1,34 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_wsgi +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +case node['platform_family'] +when 'debian' + package 'libapache2-mod-wsgi' +when 'rhel', 'fedora', 'arch' + package 'mod_wsgi' do + notifies :run, 'execute[generate-module-list]', :immediately + end +end + +file "#{node['apache']['dir']}/conf.d/wsgi.conf" do + action :delete + backup false +end + +apache_module 'wsgi' diff --git a/cookbooks/apache2/recipes/mod_xml2enc.rb b/cookbooks/apache2/recipes/mod_xml2enc.rb new file mode 100644 index 0000000..76ab418 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_xml2enc.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_xml2enc +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +apache_module 'xml2enc' diff --git a/cookbooks/apache2/recipes/mod_xsendfile.rb b/cookbooks/apache2/recipes/mod_xsendfile.rb new file mode 100644 index 0000000..8000984 --- /dev/null +++ b/cookbooks/apache2/recipes/mod_xsendfile.rb @@ -0,0 +1,38 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mod_xsendfile +# +# Copyright 2011-2013, CustomInk, LLC. +# +# 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. +# + +case node['platform_family'] +when 'suse' + package 'apache2-mod_xsendfile' do + notifies :run, 'execute[generate-module-list]', :immediately + end +when 'debian' + package 'libapache2-mod-xsendfile' +when 'rhel', 'fedora' + package 'mod_xsendfile' do + notifies :run, 'execute[generate-module-list]', :immediately + end +end + +file "#{node['apache']['dir']}/conf.d/xsendfile.conf" do + action :delete + backup false +end + +apache_module 'xsendfile' diff --git a/cookbooks/apache2/recipes/mpm_event.rb b/cookbooks/apache2/recipes/mpm_event.rb new file mode 100644 index 0000000..3f27aea --- /dev/null +++ b/cookbooks/apache2/recipes/mpm_event.rb @@ -0,0 +1,27 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mpm_event +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +# apache_module('mpm_itk') { enable false } +apache_module('mpm_prefork') { enable false } +apache_module('mpm_worker') { enable false } + +apache_module 'mpm_event' do + conf true + restart true +end diff --git a/cookbooks/apache2/recipes/mpm_prefork.rb b/cookbooks/apache2/recipes/mpm_prefork.rb new file mode 100644 index 0000000..de30821 --- /dev/null +++ b/cookbooks/apache2/recipes/mpm_prefork.rb @@ -0,0 +1,27 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mpm_prefork +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +# apache_module('mpm_itk') { enable false } +apache_module('mpm_event') { enable false } +apache_module('mpm_worker') { enable false } + +apache_module 'mpm_prefork' do + conf true + restart true +end diff --git a/cookbooks/apache2/recipes/mpm_worker.rb b/cookbooks/apache2/recipes/mpm_worker.rb new file mode 100644 index 0000000..86a01a5 --- /dev/null +++ b/cookbooks/apache2/recipes/mpm_worker.rb @@ -0,0 +1,27 @@ +# +# Cookbook Name:: apache2 +# Recipe:: mpm_worker +# +# Copyright 2013, OneHealth Solutions, Inc. +# +# 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. +# + +# apache_module('mpm_itk') { enable false } +apache_module('mpm_event') { enable false } +apache_module('mpm_prefork') { enable false } + +apache_module 'mpm_worker' do + conf true + restart true +end diff --git a/cookbooks/apache2/templates/default/a2disconf.erb b/cookbooks/apache2/templates/default/a2disconf.erb new file mode 100644 index 0000000..6c2c262 --- /dev/null +++ b/cookbooks/apache2/templates/default/a2disconf.erb @@ -0,0 +1,532 @@ +#!/usr/bin/perl -w +# +# a2enmod by Stefan Fritsch +# Licensed under Apache License 2.0 +# +# The coding style is "perltidy -pbp" + +use strict; +use Cwd 'realpath'; +use File::Spec; +use File::Basename; +use File::Path; +use Getopt::Long; + +my $quiet; +my $force; +my $maintmode; +my $purge; + +Getopt::Long::Configure('bundling'); +GetOptions( + 'quiet|q' => \$quiet, + 'force|f' => \$force, + 'maintmode|m' => \$maintmode, + 'purge|p' => \$purge +) or exit 2; + +my $basename = basename($0); +$basename =~ /^a2(en|dis)(mod|site|conf)((?:-.+)?)$/ + or die "$basename call name unknown\n"; +my $act = $1; +my $obj = $2; +my $dir_suffix = $3; + +my $env_file = $ENV{APACHE_ENVVARS} + || ( + $ENV{APACHE_CONFDIR} + ? "$ENV{APACHE_CONFDIR}/envvars" + : "<%= node['apache']['dir'] %>$dir_suffix/envvars" + ); +$ENV{LANG} = 'C'; +read_env_file($env_file); + +$act .= 'able'; +my ( $name, $dir, $sffx, $reload ); +if ( $obj eq 'mod' ) { + $obj = 'module'; + $dir = 'mods'; + $sffx = '.load'; + $reload = 'restart'; +} +elsif ( $obj eq 'conf' ) { + $obj = 'conf'; + $dir = 'conf'; + $sffx = '.conf'; + $reload = 'reload'; +} +else { + $dir = 'sites'; + $sffx = '.conf'; + $reload = 'reload'; +} +$name = ucfirst($obj); + +my $confdir = $ENV{APACHE_CONFDIR} || "<%= node['apache']['dir'] %>$dir_suffix"; +my $availdir = $ENV{ uc("APACHE_${dir}_AVAILABLE") } + || "$confdir/$dir-available"; +my $enabldir = $ENV{ uc("APACHE_${dir}_ENABLED") } || "$confdir/$dir-enabled"; +my $statedir = $ENV{ uc("APACHE_STATE_DIRECTORY") } || "<%= node['apache']['lib_dir'] %>"; + +$statedir .= "/$obj"; + +my $choicedir = $act eq 'enable' ? $availdir : $enabldir; +my $linkdir = File::Spec->abs2rel( $availdir, $enabldir ); + +my $request_reload = 0; + +my $rc = 0; + +if ( !scalar @ARGV ) { + my @choices = myglob('*'); + print "Your choices are: @choices\n"; + print "Which ${obj}(s) do you want to $act (wildcards ok)?\n"; + my $input = <>; + @ARGV = split /\s+/, $input; + +} + +my @objs; +foreach my $arg (@ARGV) { + $arg =~ s/${sffx}$//; + my @glob = myglob($arg); + if ( !@glob ) { + error("No $obj found matching $arg!\n"); + $rc = 1; + } + else { + push @objs, @glob; + } +} + +foreach my $acton (@objs) { + doit($acton) or $rc = 1; +} + +info( + "To activate the new configuration, you need to run:\n service apache2 $reload\n" +) if $request_reload; + +exit($rc); + +############################################################################## + +sub myglob { + my $arg = shift; + + my @glob = map { + s{^$choicedir/}{}; + s{$sffx$}{}; + $_ + } glob("$choicedir/$arg$sffx"); + + return @glob; +} + +sub doit { + my $acton = shift; + + my ( $conftgt, $conflink ); + if ( $obj eq 'module' ) { + if ( $acton eq 'cgi' && threaded() ) { + print + "Your MPM seems to be threaded. Selecting cgid instead of cgi.\n"; + $acton = 'cgid'; + } + + $conftgt = "$availdir/$acton.conf"; + if ( -e $conftgt ) { + $conflink = "$enabldir/$acton.conf"; + } + } + + my $tgt = "$availdir/$acton$sffx"; + my $link = "$enabldir/$acton$sffx"; + + if ( !-e $tgt ) { + if ( -l $link && !-e $link ) { + if ( $act eq 'disable' ) { + info("removing dangling symlink $link\n"); + unlink($link); + + # force a .conf path. It may exist as dangling link, too + $conflink = "$enabldir/$acton.conf"; + + if ( -l $conflink && !-e $conflink ) { + info("removing dangling symlink $conflink\n"); + unlink($conflink); + } + + return 1; + } + else { + error("$link is a dangling symlink!\n"); + } + } + + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + # exit silently, we are purging anyway + return 1; + } + + error("$name $acton does not exist!\n"); + return 0; + } + + # handle module dependencies + if ( $obj eq 'module' ) { + if ( $act eq 'enable' ) { + if ( $acton eq 'mpm_itk' ) { + warning( "MPM_ITK is a third party module that is not part " + . "of the official Apache HTTPD. It has seen less " + . "testing than the official MPM modules." ); + } + my @depends = get_deps("$availdir/$acton.load"); + do_deps( $acton, @depends ) or return 0; + + my @conflicts = get_deps( "$availdir/$acton.load", "Conflicts" ); + check_conflicts( $acton, @conflicts ) or return 0; + } + else { + my @depending; + foreach my $d ( glob("$enabldir/*.load") ) { + my @deps = get_deps($d); + if ( is_in( $acton, @deps ) ) { + $d =~ m,/([^/]+).load$,; + push @depending, $1; + } + } + if ( scalar @depending ) { + if ($force) { + do_deps( $acton, @depending ) or return 0; + } + else { + error( + "The following modules depend on $acton ", + "and need to be disabled first: @depending\n" + ); + return 0; + } + } + } + } + elsif ( $act eq 'enable' ) { + my @depends = get_deps("$availdir/$acton$sffx"); + warn_deps( $acton, @depends ) or return 0; + } + + if ( $act eq 'enable' ) { + my $check = check_link( $tgt, $link ); + if ( $check eq 'ok' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'ok' ) { + info("$name $acton already enabled\n"); + return 1; + } + elsif ( $confcheck eq 'missing' ) { + print "Enabling config file $acton.conf.\n"; + add_link( $conftgt, $conflink ) or return 0; + } + else { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + else { + info("$name $acton already enabled\n"); + return 1; + } + } + elsif ( $check eq 'missing' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'missing' ) { + add_link( $conftgt, $conflink ) or return 0; + } + elsif ( $confcheck ne 'ok' ) { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + + print "Enabling $obj $acton.\n"; + if ( $acton eq 'ssl' ) { + info( "See /usr/share/doc/apache2/README.Debian.gz on " + . "how to configure SSL and create self-signed certificates.\n" + ); + } + return add_link( $tgt, $link ) + && switch_marker( $obj, $act, $acton ); + } + else { + error("$name $acton not properly enabled: $check\n"); + return 0; + } + } + else { + if ( -e $link || -l $link ) { + remove_link($link); + if ( $conflink && -e $conflink ) { + remove_link($conflink); + } + switch_marker( $obj, $act, $acton ); + print "$name $acton disabled.\n"; + } + elsif ( $conflink && -e $conflink ) { + print "Disabling stale config file $acton.conf.\n"; + remove_link($conflink); + } + else { + info("$name $acton already disabled\n"); + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + } + return 1; + } + } + + return 1; +} + +sub get_deps { + my $file = shift; + my $type = shift || "Depends"; + + my $fd; + if ( !open( $fd, '<', $file ) ) { + error("Can't open $file: $!"); + return; + } + my $line; + while ( defined( $line = <$fd> ) ) { + chomp $line; + if ( $line =~ /^# $type:\s+(.*?)\s*$/ ) { + my $deps = $1; + return split( /[\n\s]+/, $deps ); + } + + # only check until the first non-empty non-comment line + last if ( $line !~ /^\s*(?:#.*)?$/ ); + } + return; +} + +sub do_deps { + my $acton = shift; + foreach my $d (@_) { + info("Considering dependency $d for $acton:\n"); + if ( !doit($d) ) { + error("Could not $act dependency $d for $acton, aborting\n"); + return 0; + } + } + return 1; +} + +sub warn_deps { + my $acton = shift; + my $modsenabldir = $ENV{APACHE_MODS_ENABLED} || "$confdir/mods-enabled"; + foreach my $d (@_) { + info("Checking dependency $d for $acton:\n"); + if ( !-e "$modsenabldir/$d.load" ) { + warning( + "Module $d is not enabled, but $acton depends on it, aborting\n" + ); + return 0; + } + } + return 1; +} + +sub check_conflicts { + my $acton = shift; + my $haderror = 0; + foreach my $d (@_) { + info("Considering conflict $d for $acton:\n"); + + my $tgt = "$availdir/$d$sffx"; + my $link = "$enabldir/$d$sffx"; + + my $confcheck = check_link( $tgt, $link ); + if ( $confcheck eq 'ok' ) { + error( + "Module $d is enabled - cannot proceed due to conflicts. It needs to be disabled first!\n" + ); + + # Don't return immediately, there could be several conflicts + $haderror++; + } + } + + if ($haderror) { + return 0; + } + + return 1; +} + +sub add_link { + my ( $tgt, $link ) = @_; + + # create relative link + if ( !symlink( File::Spec->abs2rel( $tgt, dirname($link) ), $link ) ) { + die("Could not create $link: $!\n"); + } + $request_reload = 1; + return 1; +} + +sub check_link { + my ( $tgt, $link ) = @_; + + if ( !-e $link ) { + if ( -l $link ) { + + # points to nowhere + info("Removing dangling link $link"); + unlink($link) or die "Could not remove $link\n"; + } + return 'missing'; + } + + if ( -e $link && !-l $link ) { + return "$link is a real file, not touching it"; + } + if ( realpath($link) ne realpath($tgt) ) { + return "$link exists but does not point to $tgt, not touching it"; + } + return 'ok'; +} + +sub remove_link { + my ($link) = @_; + + if ( -l $link ) { + unlink($link) or die "Could not remove $link: $!\n"; + } + elsif ( -e $link ) { + error("$link is not a symbolic link, not deleting\n"); + return 0; + } + $request_reload = 1; + return 1; +} + +sub threaded { + my $result = ""; + $result = qx{<%= node['apache']['apachectl'] %> -V | grep 'threaded'} + if -x '<%= node['apache']['apachectl'] %>'; + if ( $? != 0 ) { + + # config doesn't work + if ( -e "$enabldir/mpm_prefork.load" || -e "$enabldir/mpm_itk.load" ) + { + return 0; + } + elsif (-e "$enabldir/mpm_worker.load" + || -e "$enabldir/mpm_event.load" ) + { + return 1; + } + else { + error("Can't determine enabled MPM"); + + # do what user requested + return 0; + } + } + if ( $result =~ / no/ ) { + return 0; + } + elsif ( $result =~ / yes/ ) { + return 1; + } + else { + die("Can't parse output from apache2ctl -V:\n$result\n"); + } +} + +sub info { + print @_ if !$quiet; +} + +sub error { + print STDERR 'ERROR: ', @_; +} + +sub warning { + print STDERR 'WARNING: ', @_; +} + +sub is_in { + my $needle = shift; + foreach my $e (@_) { + return 1 if $needle eq $e; + } + return 0; +} + +sub read_env_file { + my $file = shift; + + -r $file or return; + my @lines = qx{env - sh -c '. $file && env'}; + if ($?) { + die "Could not read $file\n"; + } + + foreach my $l (@lines) { + chomp $l; + $l =~ /^(.*)?=(.*)$/ or die "Could not parse $file\n"; + $ENV{$1} = $2; + } +} + +sub switch_marker { + die('usage: switch_marker([module|site|conf], [enable|disable], $name)') + if @_ != 3; + my $which = shift; + my $what = shift; + my $name = shift; + + my $mode = "admin"; + $mode = "maint" if $maintmode; + + #print("switch_marker $which $what $name\n"); + # TODO: get rid of the magic string(s) + my $state_marker_dir = "$statedir/$what" . "d" . "_by_$mode"; + my $state_marker = "$state_marker_dir/$name"; + if ( !-d $state_marker_dir ) { + File::Path::mkpath("$state_marker_dir") + || error( + "Failed to create marker directory: '$state_marker_dir'\n"); + } + + # XXX: swap find with perl alternative + my @markers = qx{find "$statedir" -type f -a -name "$name"}; + chomp(@markers); + foreach (@markers) { + unless ( unlink $_ ) { + error("Failed to remove old marker '$_'!\n") && return 0; + } + } + unless ($purge) { + qx{touch "$state_marker"}; + if ( $? != 0 ) { + error("Failed to create marker '$state_marker'!\n") && return 0; + } + return 1; + } +} + +# vim: syntax=perl sw=4 sts=4 sr et diff --git a/cookbooks/apache2/templates/default/a2dismod.erb b/cookbooks/apache2/templates/default/a2dismod.erb new file mode 100644 index 0000000..6c2c262 --- /dev/null +++ b/cookbooks/apache2/templates/default/a2dismod.erb @@ -0,0 +1,532 @@ +#!/usr/bin/perl -w +# +# a2enmod by Stefan Fritsch +# Licensed under Apache License 2.0 +# +# The coding style is "perltidy -pbp" + +use strict; +use Cwd 'realpath'; +use File::Spec; +use File::Basename; +use File::Path; +use Getopt::Long; + +my $quiet; +my $force; +my $maintmode; +my $purge; + +Getopt::Long::Configure('bundling'); +GetOptions( + 'quiet|q' => \$quiet, + 'force|f' => \$force, + 'maintmode|m' => \$maintmode, + 'purge|p' => \$purge +) or exit 2; + +my $basename = basename($0); +$basename =~ /^a2(en|dis)(mod|site|conf)((?:-.+)?)$/ + or die "$basename call name unknown\n"; +my $act = $1; +my $obj = $2; +my $dir_suffix = $3; + +my $env_file = $ENV{APACHE_ENVVARS} + || ( + $ENV{APACHE_CONFDIR} + ? "$ENV{APACHE_CONFDIR}/envvars" + : "<%= node['apache']['dir'] %>$dir_suffix/envvars" + ); +$ENV{LANG} = 'C'; +read_env_file($env_file); + +$act .= 'able'; +my ( $name, $dir, $sffx, $reload ); +if ( $obj eq 'mod' ) { + $obj = 'module'; + $dir = 'mods'; + $sffx = '.load'; + $reload = 'restart'; +} +elsif ( $obj eq 'conf' ) { + $obj = 'conf'; + $dir = 'conf'; + $sffx = '.conf'; + $reload = 'reload'; +} +else { + $dir = 'sites'; + $sffx = '.conf'; + $reload = 'reload'; +} +$name = ucfirst($obj); + +my $confdir = $ENV{APACHE_CONFDIR} || "<%= node['apache']['dir'] %>$dir_suffix"; +my $availdir = $ENV{ uc("APACHE_${dir}_AVAILABLE") } + || "$confdir/$dir-available"; +my $enabldir = $ENV{ uc("APACHE_${dir}_ENABLED") } || "$confdir/$dir-enabled"; +my $statedir = $ENV{ uc("APACHE_STATE_DIRECTORY") } || "<%= node['apache']['lib_dir'] %>"; + +$statedir .= "/$obj"; + +my $choicedir = $act eq 'enable' ? $availdir : $enabldir; +my $linkdir = File::Spec->abs2rel( $availdir, $enabldir ); + +my $request_reload = 0; + +my $rc = 0; + +if ( !scalar @ARGV ) { + my @choices = myglob('*'); + print "Your choices are: @choices\n"; + print "Which ${obj}(s) do you want to $act (wildcards ok)?\n"; + my $input = <>; + @ARGV = split /\s+/, $input; + +} + +my @objs; +foreach my $arg (@ARGV) { + $arg =~ s/${sffx}$//; + my @glob = myglob($arg); + if ( !@glob ) { + error("No $obj found matching $arg!\n"); + $rc = 1; + } + else { + push @objs, @glob; + } +} + +foreach my $acton (@objs) { + doit($acton) or $rc = 1; +} + +info( + "To activate the new configuration, you need to run:\n service apache2 $reload\n" +) if $request_reload; + +exit($rc); + +############################################################################## + +sub myglob { + my $arg = shift; + + my @glob = map { + s{^$choicedir/}{}; + s{$sffx$}{}; + $_ + } glob("$choicedir/$arg$sffx"); + + return @glob; +} + +sub doit { + my $acton = shift; + + my ( $conftgt, $conflink ); + if ( $obj eq 'module' ) { + if ( $acton eq 'cgi' && threaded() ) { + print + "Your MPM seems to be threaded. Selecting cgid instead of cgi.\n"; + $acton = 'cgid'; + } + + $conftgt = "$availdir/$acton.conf"; + if ( -e $conftgt ) { + $conflink = "$enabldir/$acton.conf"; + } + } + + my $tgt = "$availdir/$acton$sffx"; + my $link = "$enabldir/$acton$sffx"; + + if ( !-e $tgt ) { + if ( -l $link && !-e $link ) { + if ( $act eq 'disable' ) { + info("removing dangling symlink $link\n"); + unlink($link); + + # force a .conf path. It may exist as dangling link, too + $conflink = "$enabldir/$acton.conf"; + + if ( -l $conflink && !-e $conflink ) { + info("removing dangling symlink $conflink\n"); + unlink($conflink); + } + + return 1; + } + else { + error("$link is a dangling symlink!\n"); + } + } + + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + # exit silently, we are purging anyway + return 1; + } + + error("$name $acton does not exist!\n"); + return 0; + } + + # handle module dependencies + if ( $obj eq 'module' ) { + if ( $act eq 'enable' ) { + if ( $acton eq 'mpm_itk' ) { + warning( "MPM_ITK is a third party module that is not part " + . "of the official Apache HTTPD. It has seen less " + . "testing than the official MPM modules." ); + } + my @depends = get_deps("$availdir/$acton.load"); + do_deps( $acton, @depends ) or return 0; + + my @conflicts = get_deps( "$availdir/$acton.load", "Conflicts" ); + check_conflicts( $acton, @conflicts ) or return 0; + } + else { + my @depending; + foreach my $d ( glob("$enabldir/*.load") ) { + my @deps = get_deps($d); + if ( is_in( $acton, @deps ) ) { + $d =~ m,/([^/]+).load$,; + push @depending, $1; + } + } + if ( scalar @depending ) { + if ($force) { + do_deps( $acton, @depending ) or return 0; + } + else { + error( + "The following modules depend on $acton ", + "and need to be disabled first: @depending\n" + ); + return 0; + } + } + } + } + elsif ( $act eq 'enable' ) { + my @depends = get_deps("$availdir/$acton$sffx"); + warn_deps( $acton, @depends ) or return 0; + } + + if ( $act eq 'enable' ) { + my $check = check_link( $tgt, $link ); + if ( $check eq 'ok' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'ok' ) { + info("$name $acton already enabled\n"); + return 1; + } + elsif ( $confcheck eq 'missing' ) { + print "Enabling config file $acton.conf.\n"; + add_link( $conftgt, $conflink ) or return 0; + } + else { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + else { + info("$name $acton already enabled\n"); + return 1; + } + } + elsif ( $check eq 'missing' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'missing' ) { + add_link( $conftgt, $conflink ) or return 0; + } + elsif ( $confcheck ne 'ok' ) { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + + print "Enabling $obj $acton.\n"; + if ( $acton eq 'ssl' ) { + info( "See /usr/share/doc/apache2/README.Debian.gz on " + . "how to configure SSL and create self-signed certificates.\n" + ); + } + return add_link( $tgt, $link ) + && switch_marker( $obj, $act, $acton ); + } + else { + error("$name $acton not properly enabled: $check\n"); + return 0; + } + } + else { + if ( -e $link || -l $link ) { + remove_link($link); + if ( $conflink && -e $conflink ) { + remove_link($conflink); + } + switch_marker( $obj, $act, $acton ); + print "$name $acton disabled.\n"; + } + elsif ( $conflink && -e $conflink ) { + print "Disabling stale config file $acton.conf.\n"; + remove_link($conflink); + } + else { + info("$name $acton already disabled\n"); + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + } + return 1; + } + } + + return 1; +} + +sub get_deps { + my $file = shift; + my $type = shift || "Depends"; + + my $fd; + if ( !open( $fd, '<', $file ) ) { + error("Can't open $file: $!"); + return; + } + my $line; + while ( defined( $line = <$fd> ) ) { + chomp $line; + if ( $line =~ /^# $type:\s+(.*?)\s*$/ ) { + my $deps = $1; + return split( /[\n\s]+/, $deps ); + } + + # only check until the first non-empty non-comment line + last if ( $line !~ /^\s*(?:#.*)?$/ ); + } + return; +} + +sub do_deps { + my $acton = shift; + foreach my $d (@_) { + info("Considering dependency $d for $acton:\n"); + if ( !doit($d) ) { + error("Could not $act dependency $d for $acton, aborting\n"); + return 0; + } + } + return 1; +} + +sub warn_deps { + my $acton = shift; + my $modsenabldir = $ENV{APACHE_MODS_ENABLED} || "$confdir/mods-enabled"; + foreach my $d (@_) { + info("Checking dependency $d for $acton:\n"); + if ( !-e "$modsenabldir/$d.load" ) { + warning( + "Module $d is not enabled, but $acton depends on it, aborting\n" + ); + return 0; + } + } + return 1; +} + +sub check_conflicts { + my $acton = shift; + my $haderror = 0; + foreach my $d (@_) { + info("Considering conflict $d for $acton:\n"); + + my $tgt = "$availdir/$d$sffx"; + my $link = "$enabldir/$d$sffx"; + + my $confcheck = check_link( $tgt, $link ); + if ( $confcheck eq 'ok' ) { + error( + "Module $d is enabled - cannot proceed due to conflicts. It needs to be disabled first!\n" + ); + + # Don't return immediately, there could be several conflicts + $haderror++; + } + } + + if ($haderror) { + return 0; + } + + return 1; +} + +sub add_link { + my ( $tgt, $link ) = @_; + + # create relative link + if ( !symlink( File::Spec->abs2rel( $tgt, dirname($link) ), $link ) ) { + die("Could not create $link: $!\n"); + } + $request_reload = 1; + return 1; +} + +sub check_link { + my ( $tgt, $link ) = @_; + + if ( !-e $link ) { + if ( -l $link ) { + + # points to nowhere + info("Removing dangling link $link"); + unlink($link) or die "Could not remove $link\n"; + } + return 'missing'; + } + + if ( -e $link && !-l $link ) { + return "$link is a real file, not touching it"; + } + if ( realpath($link) ne realpath($tgt) ) { + return "$link exists but does not point to $tgt, not touching it"; + } + return 'ok'; +} + +sub remove_link { + my ($link) = @_; + + if ( -l $link ) { + unlink($link) or die "Could not remove $link: $!\n"; + } + elsif ( -e $link ) { + error("$link is not a symbolic link, not deleting\n"); + return 0; + } + $request_reload = 1; + return 1; +} + +sub threaded { + my $result = ""; + $result = qx{<%= node['apache']['apachectl'] %> -V | grep 'threaded'} + if -x '<%= node['apache']['apachectl'] %>'; + if ( $? != 0 ) { + + # config doesn't work + if ( -e "$enabldir/mpm_prefork.load" || -e "$enabldir/mpm_itk.load" ) + { + return 0; + } + elsif (-e "$enabldir/mpm_worker.load" + || -e "$enabldir/mpm_event.load" ) + { + return 1; + } + else { + error("Can't determine enabled MPM"); + + # do what user requested + return 0; + } + } + if ( $result =~ / no/ ) { + return 0; + } + elsif ( $result =~ / yes/ ) { + return 1; + } + else { + die("Can't parse output from apache2ctl -V:\n$result\n"); + } +} + +sub info { + print @_ if !$quiet; +} + +sub error { + print STDERR 'ERROR: ', @_; +} + +sub warning { + print STDERR 'WARNING: ', @_; +} + +sub is_in { + my $needle = shift; + foreach my $e (@_) { + return 1 if $needle eq $e; + } + return 0; +} + +sub read_env_file { + my $file = shift; + + -r $file or return; + my @lines = qx{env - sh -c '. $file && env'}; + if ($?) { + die "Could not read $file\n"; + } + + foreach my $l (@lines) { + chomp $l; + $l =~ /^(.*)?=(.*)$/ or die "Could not parse $file\n"; + $ENV{$1} = $2; + } +} + +sub switch_marker { + die('usage: switch_marker([module|site|conf], [enable|disable], $name)') + if @_ != 3; + my $which = shift; + my $what = shift; + my $name = shift; + + my $mode = "admin"; + $mode = "maint" if $maintmode; + + #print("switch_marker $which $what $name\n"); + # TODO: get rid of the magic string(s) + my $state_marker_dir = "$statedir/$what" . "d" . "_by_$mode"; + my $state_marker = "$state_marker_dir/$name"; + if ( !-d $state_marker_dir ) { + File::Path::mkpath("$state_marker_dir") + || error( + "Failed to create marker directory: '$state_marker_dir'\n"); + } + + # XXX: swap find with perl alternative + my @markers = qx{find "$statedir" -type f -a -name "$name"}; + chomp(@markers); + foreach (@markers) { + unless ( unlink $_ ) { + error("Failed to remove old marker '$_'!\n") && return 0; + } + } + unless ($purge) { + qx{touch "$state_marker"}; + if ( $? != 0 ) { + error("Failed to create marker '$state_marker'!\n") && return 0; + } + return 1; + } +} + +# vim: syntax=perl sw=4 sts=4 sr et diff --git a/cookbooks/apache2/templates/default/a2dissite.erb b/cookbooks/apache2/templates/default/a2dissite.erb new file mode 100644 index 0000000..6c2c262 --- /dev/null +++ b/cookbooks/apache2/templates/default/a2dissite.erb @@ -0,0 +1,532 @@ +#!/usr/bin/perl -w +# +# a2enmod by Stefan Fritsch +# Licensed under Apache License 2.0 +# +# The coding style is "perltidy -pbp" + +use strict; +use Cwd 'realpath'; +use File::Spec; +use File::Basename; +use File::Path; +use Getopt::Long; + +my $quiet; +my $force; +my $maintmode; +my $purge; + +Getopt::Long::Configure('bundling'); +GetOptions( + 'quiet|q' => \$quiet, + 'force|f' => \$force, + 'maintmode|m' => \$maintmode, + 'purge|p' => \$purge +) or exit 2; + +my $basename = basename($0); +$basename =~ /^a2(en|dis)(mod|site|conf)((?:-.+)?)$/ + or die "$basename call name unknown\n"; +my $act = $1; +my $obj = $2; +my $dir_suffix = $3; + +my $env_file = $ENV{APACHE_ENVVARS} + || ( + $ENV{APACHE_CONFDIR} + ? "$ENV{APACHE_CONFDIR}/envvars" + : "<%= node['apache']['dir'] %>$dir_suffix/envvars" + ); +$ENV{LANG} = 'C'; +read_env_file($env_file); + +$act .= 'able'; +my ( $name, $dir, $sffx, $reload ); +if ( $obj eq 'mod' ) { + $obj = 'module'; + $dir = 'mods'; + $sffx = '.load'; + $reload = 'restart'; +} +elsif ( $obj eq 'conf' ) { + $obj = 'conf'; + $dir = 'conf'; + $sffx = '.conf'; + $reload = 'reload'; +} +else { + $dir = 'sites'; + $sffx = '.conf'; + $reload = 'reload'; +} +$name = ucfirst($obj); + +my $confdir = $ENV{APACHE_CONFDIR} || "<%= node['apache']['dir'] %>$dir_suffix"; +my $availdir = $ENV{ uc("APACHE_${dir}_AVAILABLE") } + || "$confdir/$dir-available"; +my $enabldir = $ENV{ uc("APACHE_${dir}_ENABLED") } || "$confdir/$dir-enabled"; +my $statedir = $ENV{ uc("APACHE_STATE_DIRECTORY") } || "<%= node['apache']['lib_dir'] %>"; + +$statedir .= "/$obj"; + +my $choicedir = $act eq 'enable' ? $availdir : $enabldir; +my $linkdir = File::Spec->abs2rel( $availdir, $enabldir ); + +my $request_reload = 0; + +my $rc = 0; + +if ( !scalar @ARGV ) { + my @choices = myglob('*'); + print "Your choices are: @choices\n"; + print "Which ${obj}(s) do you want to $act (wildcards ok)?\n"; + my $input = <>; + @ARGV = split /\s+/, $input; + +} + +my @objs; +foreach my $arg (@ARGV) { + $arg =~ s/${sffx}$//; + my @glob = myglob($arg); + if ( !@glob ) { + error("No $obj found matching $arg!\n"); + $rc = 1; + } + else { + push @objs, @glob; + } +} + +foreach my $acton (@objs) { + doit($acton) or $rc = 1; +} + +info( + "To activate the new configuration, you need to run:\n service apache2 $reload\n" +) if $request_reload; + +exit($rc); + +############################################################################## + +sub myglob { + my $arg = shift; + + my @glob = map { + s{^$choicedir/}{}; + s{$sffx$}{}; + $_ + } glob("$choicedir/$arg$sffx"); + + return @glob; +} + +sub doit { + my $acton = shift; + + my ( $conftgt, $conflink ); + if ( $obj eq 'module' ) { + if ( $acton eq 'cgi' && threaded() ) { + print + "Your MPM seems to be threaded. Selecting cgid instead of cgi.\n"; + $acton = 'cgid'; + } + + $conftgt = "$availdir/$acton.conf"; + if ( -e $conftgt ) { + $conflink = "$enabldir/$acton.conf"; + } + } + + my $tgt = "$availdir/$acton$sffx"; + my $link = "$enabldir/$acton$sffx"; + + if ( !-e $tgt ) { + if ( -l $link && !-e $link ) { + if ( $act eq 'disable' ) { + info("removing dangling symlink $link\n"); + unlink($link); + + # force a .conf path. It may exist as dangling link, too + $conflink = "$enabldir/$acton.conf"; + + if ( -l $conflink && !-e $conflink ) { + info("removing dangling symlink $conflink\n"); + unlink($conflink); + } + + return 1; + } + else { + error("$link is a dangling symlink!\n"); + } + } + + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + # exit silently, we are purging anyway + return 1; + } + + error("$name $acton does not exist!\n"); + return 0; + } + + # handle module dependencies + if ( $obj eq 'module' ) { + if ( $act eq 'enable' ) { + if ( $acton eq 'mpm_itk' ) { + warning( "MPM_ITK is a third party module that is not part " + . "of the official Apache HTTPD. It has seen less " + . "testing than the official MPM modules." ); + } + my @depends = get_deps("$availdir/$acton.load"); + do_deps( $acton, @depends ) or return 0; + + my @conflicts = get_deps( "$availdir/$acton.load", "Conflicts" ); + check_conflicts( $acton, @conflicts ) or return 0; + } + else { + my @depending; + foreach my $d ( glob("$enabldir/*.load") ) { + my @deps = get_deps($d); + if ( is_in( $acton, @deps ) ) { + $d =~ m,/([^/]+).load$,; + push @depending, $1; + } + } + if ( scalar @depending ) { + if ($force) { + do_deps( $acton, @depending ) or return 0; + } + else { + error( + "The following modules depend on $acton ", + "and need to be disabled first: @depending\n" + ); + return 0; + } + } + } + } + elsif ( $act eq 'enable' ) { + my @depends = get_deps("$availdir/$acton$sffx"); + warn_deps( $acton, @depends ) or return 0; + } + + if ( $act eq 'enable' ) { + my $check = check_link( $tgt, $link ); + if ( $check eq 'ok' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'ok' ) { + info("$name $acton already enabled\n"); + return 1; + } + elsif ( $confcheck eq 'missing' ) { + print "Enabling config file $acton.conf.\n"; + add_link( $conftgt, $conflink ) or return 0; + } + else { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + else { + info("$name $acton already enabled\n"); + return 1; + } + } + elsif ( $check eq 'missing' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'missing' ) { + add_link( $conftgt, $conflink ) or return 0; + } + elsif ( $confcheck ne 'ok' ) { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + + print "Enabling $obj $acton.\n"; + if ( $acton eq 'ssl' ) { + info( "See /usr/share/doc/apache2/README.Debian.gz on " + . "how to configure SSL and create self-signed certificates.\n" + ); + } + return add_link( $tgt, $link ) + && switch_marker( $obj, $act, $acton ); + } + else { + error("$name $acton not properly enabled: $check\n"); + return 0; + } + } + else { + if ( -e $link || -l $link ) { + remove_link($link); + if ( $conflink && -e $conflink ) { + remove_link($conflink); + } + switch_marker( $obj, $act, $acton ); + print "$name $acton disabled.\n"; + } + elsif ( $conflink && -e $conflink ) { + print "Disabling stale config file $acton.conf.\n"; + remove_link($conflink); + } + else { + info("$name $acton already disabled\n"); + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + } + return 1; + } + } + + return 1; +} + +sub get_deps { + my $file = shift; + my $type = shift || "Depends"; + + my $fd; + if ( !open( $fd, '<', $file ) ) { + error("Can't open $file: $!"); + return; + } + my $line; + while ( defined( $line = <$fd> ) ) { + chomp $line; + if ( $line =~ /^# $type:\s+(.*?)\s*$/ ) { + my $deps = $1; + return split( /[\n\s]+/, $deps ); + } + + # only check until the first non-empty non-comment line + last if ( $line !~ /^\s*(?:#.*)?$/ ); + } + return; +} + +sub do_deps { + my $acton = shift; + foreach my $d (@_) { + info("Considering dependency $d for $acton:\n"); + if ( !doit($d) ) { + error("Could not $act dependency $d for $acton, aborting\n"); + return 0; + } + } + return 1; +} + +sub warn_deps { + my $acton = shift; + my $modsenabldir = $ENV{APACHE_MODS_ENABLED} || "$confdir/mods-enabled"; + foreach my $d (@_) { + info("Checking dependency $d for $acton:\n"); + if ( !-e "$modsenabldir/$d.load" ) { + warning( + "Module $d is not enabled, but $acton depends on it, aborting\n" + ); + return 0; + } + } + return 1; +} + +sub check_conflicts { + my $acton = shift; + my $haderror = 0; + foreach my $d (@_) { + info("Considering conflict $d for $acton:\n"); + + my $tgt = "$availdir/$d$sffx"; + my $link = "$enabldir/$d$sffx"; + + my $confcheck = check_link( $tgt, $link ); + if ( $confcheck eq 'ok' ) { + error( + "Module $d is enabled - cannot proceed due to conflicts. It needs to be disabled first!\n" + ); + + # Don't return immediately, there could be several conflicts + $haderror++; + } + } + + if ($haderror) { + return 0; + } + + return 1; +} + +sub add_link { + my ( $tgt, $link ) = @_; + + # create relative link + if ( !symlink( File::Spec->abs2rel( $tgt, dirname($link) ), $link ) ) { + die("Could not create $link: $!\n"); + } + $request_reload = 1; + return 1; +} + +sub check_link { + my ( $tgt, $link ) = @_; + + if ( !-e $link ) { + if ( -l $link ) { + + # points to nowhere + info("Removing dangling link $link"); + unlink($link) or die "Could not remove $link\n"; + } + return 'missing'; + } + + if ( -e $link && !-l $link ) { + return "$link is a real file, not touching it"; + } + if ( realpath($link) ne realpath($tgt) ) { + return "$link exists but does not point to $tgt, not touching it"; + } + return 'ok'; +} + +sub remove_link { + my ($link) = @_; + + if ( -l $link ) { + unlink($link) or die "Could not remove $link: $!\n"; + } + elsif ( -e $link ) { + error("$link is not a symbolic link, not deleting\n"); + return 0; + } + $request_reload = 1; + return 1; +} + +sub threaded { + my $result = ""; + $result = qx{<%= node['apache']['apachectl'] %> -V | grep 'threaded'} + if -x '<%= node['apache']['apachectl'] %>'; + if ( $? != 0 ) { + + # config doesn't work + if ( -e "$enabldir/mpm_prefork.load" || -e "$enabldir/mpm_itk.load" ) + { + return 0; + } + elsif (-e "$enabldir/mpm_worker.load" + || -e "$enabldir/mpm_event.load" ) + { + return 1; + } + else { + error("Can't determine enabled MPM"); + + # do what user requested + return 0; + } + } + if ( $result =~ / no/ ) { + return 0; + } + elsif ( $result =~ / yes/ ) { + return 1; + } + else { + die("Can't parse output from apache2ctl -V:\n$result\n"); + } +} + +sub info { + print @_ if !$quiet; +} + +sub error { + print STDERR 'ERROR: ', @_; +} + +sub warning { + print STDERR 'WARNING: ', @_; +} + +sub is_in { + my $needle = shift; + foreach my $e (@_) { + return 1 if $needle eq $e; + } + return 0; +} + +sub read_env_file { + my $file = shift; + + -r $file or return; + my @lines = qx{env - sh -c '. $file && env'}; + if ($?) { + die "Could not read $file\n"; + } + + foreach my $l (@lines) { + chomp $l; + $l =~ /^(.*)?=(.*)$/ or die "Could not parse $file\n"; + $ENV{$1} = $2; + } +} + +sub switch_marker { + die('usage: switch_marker([module|site|conf], [enable|disable], $name)') + if @_ != 3; + my $which = shift; + my $what = shift; + my $name = shift; + + my $mode = "admin"; + $mode = "maint" if $maintmode; + + #print("switch_marker $which $what $name\n"); + # TODO: get rid of the magic string(s) + my $state_marker_dir = "$statedir/$what" . "d" . "_by_$mode"; + my $state_marker = "$state_marker_dir/$name"; + if ( !-d $state_marker_dir ) { + File::Path::mkpath("$state_marker_dir") + || error( + "Failed to create marker directory: '$state_marker_dir'\n"); + } + + # XXX: swap find with perl alternative + my @markers = qx{find "$statedir" -type f -a -name "$name"}; + chomp(@markers); + foreach (@markers) { + unless ( unlink $_ ) { + error("Failed to remove old marker '$_'!\n") && return 0; + } + } + unless ($purge) { + qx{touch "$state_marker"}; + if ( $? != 0 ) { + error("Failed to create marker '$state_marker'!\n") && return 0; + } + return 1; + } +} + +# vim: syntax=perl sw=4 sts=4 sr et diff --git a/cookbooks/apache2/templates/default/a2enconf.erb b/cookbooks/apache2/templates/default/a2enconf.erb new file mode 100644 index 0000000..6c2c262 --- /dev/null +++ b/cookbooks/apache2/templates/default/a2enconf.erb @@ -0,0 +1,532 @@ +#!/usr/bin/perl -w +# +# a2enmod by Stefan Fritsch +# Licensed under Apache License 2.0 +# +# The coding style is "perltidy -pbp" + +use strict; +use Cwd 'realpath'; +use File::Spec; +use File::Basename; +use File::Path; +use Getopt::Long; + +my $quiet; +my $force; +my $maintmode; +my $purge; + +Getopt::Long::Configure('bundling'); +GetOptions( + 'quiet|q' => \$quiet, + 'force|f' => \$force, + 'maintmode|m' => \$maintmode, + 'purge|p' => \$purge +) or exit 2; + +my $basename = basename($0); +$basename =~ /^a2(en|dis)(mod|site|conf)((?:-.+)?)$/ + or die "$basename call name unknown\n"; +my $act = $1; +my $obj = $2; +my $dir_suffix = $3; + +my $env_file = $ENV{APACHE_ENVVARS} + || ( + $ENV{APACHE_CONFDIR} + ? "$ENV{APACHE_CONFDIR}/envvars" + : "<%= node['apache']['dir'] %>$dir_suffix/envvars" + ); +$ENV{LANG} = 'C'; +read_env_file($env_file); + +$act .= 'able'; +my ( $name, $dir, $sffx, $reload ); +if ( $obj eq 'mod' ) { + $obj = 'module'; + $dir = 'mods'; + $sffx = '.load'; + $reload = 'restart'; +} +elsif ( $obj eq 'conf' ) { + $obj = 'conf'; + $dir = 'conf'; + $sffx = '.conf'; + $reload = 'reload'; +} +else { + $dir = 'sites'; + $sffx = '.conf'; + $reload = 'reload'; +} +$name = ucfirst($obj); + +my $confdir = $ENV{APACHE_CONFDIR} || "<%= node['apache']['dir'] %>$dir_suffix"; +my $availdir = $ENV{ uc("APACHE_${dir}_AVAILABLE") } + || "$confdir/$dir-available"; +my $enabldir = $ENV{ uc("APACHE_${dir}_ENABLED") } || "$confdir/$dir-enabled"; +my $statedir = $ENV{ uc("APACHE_STATE_DIRECTORY") } || "<%= node['apache']['lib_dir'] %>"; + +$statedir .= "/$obj"; + +my $choicedir = $act eq 'enable' ? $availdir : $enabldir; +my $linkdir = File::Spec->abs2rel( $availdir, $enabldir ); + +my $request_reload = 0; + +my $rc = 0; + +if ( !scalar @ARGV ) { + my @choices = myglob('*'); + print "Your choices are: @choices\n"; + print "Which ${obj}(s) do you want to $act (wildcards ok)?\n"; + my $input = <>; + @ARGV = split /\s+/, $input; + +} + +my @objs; +foreach my $arg (@ARGV) { + $arg =~ s/${sffx}$//; + my @glob = myglob($arg); + if ( !@glob ) { + error("No $obj found matching $arg!\n"); + $rc = 1; + } + else { + push @objs, @glob; + } +} + +foreach my $acton (@objs) { + doit($acton) or $rc = 1; +} + +info( + "To activate the new configuration, you need to run:\n service apache2 $reload\n" +) if $request_reload; + +exit($rc); + +############################################################################## + +sub myglob { + my $arg = shift; + + my @glob = map { + s{^$choicedir/}{}; + s{$sffx$}{}; + $_ + } glob("$choicedir/$arg$sffx"); + + return @glob; +} + +sub doit { + my $acton = shift; + + my ( $conftgt, $conflink ); + if ( $obj eq 'module' ) { + if ( $acton eq 'cgi' && threaded() ) { + print + "Your MPM seems to be threaded. Selecting cgid instead of cgi.\n"; + $acton = 'cgid'; + } + + $conftgt = "$availdir/$acton.conf"; + if ( -e $conftgt ) { + $conflink = "$enabldir/$acton.conf"; + } + } + + my $tgt = "$availdir/$acton$sffx"; + my $link = "$enabldir/$acton$sffx"; + + if ( !-e $tgt ) { + if ( -l $link && !-e $link ) { + if ( $act eq 'disable' ) { + info("removing dangling symlink $link\n"); + unlink($link); + + # force a .conf path. It may exist as dangling link, too + $conflink = "$enabldir/$acton.conf"; + + if ( -l $conflink && !-e $conflink ) { + info("removing dangling symlink $conflink\n"); + unlink($conflink); + } + + return 1; + } + else { + error("$link is a dangling symlink!\n"); + } + } + + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + # exit silently, we are purging anyway + return 1; + } + + error("$name $acton does not exist!\n"); + return 0; + } + + # handle module dependencies + if ( $obj eq 'module' ) { + if ( $act eq 'enable' ) { + if ( $acton eq 'mpm_itk' ) { + warning( "MPM_ITK is a third party module that is not part " + . "of the official Apache HTTPD. It has seen less " + . "testing than the official MPM modules." ); + } + my @depends = get_deps("$availdir/$acton.load"); + do_deps( $acton, @depends ) or return 0; + + my @conflicts = get_deps( "$availdir/$acton.load", "Conflicts" ); + check_conflicts( $acton, @conflicts ) or return 0; + } + else { + my @depending; + foreach my $d ( glob("$enabldir/*.load") ) { + my @deps = get_deps($d); + if ( is_in( $acton, @deps ) ) { + $d =~ m,/([^/]+).load$,; + push @depending, $1; + } + } + if ( scalar @depending ) { + if ($force) { + do_deps( $acton, @depending ) or return 0; + } + else { + error( + "The following modules depend on $acton ", + "and need to be disabled first: @depending\n" + ); + return 0; + } + } + } + } + elsif ( $act eq 'enable' ) { + my @depends = get_deps("$availdir/$acton$sffx"); + warn_deps( $acton, @depends ) or return 0; + } + + if ( $act eq 'enable' ) { + my $check = check_link( $tgt, $link ); + if ( $check eq 'ok' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'ok' ) { + info("$name $acton already enabled\n"); + return 1; + } + elsif ( $confcheck eq 'missing' ) { + print "Enabling config file $acton.conf.\n"; + add_link( $conftgt, $conflink ) or return 0; + } + else { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + else { + info("$name $acton already enabled\n"); + return 1; + } + } + elsif ( $check eq 'missing' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'missing' ) { + add_link( $conftgt, $conflink ) or return 0; + } + elsif ( $confcheck ne 'ok' ) { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + + print "Enabling $obj $acton.\n"; + if ( $acton eq 'ssl' ) { + info( "See /usr/share/doc/apache2/README.Debian.gz on " + . "how to configure SSL and create self-signed certificates.\n" + ); + } + return add_link( $tgt, $link ) + && switch_marker( $obj, $act, $acton ); + } + else { + error("$name $acton not properly enabled: $check\n"); + return 0; + } + } + else { + if ( -e $link || -l $link ) { + remove_link($link); + if ( $conflink && -e $conflink ) { + remove_link($conflink); + } + switch_marker( $obj, $act, $acton ); + print "$name $acton disabled.\n"; + } + elsif ( $conflink && -e $conflink ) { + print "Disabling stale config file $acton.conf.\n"; + remove_link($conflink); + } + else { + info("$name $acton already disabled\n"); + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + } + return 1; + } + } + + return 1; +} + +sub get_deps { + my $file = shift; + my $type = shift || "Depends"; + + my $fd; + if ( !open( $fd, '<', $file ) ) { + error("Can't open $file: $!"); + return; + } + my $line; + while ( defined( $line = <$fd> ) ) { + chomp $line; + if ( $line =~ /^# $type:\s+(.*?)\s*$/ ) { + my $deps = $1; + return split( /[\n\s]+/, $deps ); + } + + # only check until the first non-empty non-comment line + last if ( $line !~ /^\s*(?:#.*)?$/ ); + } + return; +} + +sub do_deps { + my $acton = shift; + foreach my $d (@_) { + info("Considering dependency $d for $acton:\n"); + if ( !doit($d) ) { + error("Could not $act dependency $d for $acton, aborting\n"); + return 0; + } + } + return 1; +} + +sub warn_deps { + my $acton = shift; + my $modsenabldir = $ENV{APACHE_MODS_ENABLED} || "$confdir/mods-enabled"; + foreach my $d (@_) { + info("Checking dependency $d for $acton:\n"); + if ( !-e "$modsenabldir/$d.load" ) { + warning( + "Module $d is not enabled, but $acton depends on it, aborting\n" + ); + return 0; + } + } + return 1; +} + +sub check_conflicts { + my $acton = shift; + my $haderror = 0; + foreach my $d (@_) { + info("Considering conflict $d for $acton:\n"); + + my $tgt = "$availdir/$d$sffx"; + my $link = "$enabldir/$d$sffx"; + + my $confcheck = check_link( $tgt, $link ); + if ( $confcheck eq 'ok' ) { + error( + "Module $d is enabled - cannot proceed due to conflicts. It needs to be disabled first!\n" + ); + + # Don't return immediately, there could be several conflicts + $haderror++; + } + } + + if ($haderror) { + return 0; + } + + return 1; +} + +sub add_link { + my ( $tgt, $link ) = @_; + + # create relative link + if ( !symlink( File::Spec->abs2rel( $tgt, dirname($link) ), $link ) ) { + die("Could not create $link: $!\n"); + } + $request_reload = 1; + return 1; +} + +sub check_link { + my ( $tgt, $link ) = @_; + + if ( !-e $link ) { + if ( -l $link ) { + + # points to nowhere + info("Removing dangling link $link"); + unlink($link) or die "Could not remove $link\n"; + } + return 'missing'; + } + + if ( -e $link && !-l $link ) { + return "$link is a real file, not touching it"; + } + if ( realpath($link) ne realpath($tgt) ) { + return "$link exists but does not point to $tgt, not touching it"; + } + return 'ok'; +} + +sub remove_link { + my ($link) = @_; + + if ( -l $link ) { + unlink($link) or die "Could not remove $link: $!\n"; + } + elsif ( -e $link ) { + error("$link is not a symbolic link, not deleting\n"); + return 0; + } + $request_reload = 1; + return 1; +} + +sub threaded { + my $result = ""; + $result = qx{<%= node['apache']['apachectl'] %> -V | grep 'threaded'} + if -x '<%= node['apache']['apachectl'] %>'; + if ( $? != 0 ) { + + # config doesn't work + if ( -e "$enabldir/mpm_prefork.load" || -e "$enabldir/mpm_itk.load" ) + { + return 0; + } + elsif (-e "$enabldir/mpm_worker.load" + || -e "$enabldir/mpm_event.load" ) + { + return 1; + } + else { + error("Can't determine enabled MPM"); + + # do what user requested + return 0; + } + } + if ( $result =~ / no/ ) { + return 0; + } + elsif ( $result =~ / yes/ ) { + return 1; + } + else { + die("Can't parse output from apache2ctl -V:\n$result\n"); + } +} + +sub info { + print @_ if !$quiet; +} + +sub error { + print STDERR 'ERROR: ', @_; +} + +sub warning { + print STDERR 'WARNING: ', @_; +} + +sub is_in { + my $needle = shift; + foreach my $e (@_) { + return 1 if $needle eq $e; + } + return 0; +} + +sub read_env_file { + my $file = shift; + + -r $file or return; + my @lines = qx{env - sh -c '. $file && env'}; + if ($?) { + die "Could not read $file\n"; + } + + foreach my $l (@lines) { + chomp $l; + $l =~ /^(.*)?=(.*)$/ or die "Could not parse $file\n"; + $ENV{$1} = $2; + } +} + +sub switch_marker { + die('usage: switch_marker([module|site|conf], [enable|disable], $name)') + if @_ != 3; + my $which = shift; + my $what = shift; + my $name = shift; + + my $mode = "admin"; + $mode = "maint" if $maintmode; + + #print("switch_marker $which $what $name\n"); + # TODO: get rid of the magic string(s) + my $state_marker_dir = "$statedir/$what" . "d" . "_by_$mode"; + my $state_marker = "$state_marker_dir/$name"; + if ( !-d $state_marker_dir ) { + File::Path::mkpath("$state_marker_dir") + || error( + "Failed to create marker directory: '$state_marker_dir'\n"); + } + + # XXX: swap find with perl alternative + my @markers = qx{find "$statedir" -type f -a -name "$name"}; + chomp(@markers); + foreach (@markers) { + unless ( unlink $_ ) { + error("Failed to remove old marker '$_'!\n") && return 0; + } + } + unless ($purge) { + qx{touch "$state_marker"}; + if ( $? != 0 ) { + error("Failed to create marker '$state_marker'!\n") && return 0; + } + return 1; + } +} + +# vim: syntax=perl sw=4 sts=4 sr et diff --git a/cookbooks/apache2/templates/default/a2enmod.erb b/cookbooks/apache2/templates/default/a2enmod.erb new file mode 100644 index 0000000..6c2c262 --- /dev/null +++ b/cookbooks/apache2/templates/default/a2enmod.erb @@ -0,0 +1,532 @@ +#!/usr/bin/perl -w +# +# a2enmod by Stefan Fritsch +# Licensed under Apache License 2.0 +# +# The coding style is "perltidy -pbp" + +use strict; +use Cwd 'realpath'; +use File::Spec; +use File::Basename; +use File::Path; +use Getopt::Long; + +my $quiet; +my $force; +my $maintmode; +my $purge; + +Getopt::Long::Configure('bundling'); +GetOptions( + 'quiet|q' => \$quiet, + 'force|f' => \$force, + 'maintmode|m' => \$maintmode, + 'purge|p' => \$purge +) or exit 2; + +my $basename = basename($0); +$basename =~ /^a2(en|dis)(mod|site|conf)((?:-.+)?)$/ + or die "$basename call name unknown\n"; +my $act = $1; +my $obj = $2; +my $dir_suffix = $3; + +my $env_file = $ENV{APACHE_ENVVARS} + || ( + $ENV{APACHE_CONFDIR} + ? "$ENV{APACHE_CONFDIR}/envvars" + : "<%= node['apache']['dir'] %>$dir_suffix/envvars" + ); +$ENV{LANG} = 'C'; +read_env_file($env_file); + +$act .= 'able'; +my ( $name, $dir, $sffx, $reload ); +if ( $obj eq 'mod' ) { + $obj = 'module'; + $dir = 'mods'; + $sffx = '.load'; + $reload = 'restart'; +} +elsif ( $obj eq 'conf' ) { + $obj = 'conf'; + $dir = 'conf'; + $sffx = '.conf'; + $reload = 'reload'; +} +else { + $dir = 'sites'; + $sffx = '.conf'; + $reload = 'reload'; +} +$name = ucfirst($obj); + +my $confdir = $ENV{APACHE_CONFDIR} || "<%= node['apache']['dir'] %>$dir_suffix"; +my $availdir = $ENV{ uc("APACHE_${dir}_AVAILABLE") } + || "$confdir/$dir-available"; +my $enabldir = $ENV{ uc("APACHE_${dir}_ENABLED") } || "$confdir/$dir-enabled"; +my $statedir = $ENV{ uc("APACHE_STATE_DIRECTORY") } || "<%= node['apache']['lib_dir'] %>"; + +$statedir .= "/$obj"; + +my $choicedir = $act eq 'enable' ? $availdir : $enabldir; +my $linkdir = File::Spec->abs2rel( $availdir, $enabldir ); + +my $request_reload = 0; + +my $rc = 0; + +if ( !scalar @ARGV ) { + my @choices = myglob('*'); + print "Your choices are: @choices\n"; + print "Which ${obj}(s) do you want to $act (wildcards ok)?\n"; + my $input = <>; + @ARGV = split /\s+/, $input; + +} + +my @objs; +foreach my $arg (@ARGV) { + $arg =~ s/${sffx}$//; + my @glob = myglob($arg); + if ( !@glob ) { + error("No $obj found matching $arg!\n"); + $rc = 1; + } + else { + push @objs, @glob; + } +} + +foreach my $acton (@objs) { + doit($acton) or $rc = 1; +} + +info( + "To activate the new configuration, you need to run:\n service apache2 $reload\n" +) if $request_reload; + +exit($rc); + +############################################################################## + +sub myglob { + my $arg = shift; + + my @glob = map { + s{^$choicedir/}{}; + s{$sffx$}{}; + $_ + } glob("$choicedir/$arg$sffx"); + + return @glob; +} + +sub doit { + my $acton = shift; + + my ( $conftgt, $conflink ); + if ( $obj eq 'module' ) { + if ( $acton eq 'cgi' && threaded() ) { + print + "Your MPM seems to be threaded. Selecting cgid instead of cgi.\n"; + $acton = 'cgid'; + } + + $conftgt = "$availdir/$acton.conf"; + if ( -e $conftgt ) { + $conflink = "$enabldir/$acton.conf"; + } + } + + my $tgt = "$availdir/$acton$sffx"; + my $link = "$enabldir/$acton$sffx"; + + if ( !-e $tgt ) { + if ( -l $link && !-e $link ) { + if ( $act eq 'disable' ) { + info("removing dangling symlink $link\n"); + unlink($link); + + # force a .conf path. It may exist as dangling link, too + $conflink = "$enabldir/$acton.conf"; + + if ( -l $conflink && !-e $conflink ) { + info("removing dangling symlink $conflink\n"); + unlink($conflink); + } + + return 1; + } + else { + error("$link is a dangling symlink!\n"); + } + } + + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + # exit silently, we are purging anyway + return 1; + } + + error("$name $acton does not exist!\n"); + return 0; + } + + # handle module dependencies + if ( $obj eq 'module' ) { + if ( $act eq 'enable' ) { + if ( $acton eq 'mpm_itk' ) { + warning( "MPM_ITK is a third party module that is not part " + . "of the official Apache HTTPD. It has seen less " + . "testing than the official MPM modules." ); + } + my @depends = get_deps("$availdir/$acton.load"); + do_deps( $acton, @depends ) or return 0; + + my @conflicts = get_deps( "$availdir/$acton.load", "Conflicts" ); + check_conflicts( $acton, @conflicts ) or return 0; + } + else { + my @depending; + foreach my $d ( glob("$enabldir/*.load") ) { + my @deps = get_deps($d); + if ( is_in( $acton, @deps ) ) { + $d =~ m,/([^/]+).load$,; + push @depending, $1; + } + } + if ( scalar @depending ) { + if ($force) { + do_deps( $acton, @depending ) or return 0; + } + else { + error( + "The following modules depend on $acton ", + "and need to be disabled first: @depending\n" + ); + return 0; + } + } + } + } + elsif ( $act eq 'enable' ) { + my @depends = get_deps("$availdir/$acton$sffx"); + warn_deps( $acton, @depends ) or return 0; + } + + if ( $act eq 'enable' ) { + my $check = check_link( $tgt, $link ); + if ( $check eq 'ok' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'ok' ) { + info("$name $acton already enabled\n"); + return 1; + } + elsif ( $confcheck eq 'missing' ) { + print "Enabling config file $acton.conf.\n"; + add_link( $conftgt, $conflink ) or return 0; + } + else { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + else { + info("$name $acton already enabled\n"); + return 1; + } + } + elsif ( $check eq 'missing' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'missing' ) { + add_link( $conftgt, $conflink ) or return 0; + } + elsif ( $confcheck ne 'ok' ) { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + + print "Enabling $obj $acton.\n"; + if ( $acton eq 'ssl' ) { + info( "See /usr/share/doc/apache2/README.Debian.gz on " + . "how to configure SSL and create self-signed certificates.\n" + ); + } + return add_link( $tgt, $link ) + && switch_marker( $obj, $act, $acton ); + } + else { + error("$name $acton not properly enabled: $check\n"); + return 0; + } + } + else { + if ( -e $link || -l $link ) { + remove_link($link); + if ( $conflink && -e $conflink ) { + remove_link($conflink); + } + switch_marker( $obj, $act, $acton ); + print "$name $acton disabled.\n"; + } + elsif ( $conflink && -e $conflink ) { + print "Disabling stale config file $acton.conf.\n"; + remove_link($conflink); + } + else { + info("$name $acton already disabled\n"); + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + } + return 1; + } + } + + return 1; +} + +sub get_deps { + my $file = shift; + my $type = shift || "Depends"; + + my $fd; + if ( !open( $fd, '<', $file ) ) { + error("Can't open $file: $!"); + return; + } + my $line; + while ( defined( $line = <$fd> ) ) { + chomp $line; + if ( $line =~ /^# $type:\s+(.*?)\s*$/ ) { + my $deps = $1; + return split( /[\n\s]+/, $deps ); + } + + # only check until the first non-empty non-comment line + last if ( $line !~ /^\s*(?:#.*)?$/ ); + } + return; +} + +sub do_deps { + my $acton = shift; + foreach my $d (@_) { + info("Considering dependency $d for $acton:\n"); + if ( !doit($d) ) { + error("Could not $act dependency $d for $acton, aborting\n"); + return 0; + } + } + return 1; +} + +sub warn_deps { + my $acton = shift; + my $modsenabldir = $ENV{APACHE_MODS_ENABLED} || "$confdir/mods-enabled"; + foreach my $d (@_) { + info("Checking dependency $d for $acton:\n"); + if ( !-e "$modsenabldir/$d.load" ) { + warning( + "Module $d is not enabled, but $acton depends on it, aborting\n" + ); + return 0; + } + } + return 1; +} + +sub check_conflicts { + my $acton = shift; + my $haderror = 0; + foreach my $d (@_) { + info("Considering conflict $d for $acton:\n"); + + my $tgt = "$availdir/$d$sffx"; + my $link = "$enabldir/$d$sffx"; + + my $confcheck = check_link( $tgt, $link ); + if ( $confcheck eq 'ok' ) { + error( + "Module $d is enabled - cannot proceed due to conflicts. It needs to be disabled first!\n" + ); + + # Don't return immediately, there could be several conflicts + $haderror++; + } + } + + if ($haderror) { + return 0; + } + + return 1; +} + +sub add_link { + my ( $tgt, $link ) = @_; + + # create relative link + if ( !symlink( File::Spec->abs2rel( $tgt, dirname($link) ), $link ) ) { + die("Could not create $link: $!\n"); + } + $request_reload = 1; + return 1; +} + +sub check_link { + my ( $tgt, $link ) = @_; + + if ( !-e $link ) { + if ( -l $link ) { + + # points to nowhere + info("Removing dangling link $link"); + unlink($link) or die "Could not remove $link\n"; + } + return 'missing'; + } + + if ( -e $link && !-l $link ) { + return "$link is a real file, not touching it"; + } + if ( realpath($link) ne realpath($tgt) ) { + return "$link exists but does not point to $tgt, not touching it"; + } + return 'ok'; +} + +sub remove_link { + my ($link) = @_; + + if ( -l $link ) { + unlink($link) or die "Could not remove $link: $!\n"; + } + elsif ( -e $link ) { + error("$link is not a symbolic link, not deleting\n"); + return 0; + } + $request_reload = 1; + return 1; +} + +sub threaded { + my $result = ""; + $result = qx{<%= node['apache']['apachectl'] %> -V | grep 'threaded'} + if -x '<%= node['apache']['apachectl'] %>'; + if ( $? != 0 ) { + + # config doesn't work + if ( -e "$enabldir/mpm_prefork.load" || -e "$enabldir/mpm_itk.load" ) + { + return 0; + } + elsif (-e "$enabldir/mpm_worker.load" + || -e "$enabldir/mpm_event.load" ) + { + return 1; + } + else { + error("Can't determine enabled MPM"); + + # do what user requested + return 0; + } + } + if ( $result =~ / no/ ) { + return 0; + } + elsif ( $result =~ / yes/ ) { + return 1; + } + else { + die("Can't parse output from apache2ctl -V:\n$result\n"); + } +} + +sub info { + print @_ if !$quiet; +} + +sub error { + print STDERR 'ERROR: ', @_; +} + +sub warning { + print STDERR 'WARNING: ', @_; +} + +sub is_in { + my $needle = shift; + foreach my $e (@_) { + return 1 if $needle eq $e; + } + return 0; +} + +sub read_env_file { + my $file = shift; + + -r $file or return; + my @lines = qx{env - sh -c '. $file && env'}; + if ($?) { + die "Could not read $file\n"; + } + + foreach my $l (@lines) { + chomp $l; + $l =~ /^(.*)?=(.*)$/ or die "Could not parse $file\n"; + $ENV{$1} = $2; + } +} + +sub switch_marker { + die('usage: switch_marker([module|site|conf], [enable|disable], $name)') + if @_ != 3; + my $which = shift; + my $what = shift; + my $name = shift; + + my $mode = "admin"; + $mode = "maint" if $maintmode; + + #print("switch_marker $which $what $name\n"); + # TODO: get rid of the magic string(s) + my $state_marker_dir = "$statedir/$what" . "d" . "_by_$mode"; + my $state_marker = "$state_marker_dir/$name"; + if ( !-d $state_marker_dir ) { + File::Path::mkpath("$state_marker_dir") + || error( + "Failed to create marker directory: '$state_marker_dir'\n"); + } + + # XXX: swap find with perl alternative + my @markers = qx{find "$statedir" -type f -a -name "$name"}; + chomp(@markers); + foreach (@markers) { + unless ( unlink $_ ) { + error("Failed to remove old marker '$_'!\n") && return 0; + } + } + unless ($purge) { + qx{touch "$state_marker"}; + if ( $? != 0 ) { + error("Failed to create marker '$state_marker'!\n") && return 0; + } + return 1; + } +} + +# vim: syntax=perl sw=4 sts=4 sr et diff --git a/cookbooks/apache2/templates/default/a2ensite.erb b/cookbooks/apache2/templates/default/a2ensite.erb new file mode 100644 index 0000000..6c2c262 --- /dev/null +++ b/cookbooks/apache2/templates/default/a2ensite.erb @@ -0,0 +1,532 @@ +#!/usr/bin/perl -w +# +# a2enmod by Stefan Fritsch +# Licensed under Apache License 2.0 +# +# The coding style is "perltidy -pbp" + +use strict; +use Cwd 'realpath'; +use File::Spec; +use File::Basename; +use File::Path; +use Getopt::Long; + +my $quiet; +my $force; +my $maintmode; +my $purge; + +Getopt::Long::Configure('bundling'); +GetOptions( + 'quiet|q' => \$quiet, + 'force|f' => \$force, + 'maintmode|m' => \$maintmode, + 'purge|p' => \$purge +) or exit 2; + +my $basename = basename($0); +$basename =~ /^a2(en|dis)(mod|site|conf)((?:-.+)?)$/ + or die "$basename call name unknown\n"; +my $act = $1; +my $obj = $2; +my $dir_suffix = $3; + +my $env_file = $ENV{APACHE_ENVVARS} + || ( + $ENV{APACHE_CONFDIR} + ? "$ENV{APACHE_CONFDIR}/envvars" + : "<%= node['apache']['dir'] %>$dir_suffix/envvars" + ); +$ENV{LANG} = 'C'; +read_env_file($env_file); + +$act .= 'able'; +my ( $name, $dir, $sffx, $reload ); +if ( $obj eq 'mod' ) { + $obj = 'module'; + $dir = 'mods'; + $sffx = '.load'; + $reload = 'restart'; +} +elsif ( $obj eq 'conf' ) { + $obj = 'conf'; + $dir = 'conf'; + $sffx = '.conf'; + $reload = 'reload'; +} +else { + $dir = 'sites'; + $sffx = '.conf'; + $reload = 'reload'; +} +$name = ucfirst($obj); + +my $confdir = $ENV{APACHE_CONFDIR} || "<%= node['apache']['dir'] %>$dir_suffix"; +my $availdir = $ENV{ uc("APACHE_${dir}_AVAILABLE") } + || "$confdir/$dir-available"; +my $enabldir = $ENV{ uc("APACHE_${dir}_ENABLED") } || "$confdir/$dir-enabled"; +my $statedir = $ENV{ uc("APACHE_STATE_DIRECTORY") } || "<%= node['apache']['lib_dir'] %>"; + +$statedir .= "/$obj"; + +my $choicedir = $act eq 'enable' ? $availdir : $enabldir; +my $linkdir = File::Spec->abs2rel( $availdir, $enabldir ); + +my $request_reload = 0; + +my $rc = 0; + +if ( !scalar @ARGV ) { + my @choices = myglob('*'); + print "Your choices are: @choices\n"; + print "Which ${obj}(s) do you want to $act (wildcards ok)?\n"; + my $input = <>; + @ARGV = split /\s+/, $input; + +} + +my @objs; +foreach my $arg (@ARGV) { + $arg =~ s/${sffx}$//; + my @glob = myglob($arg); + if ( !@glob ) { + error("No $obj found matching $arg!\n"); + $rc = 1; + } + else { + push @objs, @glob; + } +} + +foreach my $acton (@objs) { + doit($acton) or $rc = 1; +} + +info( + "To activate the new configuration, you need to run:\n service apache2 $reload\n" +) if $request_reload; + +exit($rc); + +############################################################################## + +sub myglob { + my $arg = shift; + + my @glob = map { + s{^$choicedir/}{}; + s{$sffx$}{}; + $_ + } glob("$choicedir/$arg$sffx"); + + return @glob; +} + +sub doit { + my $acton = shift; + + my ( $conftgt, $conflink ); + if ( $obj eq 'module' ) { + if ( $acton eq 'cgi' && threaded() ) { + print + "Your MPM seems to be threaded. Selecting cgid instead of cgi.\n"; + $acton = 'cgid'; + } + + $conftgt = "$availdir/$acton.conf"; + if ( -e $conftgt ) { + $conflink = "$enabldir/$acton.conf"; + } + } + + my $tgt = "$availdir/$acton$sffx"; + my $link = "$enabldir/$acton$sffx"; + + if ( !-e $tgt ) { + if ( -l $link && !-e $link ) { + if ( $act eq 'disable' ) { + info("removing dangling symlink $link\n"); + unlink($link); + + # force a .conf path. It may exist as dangling link, too + $conflink = "$enabldir/$acton.conf"; + + if ( -l $conflink && !-e $conflink ) { + info("removing dangling symlink $conflink\n"); + unlink($conflink); + } + + return 1; + } + else { + error("$link is a dangling symlink!\n"); + } + } + + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + # exit silently, we are purging anyway + return 1; + } + + error("$name $acton does not exist!\n"); + return 0; + } + + # handle module dependencies + if ( $obj eq 'module' ) { + if ( $act eq 'enable' ) { + if ( $acton eq 'mpm_itk' ) { + warning( "MPM_ITK is a third party module that is not part " + . "of the official Apache HTTPD. It has seen less " + . "testing than the official MPM modules." ); + } + my @depends = get_deps("$availdir/$acton.load"); + do_deps( $acton, @depends ) or return 0; + + my @conflicts = get_deps( "$availdir/$acton.load", "Conflicts" ); + check_conflicts( $acton, @conflicts ) or return 0; + } + else { + my @depending; + foreach my $d ( glob("$enabldir/*.load") ) { + my @deps = get_deps($d); + if ( is_in( $acton, @deps ) ) { + $d =~ m,/([^/]+).load$,; + push @depending, $1; + } + } + if ( scalar @depending ) { + if ($force) { + do_deps( $acton, @depending ) or return 0; + } + else { + error( + "The following modules depend on $acton ", + "and need to be disabled first: @depending\n" + ); + return 0; + } + } + } + } + elsif ( $act eq 'enable' ) { + my @depends = get_deps("$availdir/$acton$sffx"); + warn_deps( $acton, @depends ) or return 0; + } + + if ( $act eq 'enable' ) { + my $check = check_link( $tgt, $link ); + if ( $check eq 'ok' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'ok' ) { + info("$name $acton already enabled\n"); + return 1; + } + elsif ( $confcheck eq 'missing' ) { + print "Enabling config file $acton.conf.\n"; + add_link( $conftgt, $conflink ) or return 0; + } + else { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + else { + info("$name $acton already enabled\n"); + return 1; + } + } + elsif ( $check eq 'missing' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'missing' ) { + add_link( $conftgt, $conflink ) or return 0; + } + elsif ( $confcheck ne 'ok' ) { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + + print "Enabling $obj $acton.\n"; + if ( $acton eq 'ssl' ) { + info( "See /usr/share/doc/apache2/README.Debian.gz on " + . "how to configure SSL and create self-signed certificates.\n" + ); + } + return add_link( $tgt, $link ) + && switch_marker( $obj, $act, $acton ); + } + else { + error("$name $acton not properly enabled: $check\n"); + return 0; + } + } + else { + if ( -e $link || -l $link ) { + remove_link($link); + if ( $conflink && -e $conflink ) { + remove_link($conflink); + } + switch_marker( $obj, $act, $acton ); + print "$name $acton disabled.\n"; + } + elsif ( $conflink && -e $conflink ) { + print "Disabling stale config file $acton.conf.\n"; + remove_link($conflink); + } + else { + info("$name $acton already disabled\n"); + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + } + return 1; + } + } + + return 1; +} + +sub get_deps { + my $file = shift; + my $type = shift || "Depends"; + + my $fd; + if ( !open( $fd, '<', $file ) ) { + error("Can't open $file: $!"); + return; + } + my $line; + while ( defined( $line = <$fd> ) ) { + chomp $line; + if ( $line =~ /^# $type:\s+(.*?)\s*$/ ) { + my $deps = $1; + return split( /[\n\s]+/, $deps ); + } + + # only check until the first non-empty non-comment line + last if ( $line !~ /^\s*(?:#.*)?$/ ); + } + return; +} + +sub do_deps { + my $acton = shift; + foreach my $d (@_) { + info("Considering dependency $d for $acton:\n"); + if ( !doit($d) ) { + error("Could not $act dependency $d for $acton, aborting\n"); + return 0; + } + } + return 1; +} + +sub warn_deps { + my $acton = shift; + my $modsenabldir = $ENV{APACHE_MODS_ENABLED} || "$confdir/mods-enabled"; + foreach my $d (@_) { + info("Checking dependency $d for $acton:\n"); + if ( !-e "$modsenabldir/$d.load" ) { + warning( + "Module $d is not enabled, but $acton depends on it, aborting\n" + ); + return 0; + } + } + return 1; +} + +sub check_conflicts { + my $acton = shift; + my $haderror = 0; + foreach my $d (@_) { + info("Considering conflict $d for $acton:\n"); + + my $tgt = "$availdir/$d$sffx"; + my $link = "$enabldir/$d$sffx"; + + my $confcheck = check_link( $tgt, $link ); + if ( $confcheck eq 'ok' ) { + error( + "Module $d is enabled - cannot proceed due to conflicts. It needs to be disabled first!\n" + ); + + # Don't return immediately, there could be several conflicts + $haderror++; + } + } + + if ($haderror) { + return 0; + } + + return 1; +} + +sub add_link { + my ( $tgt, $link ) = @_; + + # create relative link + if ( !symlink( File::Spec->abs2rel( $tgt, dirname($link) ), $link ) ) { + die("Could not create $link: $!\n"); + } + $request_reload = 1; + return 1; +} + +sub check_link { + my ( $tgt, $link ) = @_; + + if ( !-e $link ) { + if ( -l $link ) { + + # points to nowhere + info("Removing dangling link $link"); + unlink($link) or die "Could not remove $link\n"; + } + return 'missing'; + } + + if ( -e $link && !-l $link ) { + return "$link is a real file, not touching it"; + } + if ( realpath($link) ne realpath($tgt) ) { + return "$link exists but does not point to $tgt, not touching it"; + } + return 'ok'; +} + +sub remove_link { + my ($link) = @_; + + if ( -l $link ) { + unlink($link) or die "Could not remove $link: $!\n"; + } + elsif ( -e $link ) { + error("$link is not a symbolic link, not deleting\n"); + return 0; + } + $request_reload = 1; + return 1; +} + +sub threaded { + my $result = ""; + $result = qx{<%= node['apache']['apachectl'] %> -V | grep 'threaded'} + if -x '<%= node['apache']['apachectl'] %>'; + if ( $? != 0 ) { + + # config doesn't work + if ( -e "$enabldir/mpm_prefork.load" || -e "$enabldir/mpm_itk.load" ) + { + return 0; + } + elsif (-e "$enabldir/mpm_worker.load" + || -e "$enabldir/mpm_event.load" ) + { + return 1; + } + else { + error("Can't determine enabled MPM"); + + # do what user requested + return 0; + } + } + if ( $result =~ / no/ ) { + return 0; + } + elsif ( $result =~ / yes/ ) { + return 1; + } + else { + die("Can't parse output from apache2ctl -V:\n$result\n"); + } +} + +sub info { + print @_ if !$quiet; +} + +sub error { + print STDERR 'ERROR: ', @_; +} + +sub warning { + print STDERR 'WARNING: ', @_; +} + +sub is_in { + my $needle = shift; + foreach my $e (@_) { + return 1 if $needle eq $e; + } + return 0; +} + +sub read_env_file { + my $file = shift; + + -r $file or return; + my @lines = qx{env - sh -c '. $file && env'}; + if ($?) { + die "Could not read $file\n"; + } + + foreach my $l (@lines) { + chomp $l; + $l =~ /^(.*)?=(.*)$/ or die "Could not parse $file\n"; + $ENV{$1} = $2; + } +} + +sub switch_marker { + die('usage: switch_marker([module|site|conf], [enable|disable], $name)') + if @_ != 3; + my $which = shift; + my $what = shift; + my $name = shift; + + my $mode = "admin"; + $mode = "maint" if $maintmode; + + #print("switch_marker $which $what $name\n"); + # TODO: get rid of the magic string(s) + my $state_marker_dir = "$statedir/$what" . "d" . "_by_$mode"; + my $state_marker = "$state_marker_dir/$name"; + if ( !-d $state_marker_dir ) { + File::Path::mkpath("$state_marker_dir") + || error( + "Failed to create marker directory: '$state_marker_dir'\n"); + } + + # XXX: swap find with perl alternative + my @markers = qx{find "$statedir" -type f -a -name "$name"}; + chomp(@markers); + foreach (@markers) { + unless ( unlink $_ ) { + error("Failed to remove old marker '$_'!\n") && return 0; + } + } + unless ($purge) { + qx{touch "$state_marker"}; + if ( $? != 0 ) { + error("Failed to create marker '$state_marker'!\n") && return 0; + } + return 1; + } +} + +# vim: syntax=perl sw=4 sts=4 sr et diff --git a/cookbooks/apache2/templates/default/apache2.conf.erb b/cookbooks/apache2/templates/default/apache2.conf.erb new file mode 100644 index 0000000..72f93bf --- /dev/null +++ b/cookbooks/apache2/templates/default/apache2.conf.erb @@ -0,0 +1,260 @@ +# +# Generated by Chef +# +# Based on the Ubuntu apache2.conf + +ServerRoot "<%= node['apache']['dir'] %>" + +# +# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. +# +<% if node['apache']['version'] == '2.2' -%> +LockFile <%= node['apache']['lock_dir'] %>/accept.lock +<% elsif node['apache']['version'] == '2.4' -%> +Mutex file:<%= node['apache']['lock_dir'] %> default +<% end -%> + +# +# PidFile: The file in which the server should record its process +# identification number when it starts. +# +PidFile <%= node['apache']['pid_file'] %> + +# +# Timeout: The number of seconds before receives and sends time out. +# +Timeout <%= node['apache']['timeout'] %> + +# +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +# +KeepAlive <%= node['apache']['keepalive'] %> + +# +# 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 <%= node['apache']['keepaliverequests'] %> + +# +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. +# +KeepAliveTimeout <%= node['apache']['keepalivetimeout'] %> + +# +User <%= node['apache']['user'] %> +Group <%= node['apache']['group'] %> +# + +<% if node['apache']['version'] == '2.4' -%> +# Sets the default security model of the Apache2 HTTPD server. It does +# not allow access to the root filesystem outside of /usr/share and <%= node['apache']['docroot_dir'] %>. +# If your system is serving content from a sub-directory in /srv you must allow +# access in conf-enabled, or in any related virtual host. e.g. +# +# +# Options Indexes FollowSymLinks +# AllowOverride None +# Require all granted +# +# + + Options FollowSymLinks + AllowOverride None + Require all denied + + + + AllowOverride None + Require all granted + + +> + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + +<% end -%> + +# +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. +# + +AccessFileName <%= node['apache']['access_file_name'] %> + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# +<% access_file_name_prefix = node['apache']['access_file_name'][0..2] if !node['apache']['access_file_name'].empty? + if access_file_name_prefix != '.ht' + file_name_prefix = '(' + access_file_name_prefix + '|.ht)' + else + file_name_prefix = '.ht' + end +%> +"> + <% if node['apache']['version'] == '2.2' -%> + Order allow,deny + Deny from all + <% elsif node['apache']['version'] == '2.4' -%> + Require all denied + <% end -%> + + +<% if node['apache']['version'] == '2.2' -%> +# +# 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 +<% end -%> + +# +# 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 +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +<% if node['apache']['error_log'] =~ /^syslog:/ || node['apache']['error_log'] =~ /^\|/ %> +ErrorLog <%= node['apache']['error_log'] %> +<% else %> +ErrorLog <%= node['apache']['log_dir'] %>/<%= node['apache']['error_log'] %> +<% end %> + +# +# LogLevel: Control the number of messages logged to the error_log. +# Possible values include: debug, info, notice, warn, error, crit, +# alert, emerg. +# +LogLevel warn + +# COOK-1021: Dummy LoadModule directive to aid module installations +#LoadModule dummy_module modules/mod_dummy.so + +# Include module configuration: +<% if node['apache']['version'] == '2.2' -%> +Include <%= node['apache']['dir'] %>/mods-enabled/*.load +Include <%= node['apache']['dir'] %>/mods-enabled/*.conf +<% elsif node['apache']['version'] == '2.4' -%> +IncludeOptional <%= node['apache']['dir'] %>/mods-enabled/*.load +IncludeOptional <%= node['apache']['dir'] %>/mods-enabled/*.conf +<% end -%> + +<% if %w[freebsd].include?(node['platform_family']) -%> + + AcceptFilter http none + AcceptFilter https none + +<% end %> + +# Include ports listing +Include <%= node['apache']['dir'] %>/ports.conf + +# +# The following directives define some format nicknames for use with +# a CustomLog directive (see below). +# +LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined +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 +# +<% if node['apache']['version'] == '2.2' -%> +# 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 +# + +# +# Putting this all together, we can internationalize error responses. +# +# We use Alias to redirect any /error/HTTP_.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_.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 +# /usr/share/apache2/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. +# +# The internationalized error documents require mod_alias, mod_include +# and mod_negotiation. To activate them, uncomment the following 30 lines. + +# Alias /error/ "/usr/share/apache2/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 nl sv pt-br ro +# ForceLanguagePriority Prefer Fallback +# +# +# 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 +<% end -%> + +<% if node['apache']['version'] == '2.4' -%> +# Include generic snippets of statements +IncludeOptional <%= node['apache']['dir'] %>/conf-enabled/*.conf + +# Include the virtual host configurations: +IncludeOptional <%= node['apache']['dir'] %>/sites-enabled/*.conf +<% else -%> +# Include generic snippets of statements +Include <%= node['apache']['dir'] %>/conf-enabled/*.conf + +# Include the virtual host configurations: +Include <%= node['apache']['dir'] %>/sites-enabled/*.conf +<% end -%> diff --git a/cookbooks/apache2/templates/default/charset.conf.erb b/cookbooks/apache2/templates/default/charset.conf.erb new file mode 100644 index 0000000..40d7198 --- /dev/null +++ b/cookbooks/apache2/templates/default/charset.conf.erb @@ -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/cookbooks/apache2/templates/default/default-site.conf.erb b/cookbooks/apache2/templates/default/default-site.conf.erb new file mode 100644 index 0000000..d0e299a --- /dev/null +++ b/cookbooks/apache2/templates/default/default-site.conf.erb @@ -0,0 +1,71 @@ +> + ServerAdmin <%= node['apache']['contact'] %> + + DocumentRoot <%= node['apache']['docroot_dir'] %>/ + + Options FollowSymLinks + AllowOverride None + + + /> + Options Indexes FollowSymLinks MultiViews + AllowOverride None + <% if node['apache']['version'] == '2.4' -%> + Require all granted + # This directive allows us to have apache2's default start page + # in /apache2-default/, but still have / go to the right place + #RedirectMatch ^/$ /apache2-default/ + <% elsif node['apache']['version'] == '2.2' -%> + Order allow,deny + Allow from all + <% end -%> + + + ScriptAlias /cgi-bin/ <%= node['apache']['cgibin_dir'] %>/ + "> + AllowOverride None + Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch + <% if node['apache']['version'] == '2.4' -%> + Require all granted + <% elsif node['apache']['version'] == '2.2' -%> + Order allow,deny + Allow from all + <% end -%> + + + ErrorLog <%= node['apache']['log_dir'] %>/<%= node['apache']['error_log'] %> + + # Possible values include: debug, info, notice, warn, error, crit, + # alert, emerg. + LogLevel warn + + CustomLog <%= node['apache']['log_dir'] %>/<%= node['apache']['access_log'] %> combined + ServerSignature On + + Alias /doc/ "/usr/share/doc/" + + Options Indexes MultiViews FollowSymLinks + AllowOverride None + <% if node['apache']['version'] == '2.2' -%> + Order deny,allow + Deny from all + Allow from 127.0.0.0/255.0.0.0 ::1/128 + <% elsif node['apache']['version'] == '2.4' -%> + Require ip 127.0.0.0/255.0.0.0 + Require ip ::1/128 + <% end -%> + + + <% if %w{ rhel fedora }.include?(node['platform_family']) -%> + # + # 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. + # + + Options -Indexes + ErrorDocument 403 /error/noindex.html + + <% end -%> + diff --git a/cookbooks/apache2/templates/default/envvars.erb b/cookbooks/apache2/templates/default/envvars.erb new file mode 100644 index 0000000..d0bc03c --- /dev/null +++ b/cookbooks/apache2/templates/default/envvars.erb @@ -0,0 +1,43 @@ +# envvars - default environment variables for apache2ctl + +# this won't be correct after changing uid +unset HOME + +# Since there is no sane way to get the parsed apache2 config in scripts, some +# settings are defined via environment variables and then used in apache2ctl, +# /etc/init.d/apache2, /etc/logrotate.d/apache2, etc. +export APACHE_RUN_USER=<%= node['apache']['user'] %> +export APACHE_RUN_GROUP=<%= node['apache']['group'] %> +# temporary state file location. This might be changed to /run in Wheezy+1 +export APACHE_PID_FILE=<%= node['apache']['pid_file'] %> +export APACHE_RUN_DIR=<%= node['apache']['run_dir'] %> +export APACHE_LOCK_DIR=<%= node['apache']['lock_dir'] %> +export APACHE_LOG_DIR=<%= node['apache']['log_dir'] %> + +## The locale used by some modules like mod_dav +<%- if node['apache']['locale'] != 'system' %> +export LANG=<%= node['apache']['locale'] %> +export LC_ALL=<%= node['apache']['locale'] %> +<%- else %> +## Uncomment the following line to use the system default locale instead: +. /etc/default/locale +export LANG +<%- end %> + + +## The command to get the status for 'apache2ctl status'. +## Some packages providing 'www-browser' need '--dump' instead of '-dump'. +#export APACHE_LYNX='www-browser -dump' + +## If you need a higher file descriptor limit, uncomment and adjust the +## following line (default is 8192): +#APACHE_ULIMIT_MAX_FILES='ulimit -n 65536' + +## If you would like to pass arguments to the web server, add them below +## to the APACHE_ARGUMENTS environment. +#export APACHE_ARGUMENTS='' + +## Enable the debug mode for maintainer scripts. +## This will produce a verbose output on package installations of web server modules and web application +## installations which interact with Apache +#export APACHE2_MAINTSCRIPT_DEBUG=1 diff --git a/cookbooks/apache2/templates/default/etc-sysconfig-httpd.erb b/cookbooks/apache2/templates/default/etc-sysconfig-httpd.erb new file mode 100644 index 0000000..71ae7cf --- /dev/null +++ b/cookbooks/apache2/templates/default/etc-sysconfig-httpd.erb @@ -0,0 +1,35 @@ +# This file is managed by Chef. Changes will be overwritten. + +# +# 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=<%= node['apache']['binary'] %> + +# +# 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=<%= node['apache']['locale'] %> + +# +# By default, the httpd process will create the file +# /var/run/httpd/httpd.pid in which it records its process +# identification number when it starts. If an alternate location is +# specified in httpd.conf (via the PidFile directive), the new +# location needs to be reported in the PIDFILE. +# +PIDFILE=<%= node['apache']['pid_file'] %> + +<% node['apache']['sysconfig_additional_params'].each do |k,v| %> +<%= "#{k}=#{v}" %> +<% end %> diff --git a/cookbooks/apache2/templates/default/mods/README b/cookbooks/apache2/templates/default/mods/README new file mode 100644 index 0000000..df9f0bc --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/README @@ -0,0 +1,2 @@ +These configs are taken from a Debian apache2.2-common 2.2.11-3 install. They +work on CentOS 5.3 with a few conditions using erb. diff --git a/cookbooks/apache2/templates/default/mods/actions.conf.erb b/cookbooks/apache2/templates/default/mods/actions.conf.erb new file mode 100644 index 0000000..147a645 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/actions.conf.erb @@ -0,0 +1,9 @@ + + # + # 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 + # + diff --git a/cookbooks/apache2/templates/default/mods/alias.conf.erb b/cookbooks/apache2/templates/default/mods/alias.conf.erb new file mode 100644 index 0000000..80bef61 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/alias.conf.erb @@ -0,0 +1,27 @@ + + # + # 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/". If the fakename is slash-terminated, then the + # realname must also be slash terminated, and if the fakename omits the + # trailing slash, the realname must also omit it. + # + # We include the /icons/ alias for FancyIndexed directory listings. If + # you do not use FancyIndexing, you may comment this out. + # + Alias /icons/ "<%= node['apache']['icondir'] %>/" + + "> + Options Indexes MultiViews + AllowOverride None +<% if node['apache']['version'] == "2.4" -%> + Require all granted +<% else -%> + Order allow,deny + Allow from all +<% end -%> + + diff --git a/cookbooks/apache2/templates/default/mods/auth_cas.conf.erb b/cookbooks/apache2/templates/default/mods/auth_cas.conf.erb new file mode 100644 index 0000000..1f23f8e --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/auth_cas.conf.erb @@ -0,0 +1 @@ +CASCookiePath <%= node['apache']['cache_dir'] %>/mod_auth_cas/ diff --git a/cookbooks/apache2/templates/default/mods/auth_cas.load.erb b/cookbooks/apache2/templates/default/mods/auth_cas.load.erb new file mode 100644 index 0000000..5dc31a5 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/auth_cas.load.erb @@ -0,0 +1 @@ +LoadModule auth_cas_module <%= node['apache']['libexec_dir'] %>/mod_auth_cas.so diff --git a/cookbooks/apache2/templates/default/mods/authopenid.load.erb b/cookbooks/apache2/templates/default/mods/authopenid.load.erb new file mode 100644 index 0000000..7042a7e --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/authopenid.load.erb @@ -0,0 +1 @@ +LoadModule authopenid_module <%= node['apache']['libexec_dir'] %>/mod_auth_openid.so diff --git a/cookbooks/apache2/templates/default/mods/autoindex.conf.erb b/cookbooks/apache2/templates/default/mods/autoindex.conf.erb new file mode 100644 index 0000000..5f86614 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/autoindex.conf.erb @@ -0,0 +1,100 @@ + + # + # Directives controlling the display of server-generated directory listings. + # + + # + # IndexOptions: Controls the appearance of server-generated directory + # listings. + # Remove/replace the "Charset=UTF-8" if you don't use UTF-8 for your filenames. + # + IndexOptions FancyIndexing VersionSort HTMLTable NameWidth=* DescriptionWidth=* Charset=UTF-8 + + # + # 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 x-bzip2 + + 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 + # It's a suffix rule, so simply matching "core" matches "score" as well ! + AddIcon /icons/bomb.gif /core + AddIcon (SND,/icons/sound2.gif) .ogg + AddIcon (VID,/icons/movie.gif) .ogm + + AddIcon /icons/back.gif .. + AddIcon /icons/hand.right.gif README + AddIcon /icons/folder.gif ^^DIRECTORY^^ + AddIcon /icons/blank.gif ^^BLANKICON^^ + + # Default icons for OpenDocument format + AddIcon /icons/odf6odt-20x22.png .odt + AddIcon /icons/odf6ods-20x22.png .ods + AddIcon /icons/odf6odp-20x22.png .odp + AddIcon /icons/odf6odg-20x22.png .odg + AddIcon /icons/odf6odc-20x22.png .odc + AddIcon /icons/odf6odf-20x22.png .odf + AddIcon /icons/odf6odb-20x22.png .odb + AddIcon /icons/odf6odi-20x22.png .odi + AddIcon /icons/odf6odm-20x22.png .odm + + AddIcon /icons/odf6ott-20x22.png .ott + AddIcon /icons/odf6ots-20x22.png .ots + AddIcon /icons/odf6otp-20x22.png .otp + AddIcon /icons/odf6otg-20x22.png .otg + AddIcon /icons/odf6otc-20x22.png .otc + AddIcon /icons/odf6otf-20x22.png .otf + AddIcon /icons/odf6oti-20x22.png .oti + AddIcon /icons/odf6oth-20x22.png .oth + + # + # 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 .??* *~ *# RCS CVS *,v *,t + diff --git a/cookbooks/apache2/templates/default/mods/cache_disk.conf.erb b/cookbooks/apache2/templates/default/mods/cache_disk.conf.erb new file mode 100644 index 0000000..87117b4 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/cache_disk.conf.erb @@ -0,0 +1,23 @@ + + # cache cleaning is done by htcacheclean, which can be configured in + # /etc/default/apache2 + # + # For further information, see the comments in that file, + # /usr/share/doc/apache2/README.Debian, and the htcacheclean(8) + # man page. + + # This path must be the same as the one in /etc/default/apache2 + CacheRoot /var/cache/apache2/mod_cache_disk + + # This will also cache local documents. It usually makes more sense to + # put this into the configuration for just one virtual host. + CacheEnable disk / + + + # The result of CacheDirLevels * CacheDirLength must not be higher than + # 20. Moreover, pay attention on file system limits. Some file systems + # do not support more than a certain number of inodes and + # subdirectories (e.g. 32000 for ext3) + CacheDirLevels 2 + CacheDirLength 1 + diff --git a/cookbooks/apache2/templates/default/mods/cgid.conf.erb b/cookbooks/apache2/templates/default/mods/cgid.conf.erb new file mode 100644 index 0000000..355236c --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/cgid.conf.erb @@ -0,0 +1,3 @@ +# Socket for cgid communication +# +ScriptSock <%= node['apache']['run_dir'] %>/cgisock diff --git a/cookbooks/apache2/templates/default/mods/dav_fs.conf.erb b/cookbooks/apache2/templates/default/mods/dav_fs.conf.erb new file mode 100644 index 0000000..609b2f4 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/dav_fs.conf.erb @@ -0,0 +1 @@ +DAVLockDB <%= node['apache']['lock_dir'] %>/DAVLock diff --git a/cookbooks/apache2/templates/default/mods/deflate.conf.erb b/cookbooks/apache2/templates/default/mods/deflate.conf.erb new file mode 100644 index 0000000..7dd016a --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/deflate.conf.erb @@ -0,0 +1,18 @@ + + + # these are known to be safe with MSIE 6 + AddOutputFilterByType DEFLATE text/html text/plain text/xml + + # everything else may cause problems with MSIE 6 + AddOutputFilterByType DEFLATE text/css + AddOutputFilterByType DEFLATE application/x-javascript application/javascript application/ecmascript + AddOutputFilterByType DEFLATE application/rss+xml + AddOutputFilterByType DEFLATE application/xml + AddOutputFilterByType DEFLATE application/xhtml+xml + AddOutputFilterByType DEFLATE image/svg+xml + AddOutputFilterByType DEFLATE application/atom_xml + AddOutputFilterByType DEFLATE application/x-httpd-php + AddOutputFilterByType DEFLATE application/x-httpd-fastphp + AddOutputFilterByType DEFLATE application/x-httpd-eruby + + diff --git a/cookbooks/apache2/templates/default/mods/dir.conf.erb b/cookbooks/apache2/templates/default/mods/dir.conf.erb new file mode 100644 index 0000000..dd6cea1 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/dir.conf.erb @@ -0,0 +1,3 @@ + + DirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm + diff --git a/cookbooks/apache2/templates/default/mods/fastcgi.conf.erb b/cookbooks/apache2/templates/default/mods/fastcgi.conf.erb new file mode 100644 index 0000000..add16d5 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/fastcgi.conf.erb @@ -0,0 +1,5 @@ + + AddHandler fastcgi-script .fcgi + #FastCgiWrapper /usr/lib/apache2/suexec + FastCgiIpcDir <%= "#{node['apache']['lib_dir']}/fastcgi" %> + diff --git a/cookbooks/apache2/templates/default/mods/fcgid.conf.erb b/cookbooks/apache2/templates/default/mods/fcgid.conf.erb new file mode 100644 index 0000000..d13e8a6 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/fcgid.conf.erb @@ -0,0 +1,10 @@ + + AddHandler fcgid-script .fcgi + IPCConnectTimeout 20 + + +<% if %w[rhel fedora].include?(node['platform_family']) -%> +# Sane place to put sockets and shared memory file +SocketPath run/mod_fcgid +SharememPath run/mod_fcgid/fcgid_shm +<% end -%> diff --git a/cookbooks/apache2/templates/default/mods/include.conf.erb b/cookbooks/apache2/templates/default/mods/include.conf.erb new file mode 100644 index 0000000..44f54e4 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/include.conf.erb @@ -0,0 +1,4 @@ + + AddType text/html .shtml + AddOutputFilter INCLUDES .shtml + diff --git a/cookbooks/apache2/templates/default/mods/include.erb b/cookbooks/apache2/templates/default/mods/include.erb new file mode 100644 index 0000000..06c3e0a --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/include.erb @@ -0,0 +1,4 @@ + + AddType text/html .shtml + AddOutputFilter INCLUDES .shtml + diff --git a/cookbooks/apache2/templates/default/mods/info.conf.erb b/cookbooks/apache2/templates/default/mods/info.conf.erb new file mode 100644 index 0000000..a735ae5 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/info.conf.erb @@ -0,0 +1,19 @@ + + # + # Allow server info reports generated by mod_info, + # with the URL of http://servername/server-info + # Uncomment and change the ".example.com" to allow + # access from other hosts. + # + + SetHandler server-info +<% if node['apache']['version'] == '2.4' -%> + Require local + Require ip <%= node['apache']['info_allow_list'] %> +<% else -%> + Order deny,allow + Deny from all + Allow from <%= node['apache']['info_allow_list'] %> +<% end -%> + + diff --git a/cookbooks/apache2/templates/default/mods/ldap.conf.erb b/cookbooks/apache2/templates/default/mods/ldap.conf.erb new file mode 100644 index 0000000..6333d06 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/ldap.conf.erb @@ -0,0 +1,4 @@ + + SetHandler ldap-status + Require local + diff --git a/cookbooks/apache2/templates/default/mods/mime.conf.erb b/cookbooks/apache2/templates/default/mods/mime.conf.erb new file mode 100644 index 0000000..56d1fca --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/mime.conf.erb @@ -0,0 +1,199 @@ + + # + # TypesConfig points to the file containing the list of mappings from + # filename extension to MIME-type. + # + <% case node['platform_family'] -%> + <% when 'arch' -%> + TypesConfig <%= node['apache']['dir'] %>/conf/mime.types + <% when 'freebsd' -%> + TypesConfig <%= node['apache']['dir'] %>/mime.types + <% else -%> + TypesConfig /etc/mime.types + <% end -%> + + # + # AddType allows you to add to or override the MIME configuration + # file mime.types 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. + # 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 .tgz + #AddEncoding x-bzip2 .bz2 + # + # 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 + AddType application/x-bzip2 .bz2 + + AddType image/svg+xml svg svgz + AddEncoding gzip svgz + + # + # 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 + # See README.Debian for Spanish + 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 + # See README.Debian for Turkish + AddLanguage tr .tr + AddLanguage zh-CN .zh-cn + AddLanguage zh-TW .zh-tw + + # + # 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 + + # + # 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 files that include their own HTTP headers: + # + #AddHandler send-as-is asis + + # + # For server-parsed imagemap files: + # + #AddHandler imap-file map + + # + # For type maps (negotiated resources): + # (This is enabled by default to allow the Apache "It Worked" page + # to be distributed in multiple languages.) + # + 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 + diff --git a/cookbooks/apache2/templates/default/mods/mime_magic.conf.erb b/cookbooks/apache2/templates/default/mods/mime_magic.conf.erb new file mode 100644 index 0000000..bb31b1a --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/mime_magic.conf.erb @@ -0,0 +1,3 @@ + + MIMEMagicFile <%= node['apache']['dir'] %>/magic + diff --git a/cookbooks/apache2/templates/default/mods/mpm_event.conf.erb b/cookbooks/apache2/templates/default/mods/mpm_event.conf.erb new file mode 100644 index 0000000..5b85c96 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/mpm_event.conf.erb @@ -0,0 +1,32 @@ +# event MPM + + <% if node['apache']['version'] == '2.4' -%> + # 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 + # MaxRequestWorkers: maximum number of worker threads + # MaxConnectionsPerChild: maximum number of requests a server process serves + StartServers <%= node['apache']['event']['startservers'] %> + MinSpareThreads <%= node['apache']['event']['minsparethreads'] %> + MaxSpareThreads <%= node['apache']['event']['maxsparethreads'] %> + ThreadsPerChild <%= node['apache']['event']['threadsperchild'] %> + MaxRequestWorkers <%= node['apache']['event']['maxrequestworkers'] %> + MaxConnectionsPerChild <%= node['apache']['event']['maxconnectionsperchild'] %> + ThreadLimit <%= node['apache']['event']['threadlimit'] %> + ServerLimit <%= node['apache']['event']['serverlimit'] %> + <% else -%> + # 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 + StartServers <%= node['apache']['event']['startservers'] %> + MinSpareThreads <%= node['apache']['event']['minsparethreads'] %> + MaxSpareThreads <%= node['apache']['event']['maxsparethreads'] %> + MaxClients <%= node['apache']['event']['maxrequestworkers'] %> + MaxRequestsPerChild <%= node['apache']['event']['maxconnectionsperchild'] %> + ThreadLimit <%= node['apache']['event']['threadlimit'] %> + ServerLimit <%= node['apache']['event']['serverlimit'] %> + <% end -%> + diff --git a/cookbooks/apache2/templates/default/mods/mpm_prefork.conf.erb b/cookbooks/apache2/templates/default/mods/mpm_prefork.conf.erb new file mode 100644 index 0000000..62b5503 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/mpm_prefork.conf.erb @@ -0,0 +1,27 @@ +# prefork MPM + + <% if node['apache']['version'] == '2.4' -%> + # 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 + # MaxRequestWorkers: maximum number of server processes allowed to start + # MaxConnectionsPerChild: maximum number of requests a server process serves + StartServers <%= node['apache']['prefork']['startservers'] %> + MinSpareServers <%= node['apache']['prefork']['minspareservers'] %> + MaxSpareServers <%= node['apache']['prefork']['maxspareservers'] %> + MaxRequestWorkers <%= node['apache']['prefork']['maxrequestworkers'] %> + MaxConnectionsPerChild <%= node['apache']['prefork']['maxconnectionsperchild'] %> + <% else -%> + # 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 + StartServers <%= node['apache']['prefork']['startservers'] %> + MinSpareServers <%= node['apache']['prefork']['minspareservers'] %> + MaxSpareServers <%= node['apache']['prefork']['maxspareservers'] %> + ServerLimit <%= node['apache']['prefork']['serverlimit'] %> + MaxClients <%= node['apache']['prefork']['maxrequestworkers'] %> + MaxRequestsPerChild <%= node['apache']['prefork']['maxconnectionsperchild'] %> + <% end -%> + diff --git a/cookbooks/apache2/templates/default/mods/mpm_worker.conf.erb b/cookbooks/apache2/templates/default/mods/mpm_worker.conf.erb new file mode 100644 index 0000000..d3eb5a6 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/mpm_worker.conf.erb @@ -0,0 +1,20 @@ +# 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 +# MaxRequestWorkers: maximum number of threads +# MaxConnectionsPerChild: maximum number of requests a server process serves + + StartServers <%= node['apache']['worker']['startservers'] %> + MinSpareThreads <%= node['apache']['worker']['minsparethreads'] %> + MaxSpareThreads <%= node['apache']['worker']['maxsparethreads'] %> + ThreadsPerChild <%= node['apache']['worker']['threadsperchild'] %> + MaxRequestWorkers <%= node['apache']['worker']['maxrequestworkers'] %> + MaxConnectionsPerChild <%= node['apache']['worker']['maxconnectionsperchild'] %> + ThreadLimit <%= node['apache']['worker']['threadlimit'] %> + ServerLimit <%= node['apache']['worker']['serverlimit'] %> + diff --git a/cookbooks/apache2/templates/default/mods/negotiation.conf.erb b/cookbooks/apache2/templates/default/mods/negotiation.conf.erb new file mode 100644 index 0000000..6bea05f --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/negotiation.conf.erb @@ -0,0 +1,17 @@ + + # + # 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 tr 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 + diff --git a/cookbooks/apache2/templates/default/mods/pagespeed.conf.erb b/cookbooks/apache2/templates/default/mods/pagespeed.conf.erb new file mode 100644 index 0000000..6d5f3ce --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/pagespeed.conf.erb @@ -0,0 +1,293 @@ + + # Turn on mod_pagespeed. To completely disable mod_pagespeed, you + # can set this to "off". + ModPagespeed on + + # We want VHosts to inherit global configuration. + # If this is not included, they'll be independent (except for inherently + # global options), at least for backwards compatibility. + ModPagespeedInheritVHostConfig on + + # Direct Apache to send all HTML output to the mod_pagespeed + # output handler. + AddOutputFilterByType MOD_PAGESPEED_OUTPUT_FILTER text/html + + # If you want mod_pagespeed process XHTML as well, please uncomment this + # line. + # AddOutputFilterByType MOD_PAGESPEED_OUTPUT_FILTER application/xhtml+xml + + # The ModPagespeedFileCachePath directory must exist and be writable + # by the apache user (as specified by the User directive). + ModPagespeedFileCachePath "/var/cache/mod_pagespeed/" + + # Override the mod_pagespeed 'rewrite level'. The default level + # "CoreFilters" uses a set of rewrite filters that are generally + # safe for most web pages. Most sites should not need to change + # this value and can instead fine-tune the configuration using the + # ModPagespeedDisableFilters and ModPagespeedEnableFilters + # directives, below. Valid values for ModPagespeedRewriteLevel are + # PassThrough, CoreFilters and TestingCoreFilters. + # + # ModPagespeedRewriteLevel PassThrough + + # Explicitly disables specific filters. This is useful in + # conjuction with ModPagespeedRewriteLevel. For instance, if one + # of the filters in the CoreFilters needs to be disabled for a + # site, that filter can be added to + # ModPagespeedDisableFilters. This directive contains a + # comma-separated list of filter names, and can be repeated. + # + # ModPagespeedDisableFilters rewrite_images + + # Explicitly enables specific filters. This is useful in + # conjuction with ModPagespeedRewriteLevel. For instance, filters + # not included in the CoreFilters may be enabled using this + # directive. This directive contains a comma-separated list of + # filter names, and can be repeated. + # + # ModPagespeedEnableFilters rewrite_javascript,rewrite_css + # ModPagespeedEnableFilters collapse_whitespace,elide_attributes + + # ModPagespeedDomain + # authorizes rewriting of JS, CSS, and Image files found in this + # domain. By default only resources with the same origin as the + # HTML file are rewritten. For example: + # + # ModPagespeedDomain cdn.myhost.com + # + # This will allow resources found on http://cdn.myhost.com to be + # rewritten in addition to those in the same domain as the HTML. + # + # Wildcards (* and ?) are allowed in the domain specification. Be + # careful when using them as if you rewrite domains that do not + # send you traffic, then the site receiving the traffic will not + # know how to serve the rewritten content. + + # Other defaults (cache sizes and thresholds): + # + # ModPagespeedFileCacheSizeKb 102400 + # ModPagespeedFileCacheCleanIntervalMs 3600000 + # ModPagespeedLRUCacheKbPerProcess 1024 + # ModPagespeedLRUCacheByteLimit 16384 + # ModPagespeedCssFlattenMaxBytes 2048 + # ModPagespeedCssInlineMaxBytes 2048 + # ModPagespeedCssImageInlineMaxBytes 2048 + # ModPagespeedImageInlineMaxBytes 2048 + # ModPagespeedJsInlineMaxBytes 2048 + # ModPagespeedCssOutlineMinBytes 3000 + # ModPagespeedJsOutlineMinBytes 3000 + + # Limit the number of inodes in the file cache. Set to 0 for no limit. + # The default value if this paramater is not specified is 0 (no limit). + ModPagespeedFileCacheInodeLimit 500000 + + # Bound the number of images that can be rewritten at any one time; this + # avoids overloading the CPU. Set this to 0 to remove the bound. + # + # ModPagespeedImageMaxRewritesAtOnce 8 + + # You can also customize the number of threads per Apache process + # mod_pagespeed will use to do resource optimization. Plain + # "rewrite threads" are used to do short, latency-sensitive work, + # while "expensive rewrite threads" are used for actual optimization + # work that's more computationally expensive. If you live these unset, + # or use values <= 0 the defaults will be used, which is 1 for both + # values when using non-threaded MPMs (e.g. prefork) and 4 for both + # on threaded MPMs (e.g. worker and event). These settings can only + # be changed globally, and not per virtual host. + # + # ModPagespeedNumRewriteThreads 4 + # ModPagespeedNumExpensiveRewriteThreads 4 + + + # Settings for image optimization: + # + # Jpeg recompression quality (0 to 100, -1 strips metadata): + # ModPagespeedJpegRecompressionQuality -1 + # + # Percent of original image size below which optimized images are retained: + # ModPagespeedImageLimitOptimizedPercent 100 + # + # Percent of original image area below which image resizing will be + # attempted: + # ModPagespeedImageLimitResizeAreaPercent 100 + + # When Apache is set up as a browser proxy, mod_pagespeed can record + # web-sites as they are requested, so that an image of the web is built up + # in the directory of the proxy administrator's choosing. When ReadOnly is + # on, only files already present in the SlurpDirectory are served by the + # proxy. + # + # ModPagespeedSlurpDirectory ... + # ModPagespeedSlurpReadOnly on + + # The maximum URL size is generally limited to about 2k characters + # due to IE: See http://support.microsoft.com/kb/208427/EN-US. + # Apache servers by default impose a further limitation of about + # 250 characters per URL segment (text between slashes). + # mod_pagespeed circumvents this limitation, but if you employ + # proxy servers in your path you may need to re-impose it by + # overriding the setting here. The default setting is 1024 + # characters. + # + # ModPagespeedMaxSegmentLength 250 + + # Uncomment this if you want to prevent mod_pagespeed from combining files + # (e.g. CSS files) across paths + # + # ModPagespeedCombineAcrossPaths off + + # Renaming JavaScript URLs can sometimes break them. With this + # option enabled, mod_pagespeed uses a simple heuristic to decide + # not to rename JavaScript that it thinks is introspective. + # + # You can turn this off to let mod_pagespeed rename all JS files. + ModPagespeedAvoidRenamingIntrospectiveJavascript on + + # Certain common JavaScript libraries are available from Google, which acts + # as a CDN and allows you to benefit from browser caching if a new visitor + # to your site previously visited another site that makes use of the same + # libraries as you do. Enable the following filter to turn on this feature. + # + # ModPagespeedEnableFilters canonicalize_javascript_libraries + + # The following lines configure libraries that are recognized by + # canonicalize_javascript_libraries. These will have no effect unless you + # enable this filter (generally by uncommenting the last line in the + # previous stanza). It simply provides a sensible default configuration + # when the filter is switched on. + # The format is: + # ModPagespeedLibrary bytes md5 canonical_url + # Where bytes and md5 are with respect to the *minified* JS; use + # js_minify --print_size_and_hash to obtain this data. + # Note that we can register multiple hashes for the same canonical url; + # we do this if there are versions available that have already been minified + # with more sophisticated tools. + ModPagespeedLibrary 105527 ltVVzzYxo0 //ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js + ModPagespeedLibrary 92501 J8KF47pYOq //ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js + ModPagespeedLibrary 141547 GKjMUuF4PK //ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js + ModPagespeedLibrary 43 1o978_K0_L http://www.modpagespeed.com/rewrite_javascript.js + + # Explicitly tell mod_pagespeed to load some resources from disk. + # This will speed up load time and update frequency. + # + # This should only be used for static resources which do not need + # specific headers set or other processing by Apache. + # + # Both URL and filesystem path should specify directories and + # filesystem path must be absolute (for now). + # + # ModPagespeedLoadFromFile "http://example.com/static/" "/var/www/static/" + + + # Enables server-side instrumentation and statistics. If this rewriter is + # enabled, then each rewritten HTML page will have instrumentation javacript + # added that sends latency beacons to /mod_pagespeed_beacon. These + # statistics can be accessed at /mod_pagespeed_statistics. You must also + # enable the mod_pagespeed_statistics and mod_pagespeed_beacon handlers + # below. + # + # ModPagespeedEnableFilters add_instrumentation + + # The add_instrumentation filter sends a beacon after the page onload + # handler is called. The user might navigate to a new URL before this. If + # you enable the following directive, the beacon is sent as part of an + # onbeforeunload handler, for pages where navigation happens before the + # onload event. + # + # ModPagespeedReportUnloadTime on + + # Uncomment the following line so that ModPagespeed will not cache or + # rewrite resources with Vary: in the header, e.g. Vary: User-Agent. + # ModPagespeedRespectVary on + + # This handles the client-side instrumentation callbacks which are injected + # by the add_instrumentation filter. + # You can use a different location by adding the ModPagespeedBeaconUrl + # directive; see the documentation on add_instrumentation. + + SetHandler mod_pagespeed_beacon + + + # Uncomment the following line if you want to disable statistics entirely. + # + # ModPagespeedStatistics off + + # This page lets you view statistics about the mod_pagespeed module. + + Order allow,deny + # You may insert other "Allow from" lines to add hosts you want to + # allow to look at generated statistics. Another possibility is + # to comment out the "Order" and "Allow" options from the config + # file, to allow any client that can reach your server to examine + # statistics. This might be appropriate in an experimental setup or + # if the Apache server is protected by a reverse proxy that will + # filter URLs in some fashion. + Allow from localhost + Allow from 127.0.0.1 + SetHandler mod_pagespeed_statistics + + + # Uncomment the following line if you want to enable statistics logging. + # ModPagespeedStatistics is required to be enabled. + # + # ModPagespeedStatisticsLogging on + # + # The base filename to use to store logged statistics. + # Required if logging is enabled. + # + # ModPagespeedStatisticsLoggingFile "@@MOD_PAGESPEED_STATS_LOG@@" + # + # The interval at which statistics will be logged, in milliseconds. + # Optional; default is 3000. + # + # ModPagespeedStatisticsLoggingIntervalMs 3000 + + # If both of the below are set, the console will use offline copies of the + # files needed for the Google Chart Tools API rather than connecting to the + # Internet to obtain them. This is experimental, as the only supported + # loading mechanism for the Chart Tools API requires an Internet connexion. + # + # Where to find an offline copy of the CSS file required for the Google + # Chart Tools API. At the time of writing, the Google Chart Tools API CSS + # file can be found at: + # https://ajax.googleapis.com/ajax/static/modules/gviz/1.0/core/tooltip.css + # + # ModPagespeedStatisticsLoggingChartsCSS http://example.com/charts.css + # + # Where to find an offline copy of the JS file required for the Google + # Chart Tools API. At the time of writing, the Google Chart Tools API JS + # file can be found at: + # https://www.google.com/uds/api/visualization/1.0/d7d36793f7a886b687850d2813583db9/format+en,default,corechart.I.js + # + # ModPagespeedStatisticsLoggingChartsJS http://example.com/charts.js + + # This page lets you view a graphical console displaying statistics about + # the mod_pagespeed module. + + Order allow,deny + # This can be configured similarly to mod_pagespeed_statistics above. + Allow from localhost + Allow from 127.0.0.1 + SetHandler mod_pagespeed_console + + + # Page /mod_pagespeed_message lets you view the latest messages from + # mod_pagespeed, regardless of log-level in your httpd.conf + # ModPagespeedMessageBufferSize is the maximum number of bytes you would + # like to dump to your /mod_pagespeed_message page at one time, + # its default value is 100k bytes. + # Set it to 0 if you want to disable this feature. + ModPagespeedMessageBufferSize 100000 + + + Allow from localhost + Allow from 127.0.0.1 + SetHandler mod_pagespeed_message + + + Allow from localhost + Allow from 127.0.0.1 + SetHandler mod_pagespeed_referer_statistics + + diff --git a/cookbooks/apache2/templates/default/mods/php5.conf.erb b/cookbooks/apache2/templates/default/mods/php5.conf.erb new file mode 100644 index 0000000..b11f1b2 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/php5.conf.erb @@ -0,0 +1,37 @@ + + + SetHandler application/x-httpd-php + + + SetHandler application/x-httpd-php-source + # Deny access to raw php sources by default + # To re-enable it's recommended to enable access to the files + # only in specific virtual host or directory +<% if node['apache']['version'] == '2.4' -%> + Require all denied +<% else -%> + Order Deny,Allow + Deny from all +<% end -%> + + # Deny access to files without filename (e.g. '.php') + +<% if node['apache']['version'] == '2.4' -%> + Require all denied +<% else -%> + Order Deny,Allow + Deny from all +<% end -%> + + + # Running PHP scripts in user directories is disabled by default + # + # To re-enable PHP in user directories comment the following lines + # (from to .) Do NOT set it to On as it + # prevents .htaccess files from disabling it. + + + php_admin_value engine Off + + + diff --git a/cookbooks/apache2/templates/default/mods/proxy.conf.erb b/cookbooks/apache2/templates/default/mods/proxy.conf.erb new file mode 100644 index 0000000..4cb64af --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/proxy.conf.erb @@ -0,0 +1,23 @@ + + #turning ProxyRequests on and allowing proxying from all may allow + #spammers to use your proxy to send email. + + ProxyRequests Off + + + AddDefaultCharset off + <% if node['apache']['version'] == "2.4" -%> + Require <%= node['apache']['proxy']['require'] %> + <% else -%> + Order <%= node['apache']['proxy']['order'] %> + Deny from <%= node['apache']['proxy']['deny_from'] %> + Allow from <%= node['apache']['proxy']['allow_from'] %> + <% end -%> + + + # 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 + diff --git a/cookbooks/apache2/templates/default/mods/proxy_balancer.conf.erb b/cookbooks/apache2/templates/default/mods/proxy_balancer.conf.erb new file mode 100644 index 0000000..4131537 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/proxy_balancer.conf.erb @@ -0,0 +1,18 @@ + + # Balancer manager enables dynamic update of balancer members + # (needs mod_status). Uncomment to enable. + # + # + # + # SetHandler balancer-manager +<% if node['apache']['version'] == '2.4' -%> + # Require local +<% else -%> + # Order deny,allow + # Deny from all + # Allow from 127.0.0.1 ::1 + # Satisfy all +<% end -%> + # + # + diff --git a/cookbooks/apache2/templates/default/mods/proxy_ftp.conf.erb b/cookbooks/apache2/templates/default/mods/proxy_ftp.conf.erb new file mode 100644 index 0000000..548124b --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/proxy_ftp.conf.erb @@ -0,0 +1,4 @@ + + # Define the character set for proxied FTP listings. Default is ISO-8859-1 + ProxyFtpDirCharset UTF-8 + diff --git a/cookbooks/apache2/templates/default/mods/reqtimeout.conf.erb b/cookbooks/apache2/templates/default/mods/reqtimeout.conf.erb new file mode 100644 index 0000000..b64e9cf --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/reqtimeout.conf.erb @@ -0,0 +1,22 @@ + + # mod_reqtimeout limits the time waiting on the client to prevent an + # attacker from causing a denial of service by opening many connections + # but not sending requests. This file tries to give a sensible default + # configuration, but it may be necessary to tune the timeout values to + # the actual situation. Note that it is also possible to configure + # mod_reqtimeout per virtual host. + + # Wait max 20 seconds for the first byte of the request line+headers + # From then, require a minimum data rate of 500 bytes/s, but don't + # wait longer than 40 seconds in total. + # Note: Lower timeouts may make sense on non-ssl virtual hosts but can + # cause problem with ssl enabled virtual hosts: This timeout includes + # the time a browser may need to fetch the CRL for the certificate. If + # the CRL server is not reachable, it may take more than 10 seconds + # until the browser gives up. + RequestReadTimeout header=20-40,minrate=500 + + # Wait max 10 seconds for the first byte of the request body (if any) + # From then, require a minimum data rate of 500 bytes/s + RequestReadTimeout body=10,minrate=500 + diff --git a/cookbooks/apache2/templates/default/mods/setenvif.conf.erb b/cookbooks/apache2/templates/default/mods/setenvif.conf.erb new file mode 100644 index 0000000..24b5fd9 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/setenvif.conf.erb @@ -0,0 +1,28 @@ + + # + # The following directives modify normal HTTP response behavior to + # handle known problems with browser implementations. + # + BrowserMatch "Mozilla/2" nokeepalive + BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0 + BrowserMatch "RealPlayer 4\.0" force-response-1.0 + BrowserMatch "Java/1\.0" force-response-1.0 + BrowserMatch "JDK/1\.0" force-response-1.0 + + # + # The following directive disables redirects on non-GET requests for + # a directory that does not include the trailing slash. This fixes a + # problem with Microsoft WebFolders which does not appropriately handle + # redirects for folders with DAV methods. + # Same deal with Apple's DAV filesystem and Gnome VFS support for DAV. + # + BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully + BrowserMatch "MS FrontPage" redirect-carefully + BrowserMatch "^WebDrive" redirect-carefully + BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully + BrowserMatch "^gnome-vfs/1.0" redirect-carefully + BrowserMatch "^gvfs/1" redirect-carefully + BrowserMatch "^XML Spy" redirect-carefully + BrowserMatch "^Dreamweaver-WebDAV-SCM1" redirect-carefully + BrowserMatch " Konqueror/4" redirect-carefully + diff --git a/cookbooks/apache2/templates/default/mods/ssl.conf.erb b/cookbooks/apache2/templates/default/mods/ssl.conf.erb new file mode 100644 index 0000000..b5f9e49 --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/ssl.conf.erb @@ -0,0 +1,108 @@ + + # + # 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 builtin + SSLRandomSeed startup file:/dev/urandom 512 + SSLRandomSeed connect builtin + 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 + # + 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 <%= node['apache']['mod_ssl']['pass_phrase_dialog'] %> + + # Inter-Process Session Cache: + # Configure the SSL Session Cache: First the mechanism + # to use and second the expiring timeout (in seconds). + SSLSessionCache <%= node['apache']['mod_ssl']['session_cache'] %> + SSLSessionCacheTimeout <%= node['apache']['mod_ssl']['session_cache_timeout'] %> + +<% if node['apache']['version'] != '2.4' -%> + # Semaphore: + # Configure the path to the mutual exclusion semaphore the + # SSL engine uses internally for inter-process synchronization. + SSLMutex <%= node['apache']['mod_ssl']['mutex'] %> +<% end -%> + + # SSL Cipher Suite: + # List the ciphers that the client is permitted to negotiate. + # See the mod_ssl documentation for a complete list. + # enable only secure ciphers: + SSLCipherSuite <%= node['apache']['mod_ssl']['cipher_suite'] %> + + # Speed-optimized SSL Cipher configuration: + # If speed is your main concern (on busy HTTPS servers e.g.), + # you might want to force clients to specific, performance + # optimized ciphers. In this case, prepend those ciphers + # to the SSLCipherSuite list, and enable SSLHonorCipherOrder. + # Caveat: by giving precedence to RC4-SHA and AES128-SHA + # (as in the example below), most connections will no longer + # have perfect forward secrecy - if the server's key is + # compromised, captures of past or future traffic must be + # considered compromised, too. + #SSLCipherSuite RC4-SHA:AES128-SHA:HIGH:MEDIUM:!aNULL:!MD5 + SSLHonorCipherOrder <%= node['apache']['mod_ssl']['honor_cipher_order'] %> + + # The protocols to enable. + # Available values: all, SSLv3, TLSv1, TLSv1.1, TLSv1.2 + # SSL v2 is no longer supported + SSLProtocol <%= node['apache']['mod_ssl']['protocol'] %> + + # Allow insecure renegotiation with clients which do not yet support the + # secure renegotiation protocol. Default: Off + SSLInsecureRenegotiation <%= node['apache']['mod_ssl']['insecure_renegotiation'] %> + +<% unless node['apache']['mod_ssl']['strict_sni_vhost_check'] == "Off"%> + # Whether to forbid non-SNI clients to access name based virtual hosts. + # Default: Off + SSLStrictSNIVHostCheck <%= node['apache']['mod_ssl']['strict_sni_vhost_check'] %> +<% end %> + +<% if node['apache']['version'] == '2.4' -%> + # Enable compression on the SSL level + # Enabling compression causes security issues in most setups (the so called CRIME attack). + # Default: Off + SSLCompression <%= node['apache']['mod_ssl']['compression'] %> + + # OCSP Stapling, only in httpd 2.3.3 and later + # This option enables OCSP stapling, as defined by the "Certificate Status Request" TLS + # extension specified in RFC 6066. If enabled (and requested by the client), mod_ssl will + # include an OCSP response for its own certificate in the TLS handshake. + # Configuring an SSLStaplingCache is a prerequisite for enabling OCSP stapling. + # Default: Off + <% if node['apache']['mod_ssl']['use_stapling'] == 'On' -%> + SSLUseStapling <%= node['apache']['mod_ssl']['use_stapling'] %> + SSLStaplingResponderTimeout <%= node['apache']['mod_ssl']['stapling_responder_timeout'] %> + SSLStaplingReturnResponderErrors <%= node['apache']['mod_ssl']['stapling_return_responder_errors'] %> + SSLStaplingCache <%= node['apache']['mod_ssl']['stapling_cache'] %> + <% end -%> +<% end -%> + + <% node['apache']['mod_ssl']['directives'].sort_by { |key, val| key }.each do |directive, value| -%> + <%= directive %> <%= value %> + <% end -%> + diff --git a/cookbooks/apache2/templates/default/mods/status.conf.erb b/cookbooks/apache2/templates/default/mods/status.conf.erb new file mode 100644 index 0000000..be79e4e --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/status.conf.erb @@ -0,0 +1,42 @@ + + # + # Allow server status reports generated by mod_status, + # with the URL of http://servername/server-status + # Uncomment and change the ".example.com" to allow + # access from other hosts. + # + + SetHandler server-status +<% if node['apache']['version'] == '2.4' -%> + Require local + Require ip <%=node['apache']['status_allow_list']%> +<% else -%> + Order deny,allow + Deny from all + Allow from <%= node['apache']['status_allow_list'] %> + <% end -%> + + + # + # 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. + # + <% if node['apache']['ext_status'] -%> + ExtendedStatus On + <% else -%> + ExtendedStatus Off + <% end -%> + +<% if node['apache']['version'] == '2.4' -%> + # Determine if mod_status displays the first 63 characters of a request or + # the last 63, assuming the request itself is greater than 63 chars. + # Default: Off + #SeeRequestTail On + + + # Show Proxy LoadBalancer status in mod_status + ProxyStatus On + +<% end -%> + diff --git a/cookbooks/apache2/templates/default/mods/userdir.conf.erb b/cookbooks/apache2/templates/default/mods/userdir.conf.erb new file mode 100644 index 0000000..5c5c25d --- /dev/null +++ b/cookbooks/apache2/templates/default/mods/userdir.conf.erb @@ -0,0 +1,17 @@ +<% if node['apache']['version'] == '2.4' -%> + + UserDir public_html + UserDir disabled root + + + AllowOverride FileInfo AuthConfig Limit Indexes + Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec + + Require all granted + + + Require all denied + + + +<% end -%> diff --git a/cookbooks/apache2/templates/default/port_apache.erb b/cookbooks/apache2/templates/default/port_apache.erb new file mode 100644 index 0000000..64a0f8e --- /dev/null +++ b/cookbooks/apache2/templates/default/port_apache.erb @@ -0,0 +1,3 @@ +<% node['apache']['listen_ports'].each do |port| -%> +-A FWR -p tcp -m tcp --dport <%= port %> -j ACCEPT +<% end %> diff --git a/cookbooks/apache2/templates/default/ports.conf.erb b/cookbooks/apache2/templates/default/ports.conf.erb new file mode 100644 index 0000000..d3a2ca2 --- /dev/null +++ b/cookbooks/apache2/templates/default/ports.conf.erb @@ -0,0 +1,11 @@ +# This file was generated by Chef for <%= node['fqdn'] %>. +# Do NOT modify this file by hand! + +<% node['apache']['listen_ports'].map(&:to_i).uniq.each do |port| -%> +<% node['apache']['listen_addresses'].uniq.each do |address| -%> +Listen <%= address.length > 0 ? "#{address}:" : '' %><%= port %> +<% end -%> +<% if node['apache']['version'] != "2.4" -%> +NameVirtualHost *:<%= port %> +<% end -%> +<% end -%> diff --git a/cookbooks/apache2/templates/default/security.conf.erb b/cookbooks/apache2/templates/default/security.conf.erb new file mode 100644 index 0000000..c0a2d45 --- /dev/null +++ b/cookbooks/apache2/templates/default/security.conf.erb @@ -0,0 +1,45 @@ +# +# 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. +# +# +# AllowOverride None +# Order Deny,Allow +# Deny from all +# + +# 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 <%= node['apache']['servertokens'] %> + +# +# 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 <%= node['apache']['serversignature'] %> + +# +# 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 <%= node['apache']['traceenable'] %> diff --git a/cookbooks/apache2/templates/default/web_app.conf.erb b/cookbooks/apache2/templates/default/web_app.conf.erb new file mode 100644 index 0000000..d3e8b71 --- /dev/null +++ b/cookbooks/apache2/templates/default/web_app.conf.erb @@ -0,0 +1,61 @@ +> + ServerName <%= @params[:server_name] %> + <% if @params[:server_aliases] -%> + ServerAlias <%= @params[:server_aliases].join " " %> + <% end -%> + DocumentRoot <%= @params[:docroot] %> + + > + Options <%= [@params[:directory_options] || "FollowSymLinks" ].flatten.join " " %> + AllowOverride <%= [@params[:allow_override] || "None" ].flatten.join " " %> + <% if node['apache']['version'] == '2.4' -%> + Require all granted + <% else -%> + Order allow,deny + Allow from all + <% end -%> + + + + Options FollowSymLinks + AllowOverride None + + + + SetHandler server-status + + <% if node['apache']['version'] == '2.4' -%> + Require local + <% else -%> + Order Deny,Allow + Deny from all + Allow from 127.0.0.1 + <% end -%> + + + + RewriteEngine On + <%- if node['apache']['version'] == '2.4' -%> + LogLevel info rewrite:trace1 + <%- else -%> + LogLevel info + RewriteLog <%= node['apache']['log_dir'] %>/<%= @application_name %>-rewrite.log + RewriteLogLevel 0 + <%- end -%> + + ErrorLog <%= node['apache']['log_dir'] %>/<%= @params[:name] %>-error.log + CustomLog <%= node['apache']['log_dir'] %>/<%= @params[:name] %>-access.log combined + + <% if @params[:directory_index] -%> + DirectoryIndex <%= [@params[:directory_index]].flatten.join " " %> + <% end -%> + + # Canonical host, <%= @params[:server_name] %> + RewriteCond %{HTTP_HOST} !^<%= @params[:server_name] %> [NC] + RewriteCond %{HTTP_HOST} !^$ + RewriteRule ^/(.*)$ http://<%= @params[:server_name] %>/$1 [L,R=301] + + RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f + RewriteCond %{SCRIPT_FILENAME} !maintenance.html + RewriteRule ^.*$ /system/maintenance.html [L,R=503] + diff --git a/cookbooks/application/CHANGELOG.md b/cookbooks/application/CHANGELOG.md new file mode 100644 index 0000000..44aab88 --- /dev/null +++ b/cookbooks/application/CHANGELOG.md @@ -0,0 +1,103 @@ +application Cookbook CHANGELOG +======================= +This file is used to list changes made in each version of the application cookbook. + + +v4.1.6 +------ +- Support for Chef 12. +- Add `strict_ssh` option to enable host key checking. +- Add `keep_releases` option to control number of releases to keep. +- Allow passing a path to a file for `deploy_key`. + +v4.1.4 +------ +### Bug +- **[COOK-3343](https://tickets.opscode.com/browse/COOK-3343)** - Can't parse release candidate version number + + +v4.1.2 +------ +### Bug +- **[COOK-3343](https://tickets.opscode.com/browse/COOK-3343)** - Can't parse release candidate version number + + +v4.1.0 +------ +### Bug +- [COOK-3343] - Can't parse release candidate version number + + +v4.0.0 +------ +### Breaking +- Removes compatability with Chef 10 + +### Improvement +- **[COOK-3564](https://tickets.opscode.com/browse/COOK-3564)** - Replace calls to `Chef::Mixin::RecipeDefinitionDSLCore` + +v3.0.0 +------ +### Bug +- [COOK-3306]: Multiple Memory Leaks in Application Cookbook + +v2.0.4 +------ +### Bug +- [COOK-2812]: application cookbook doesn't allow to specify a block as `restart_command` + +v2.0.2 +------ +### Bug +- [COOK-2537]: Provide proper `respond_to` behavior when using `method_missing` +- [COOK-2713]: application resource should Allow sub-resource attributes to propogate up + +### Improvement +- [COOK-2597]: Allow customization for `shallow_clone` when doing a git deploy + +v2.0.0 +------ +This release is incompatible with previous releases (hence major version change). The recipes used in older versions are deprecated and completely removed. See README.md for further detail. + +- [COOK-1673] - `deploy_revision` in the application cookbook gives an argument error +- [COOK-1820] - Application cookbook: remove deprecated recipes + +v1.0.4 +------ +- [COOK-1567] - Add git submodules to application cookbook + +v1.0.2 +------ +- [COOK-1312] - string callbacks fail with method not found (really included this time) +- [COOK-1332] - add `release_path` and `shared_path` methods +- [COOK-1333] - add example for running migrations +- [COOK-1360] - fix minor typos in README +- [COOK-1374] - use runit attributes in unicorn run script + +v1.0.0 +------ +This release introduces the LWRP for application deployment, as well as other improvements. The recipes will be deprecated in August 2012 as indicated by their warning messages and in the README.md. + +- [COOK-634] - Implement LWRP for application deployment +- [COOK-1116] - use other SCMs than git +- [COOK-1252] - add `:force_deploy` that maps to corresponding action of deploy resource +- [COOK-1253] - fix rollback error +- [COOK-1312] - string callbacks fail with method not found +- [COOK-1313] - implicit file based hooks aren't invoked +- [COOK-1318] - Create `to_ary` method to resolve issue in resources() lookup on "application[foo]" resources + +v0.99.14 +-------- +- [COOK-1065] - use pip in virtualenv during deploy + +v0.99.12 +-------- +- [COOK-606] application cookbook deployment recipes should use ipaddress instead of fqdn + +v0.99.11 +-------- +- make the `_default` `chef_environment` look like production rails env + +v0.99.10 +-------- +- Use Chef 0.10's `node.chef_environment` instead of `node['app_environment']`. diff --git a/cookbooks/application/README.md b/cookbooks/application/README.md new file mode 100644 index 0000000..b0d2c4e --- /dev/null +++ b/cookbooks/application/README.md @@ -0,0 +1,206 @@ +Application cookbook +==================== +This cookbook is designed to be able to describe and deploy web applications. It provides the basic infrastructure; other cookbooks are required to support specific combinations of frameworks and application servers. The following cookbooks are available at this time: + +- [application_java](https://github.com/opscode-cookbooks/application_java) (Java and Tomcat) +- [application_nginx](https://github.com/opscode-cookbooks/application_nginx) (nginx reverse proxy) +- [application_php](https://github.com/opscode-cookbooks/application_php) (PHP with `mod_php_apache2`) +- [application_python](https://github.com/opscode-cookbooks/application_python) (Django with Gunicorn) +- [application_ruby](https://github.com/opscode-cookbooks/application_ruby) (Rails with Passenger or Unicorn) + + +Backwards Compatibility +----------------------- +- Version 4.0.0 dropped support for Chef 10 +- Version 2.0.0 dropped support for the `apps` data bag. + + +Requirements +------------ +The previous dependencies have been moved out to the application-stack-specific cookbooks, and this cookbook has no external dependencies. + + +Resources/Providers +------------------- +The `application` LWRP configures the basic properties of most applications, regardless of the framework or application server they use. These include: + +- SCM information for the deployment, such as the repository URL and branch name; +- deployment destination, including the filesystem path to deploy to; +- any OS packages to install as dependencies; +- optional callback to control the deployment. + +This LWRP uses the `deploy_revision` LWRP to perform the bulk of its tasks, and many concepts and parameters map directly to it. Check the documentation for `deploy_revision` for more information. + +Configuration of framework-specific aspects of the application are performed by invoking a sub-resource; see the appropriate cookbook for more documentation. + +### Actions +- `:deploy`: deploy an application, including any necessary configuration, restarting the associated service if necessary +- `:force_deploy`: same as `:deploy`, but it will send a `:force_deploy` action to the deploy resource, directing it to deploy the application even if the same revision is already deployed + +### Attribute Parameters +- `name`: name attribute. The name of the application you are setting up. This will be used to derive the default value for other attribute +- `packages`: an Array or Hash of packages to be installed before starting the deployment +- `path`: target path of the deployment; it will be created if it does not exist +- `owner`: the user that shall own the target path +- `group`: the group that shall own the target path +- `keep_releases`: count of keep releases +- `strategy`: the underlying LWRP that will be used to perform the deployment. The default is `:deploy_revision`, and it should never be necessary to change it +- `scm_provider`: the provider class to use for the deployment. It defaults to `Chef::Provider::Git`, you can set it to `Chef::Provider::Subversion` to deploy from an SVN repository +- `repository`: the URL of the repository the application should be checked out from +- `revision`: an identifier pointing to the revision that should be checked out +- `deploy_key`: the private key to use to access the repository via SSH, or path to a file containing the key +- `rollback_on_error`: if true, exceptions during a deployment will be caught and a clean rollback to the previous version will be attempted; the exception will then be re-raised. Defaults to true; change it only if you know what you are doing +- `environment`: a Hash of environment variables to set while running migrations +- `purge_before_symlink`: an Array of paths (relative to the checkout) to remove before creating symlinks +- `create_dirs_before_symlink`: an Array of paths (relative to the checkout) pointing to directories to create before creating symlinks +- `symlinks`: a Hash of shared/dir/path => release/dir/path. It determines which files and dirs in the shared directory get symlinked to the current release directory +- `symlink_before_migrate`: similar to symlinks, except that they will be linked before any migration is run +- `migrate`: if `true` then migrations will be run; defaults to false +- `migration_command`: a command to run to migrate the application from the previous to the current state +- `restart_command`: a command to run when restarting the application +- `environment_name`: the name of a framework-specific "environment" (for example the Rails environment). By default it is the same as the Chef environment, unless it is `_default`, in which case it is set to `production` +- `enable_submodules`: whether to enable git submodules in the deploy, passed into the deploy resource. + +### Callback Attributes +You can also set a few attributes on this LWRP that are interpreted as callback to be called at specific points during a deployment. If you pass a block, it will be evaluated within a new context. If you pass a string, it will be interpreted as a path (relative to the release directory) to a file; if it exists, it will be loaded and evaluated as though it were a Chef recipe. + +The following callback attributes are available: + +- `before_deploy`: invoked immediately after initial setup and before the deployment proper is started. This callback will be invoked on every Chef run +- `before_migrate` +- `before_symlink` +- `before_restart` +- `after_restart` + +### Sub-resources +Anything that is not a known attribute will be interpreted as the name of a sub-resource; the resource will be looked up, and any nested attribute will be passed to it. More than one sub-resource can be added to an application; the order is significant, with the latter sub-resources overriding any sub-resource that comes before. + +Sub-resources can set their own values for some attributes; if they do, they will be merged together with the attribute set on the main resource. The attributes that support this behavior are the following: + +- `environment`: environment variables from the application and from sub-resources will be merged together, with later resources overriding values set in the application or previous resources +- `migration_command`: commands from the application and from sub-resources will be concatenated together joined with '&&' and run as a single shell command. The migration will only succeed if all the commands succeed +- `restart_command`: commands from the application and from sub-resources will be evaluated in order +- `symlink_before_migrate`: will be concatenated as a single array +- `callbacks`: sub-resources callbacks will be invoked first, followed by the application callbacks + + +Usage +----- +To use the application cookbook we recommend creating a cookbook named after the application, e.g. `my_app`. In `metadata.rb` you should declare a dependency on this cookbook and any framework cookbook the application may need. For example a Rails application may include: + +```ruby +depends 'application' +depends 'application_ruby' +``` + +The default recipe should describe your application using the `application` LWRP; you may also include additional recipes, for example to set up a database, queues, search engines and other components of your application. + +A recipe using this LWRP may look like this: + +```ruby +application 'my_app' do + path '/deploy/to/dir' + owner 'app-user' + group 'app-group' + + repository 'http://git.example.com/my-app.git' + revision 'production' + + # Apply the rails LWRP from application_ruby + rails do + # Rails-specific configuration. See the README in the + # application_ruby cookbook for more information. + end + + # Apply the passenger_apache2 LWRP, also from application_ruby + passenger_apache2 do + # Passenger-specific configuration. + end +end +``` + +You can of course use any code necessary to determine the value of attributes: + +```ruby +application 'my_app' do + repository 'http://git.example.com/my-app.git' + revision node.chef_environment == 'production' ? 'production' : 'develop' +end +``` + +Attributes from the application and from sub-resources are merged together: + +```ruby +application 'my_app' do + restart_command 'kill -1 `cat /var/run/one.pid`' + environment 'LC_ALL' => 'en', 'FOO' => 'bar' + + rails do + restart_command 'touch /tmp/something' + environment 'LC_ALL' => 'en_US' + end + + passenger_apache2 do + environment 'FOO' => 'baz' + end +end + +# at the end, you will have: +# +# restart_command #=> kill -1 `cat /var/run/one.pid` && touch /tmp/something +# environment #=> LC_ALL=en_US FOO=baz +``` + +Most databases have the concept of migrations (or you can just use your own): + +```ruby +application 'my_app' do + path '/deploy/to/dir' + owner 'app-user' + group 'app-group' + + repository 'http://git.example.com/my-app.git' + revision 'production' + + php do + migrate true + migration_command 'your-applications-migrate-command' + end +end +``` + +This will run `your-applications-migrate-command`, with the current directory set to the directory of the current checkout. + +To use the application cookbook, we recommend creating a role named after the application, e.g. `my_app`. Create a Ruby DSL role in your chef-repo, or create the role directly with knife. + +```ruby +name 'my_app' +description 'My application deployment' +run_list([ + 'recipe[my_app::default]' +]) +``` + +License and Authors +------------------- +- Author: Adam Jacob () +- Author: Andrea Campi () +- Author: Joshua Timberman () +- Author: Noah Kantrowitz () +- Author: Seth Chisamore () + +```text +Copyright 2009-2013, Opscode, Inc. + +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/cookbooks/application/libraries/default.rb b/cookbooks/application/libraries/default.rb new file mode 100644 index 0000000..7cf6d2a --- /dev/null +++ b/cookbooks/application/libraries/default.rb @@ -0,0 +1,189 @@ +# +# Author:: Noah Kantrowitz +# Cookbook Name:: application +# Library:: default +# +# Copyright:: 2011-2012, Opscode, Inc +# +# 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. +# + +require "chef/mixin/from_file" + +class Chef + class Resource + # Monkey-Patch the blacklists to prevent infinite recursion in #to_json and similar + # (this is global state,thus the set-union operator to change it idempotently + to not leak) + [ :@application, :@application_provider ].each do |ivar| + FORBIDDEN_IVARS << ivar unless FORBIDDEN_IVARS.include?(ivar) + HIDDEN_IVARS << ivar unless HIDDEN_IVARS.include?(ivar) + end + end +end + +class ApplicationCookbook + + module OptionsCollector + def options + @options ||= {} + end + + def method_missing(method_sym, value=nil, &block) + super + rescue NameError + value ||= block + method_sym = method_sym.to_s.chomp('=').to_sym + options[method_sym] = value if value + options[method_sym] ||= nil + end + end + + module ResourceBase + def self.included(klass) + klass.actions :before_compile, :before_deploy, :before_migrate, :before_symlink, :before_restart, :after_restart + klass.attribute :id, :kind_of => String, :name_attribute => true + klass.attribute :environment, :kind_of => Hash, :default => {} + klass.attribute :purge_before_symlink, :kind_of => Array, :default => [] + klass.attribute :create_dirs_before_symlink, :kind_of => Array, :default => [] + klass.attribute :symlinks, :kind_of => Hash, :default => {} + klass.attribute :symlink_before_migrate, :kind_of => Hash, :default => {} + klass.attribute :migration_command, :kind_of => [String, NilClass], :default => nil + klass.attribute :application + klass.attribute :application_provider + klass.attribute :type + end + + def restart_command(arg=nil, &block) + arg ||= block + raise "Invalid restart command" unless !arg || arg.is_a?(String) || arg.is_a?(Proc) + @restart_command = arg if arg + @restart_command + end + + def method_missing(name, *args) + if application.respond_to? name + application.send(name, *args) + else + super + end + end + + class OptionsBlock + include ApplicationCookbook::OptionsCollector + end + + def options_block(options=nil, &block) + options ||= {} + if block + collector = OptionsBlock.new + collector.instance_eval(&block) + options.update(collector.options) + end + options + end + + def find_matching_role(role, single=true, &block) + return nil if !role + nodes = [] + if node['roles'].include? role + nodes << node + end + if !single || nodes.empty? + search(:node, "role:#{role} AND chef_environment:#{node.chef_environment}") do |n| + nodes << n + end + end + if block + nodes.each do |n| + yield n + end + else + if single + nodes.first + else + nodes + end + end + end + + def find_database_server(role) + dbm = find_matching_role(role) + Chef::Log.warn("No node with role #{role}") if role && !dbm + + if respond_to?(:database) && database.has_key?('host') + database['host'] + elsif dbm && dbm.attribute?('cloud') + dbm['cloud']['local_ipv4'] + elsif dbm + dbm['ipaddress'] + end + end + end + + module ProviderBase + + def self.included(klass) + klass.send(:include, Chef::Mixin::FromFile) + end + + def deploy_provider + @deploy_provider ||= begin + provider = @deploy_resource.provider_for_action(:nothing) + provider.load_current_resource + provider + end + end + + def release_path + deploy_provider.release_path + end + + def shared_path + @deploy_resource.shared_path + end + + def callback(what, callback_code=nil) + Chef::Log.debug("Got callback #{what}: #{callback_code.inspect}") + @collection = Chef::ResourceCollection.new + case callback_code + when Proc + Chef::Log.info "#{@new_resource} running callback #{what}" + safe_recipe_eval(&callback_code) + when String + callback_file = "#{release_path}/#{callback_code}" + unless ::File.exist?(callback_file) + raise RuntimeError, "Can't find your callback file #{callback_file}" + end + run_callback_from_file(callback_file) + when nil + nil + else + raise RuntimeError, "You gave me a callback I don't know what to do with: #{callback_code.inspect}" + end + end + + def run_callback_from_file(callback_file) + if ::File.exist?(callback_file) + Dir.chdir(release_path) do + Chef::Log.info "#{@new_resource} running deploy hook #{callback_file}" + safe_recipe_eval { from_file(callback_file) } + end + end + end + + def safe_recipe_eval(&callback_code) + recipe_eval(&callback_code) + converge if respond_to?(:converge) + end + end +end diff --git a/cookbooks/application/libraries/matchers.rb b/cookbooks/application/libraries/matchers.rb new file mode 100644 index 0000000..eb94f1e --- /dev/null +++ b/cookbooks/application/libraries/matchers.rb @@ -0,0 +1,11 @@ +if defined?(ChefSpec) + + def deploy_application(resource) + ChefSpec::Matchers::ResourceMatcher.new(:application, :deploy, resource) + end + + def force_deploy_application(resource) + ChefSpec::Matchers::ResourceMatcher.new(:application, :force_deploy, resource) + end + +end diff --git a/cookbooks/application/metadata.json b/cookbooks/application/metadata.json new file mode 100644 index 0000000..bea510a --- /dev/null +++ b/cookbooks/application/metadata.json @@ -0,0 +1,30 @@ +{ + "name": "application", + "version": "4.1.6", + "description": "Deploys and configures a variety of applications", + "long_description": "Application cookbook\n====================\nThis cookbook is designed to be able to describe and deploy web applications. It provides the basic infrastructure; other cookbooks are required to support specific combinations of frameworks and application servers. The following cookbooks are available at this time:\n\n- [application_java](https://github.com/opscode-cookbooks/application_java) (Java and Tomcat)\n- [application_nginx](https://github.com/opscode-cookbooks/application_nginx) (nginx reverse proxy)\n- [application_php](https://github.com/opscode-cookbooks/application_php) (PHP with `mod_php_apache2`)\n- [application_python](https://github.com/opscode-cookbooks/application_python) (Django with Gunicorn)\n- [application_ruby](https://github.com/opscode-cookbooks/application_ruby) (Rails with Passenger or Unicorn)\n\n\nBackwards Compatibility\n-----------------------\n- Version 4.0.0 dropped support for Chef 10\n- Version 2.0.0 dropped support for the `apps` data bag.\n\n\nRequirements\n------------\nThe previous dependencies have been moved out to the application-stack-specific cookbooks, and this cookbook has no external dependencies.\n\n\nResources/Providers\n-------------------\nThe `application` LWRP configures the basic properties of most applications, regardless of the framework or application server they use. These include:\n\n- SCM information for the deployment, such as the repository URL and branch name;\n- deployment destination, including the filesystem path to deploy to;\n- any OS packages to install as dependencies;\n- optional callback to control the deployment.\n\nThis LWRP uses the `deploy_revision` LWRP to perform the bulk of its tasks, and many concepts and parameters map directly to it. Check the documentation for `deploy_revision` for more information.\n\nConfiguration of framework-specific aspects of the application are performed by invoking a sub-resource; see the appropriate cookbook for more documentation.\n\n### Actions\n- `:deploy`: deploy an application, including any necessary configuration, restarting the associated service if necessary\n- `:force_deploy`: same as `:deploy`, but it will send a `:force_deploy` action to the deploy resource, directing it to deploy the application even if the same revision is already deployed\n\n### Attribute Parameters\n- `name`: name attribute. The name of the application you are setting up. This will be used to derive the default value for other attribute\n- `packages`: an Array or Hash of packages to be installed before starting the deployment\n- `path`: target path of the deployment; it will be created if it does not exist\n- `owner`: the user that shall own the target path\n- `group`: the group that shall own the target path\n- `keep_releases`: count of keep releases\n- `strategy`: the underlying LWRP that will be used to perform the deployment. The default is `:deploy_revision`, and it should never be necessary to change it\n- `scm_provider`: the provider class to use for the deployment. It defaults to `Chef::Provider::Git`, you can set it to `Chef::Provider::Subversion` to deploy from an SVN repository\n- `repository`: the URL of the repository the application should be checked out from\n- `revision`: an identifier pointing to the revision that should be checked out\n- `deploy_key`: the private key to use to access the repository via SSH, or path to a file containing the key\n- `rollback_on_error`: if true, exceptions during a deployment will be caught and a clean rollback to the previous version will be attempted; the exception will then be re-raised. Defaults to true; change it only if you know what you are doing\n- `environment`: a Hash of environment variables to set while running migrations\n- `purge_before_symlink`: an Array of paths (relative to the checkout) to remove before creating symlinks\n- `create_dirs_before_symlink`: an Array of paths (relative to the checkout) pointing to directories to create before creating symlinks\n- `symlinks`: a Hash of shared/dir/path => release/dir/path. It determines which files and dirs in the shared directory get symlinked to the current release directory\n- `symlink_before_migrate`: similar to symlinks, except that they will be linked before any migration is run\n- `migrate`: if `true` then migrations will be run; defaults to false\n- `migration_command`: a command to run to migrate the application from the previous to the current state\n- `restart_command`: a command to run when restarting the application\n- `environment_name`: the name of a framework-specific \"environment\" (for example the Rails environment). By default it is the same as the Chef environment, unless it is `_default`, in which case it is set to `production`\n- `enable_submodules`: whether to enable git submodules in the deploy, passed into the deploy resource.\n\n### Callback Attributes\nYou can also set a few attributes on this LWRP that are interpreted as callback to be called at specific points during a deployment. If you pass a block, it will be evaluated within a new context. If you pass a string, it will be interpreted as a path (relative to the release directory) to a file; if it exists, it will be loaded and evaluated as though it were a Chef recipe.\n\nThe following callback attributes are available:\n\n- `before_deploy`: invoked immediately after initial setup and before the deployment proper is started. This callback will be invoked on every Chef run\n- `before_migrate`\n- `before_symlink`\n- `before_restart`\n- `after_restart`\n\n### Sub-resources\nAnything that is not a known attribute will be interpreted as the name of a sub-resource; the resource will be looked up, and any nested attribute will be passed to it. More than one sub-resource can be added to an application; the order is significant, with the latter sub-resources overriding any sub-resource that comes before.\n\nSub-resources can set their own values for some attributes; if they do, they will be merged together with the attribute set on the main resource. The attributes that support this behavior are the following:\n\n- `environment`: environment variables from the application and from sub-resources will be merged together, with later resources overriding values set in the application or previous resources\n- `migration_command`: commands from the application and from sub-resources will be concatenated together joined with '&&' and run as a single shell command. The migration will only succeed if all the commands succeed\n- `restart_command`: commands from the application and from sub-resources will be evaluated in order\n- `symlink_before_migrate`: will be concatenated as a single array\n- `callbacks`: sub-resources callbacks will be invoked first, followed by the application callbacks\n\n\nUsage\n-----\nTo use the application cookbook we recommend creating a cookbook named after the application, e.g. `my_app`. In `metadata.rb` you should declare a dependency on this cookbook and any framework cookbook the application may need. For example a Rails application may include:\n\n```ruby\ndepends 'application'\ndepends 'application_ruby'\n```\n\nThe default recipe should describe your application using the `application` LWRP; you may also include additional recipes, for example to set up a database, queues, search engines and other components of your application.\n\nA recipe using this LWRP may look like this:\n\n```ruby\napplication 'my_app' do\n path '/deploy/to/dir'\n owner 'app-user'\n group 'app-group'\n\n repository 'http://git.example.com/my-app.git'\n revision 'production'\n\n # Apply the rails LWRP from application_ruby\n rails do\n # Rails-specific configuration. See the README in the\n # application_ruby cookbook for more information.\n end\n\n # Apply the passenger_apache2 LWRP, also from application_ruby\n passenger_apache2 do\n # Passenger-specific configuration.\n end\nend\n```\n\nYou can of course use any code necessary to determine the value of attributes:\n\n```ruby\napplication 'my_app' do\n repository 'http://git.example.com/my-app.git'\n revision node.chef_environment == 'production' ? 'production' : 'develop'\nend\n```\n\nAttributes from the application and from sub-resources are merged together:\n\n```ruby\napplication 'my_app' do\n restart_command 'kill -1 `cat /var/run/one.pid`'\n environment 'LC_ALL' => 'en', 'FOO' => 'bar'\n\n rails do\n restart_command 'touch /tmp/something'\n environment 'LC_ALL' => 'en_US'\n end\n\n passenger_apache2 do\n environment 'FOO' => 'baz'\n end\nend\n\n# at the end, you will have:\n#\n# restart_command #=> kill -1 `cat /var/run/one.pid` && touch /tmp/something\n# environment #=> LC_ALL=en_US FOO=baz\n```\n\nMost databases have the concept of migrations (or you can just use your own):\n\n```ruby\napplication 'my_app' do\n path '/deploy/to/dir'\n owner 'app-user'\n group 'app-group'\n\n repository 'http://git.example.com/my-app.git'\n revision 'production'\n\n php do\n migrate true\n migration_command 'your-applications-migrate-command'\n end\nend\n```\n\nThis will run `your-applications-migrate-command`, with the current directory set to the directory of the current checkout.\n\nTo use the application cookbook, we recommend creating a role named after the application, e.g. `my_app`. Create a Ruby DSL role in your chef-repo, or create the role directly with knife.\n\n```ruby\nname 'my_app'\ndescription 'My application deployment'\nrun_list([\n 'recipe[my_app::default]'\n])\n```\n\nLicense and Authors\n-------------------\n- Author: Adam Jacob ()\n- Author: Andrea Campi ()\n- Author: Joshua Timberman ()\n- Author: Noah Kantrowitz ()\n- Author: Seth Chisamore ()\n\n```text\nCopyright 2009-2013, Opscode, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "maintainer": "Opscode, Inc.", + "maintainer_email": "cookbooks@opscode.com", + "license": "Apache 2.0", + "platforms": { + }, + "dependencies": { + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + "application": "Empty placeholder recipe, use the LWRPs, see README.md." + } +} \ No newline at end of file diff --git a/cookbooks/application/providers/default.rb b/cookbooks/application/providers/default.rb new file mode 100644 index 0000000..f9a0a1f --- /dev/null +++ b/cookbooks/application/providers/default.rb @@ -0,0 +1,191 @@ +# +# Author:: Noah Kantrowitz +# Cookbook Name:: application +# Provider:: default +# +# Copyright:: 2011-2012, Opscode, Inc +# +# 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. +# + +include ApplicationCookbook::ProviderBase + +action :deploy do + + before_compile + + before_deploy + + run_deploy + +end + +action :force_deploy do + + before_compile + + before_deploy + + run_deploy(true) + +end + +action :restart do + + before_compile + + run_actions_with_context(:before_restart, @run_context) + + run_restart + + run_actions_with_context(:after_restart, @run_context) + + @new_resource.updated_by_last_action(true) + +end + +protected + +def before_compile + new_resource.application_provider self + new_resource.sub_resources.each do |resource| + resource.application_provider self + resource.run_action :before_compile + end +end + +def before_deploy + new_resource.packages.each do |pkg,ver| + package pkg do + action :install + version ver if ver && ver.length > 0 + end + end + + directory new_resource.path do + owner new_resource.owner + group new_resource.group + mode '0755' + recursive true + end + + directory "#{new_resource.path}/shared" do + owner new_resource.owner + group new_resource.group + mode '0755' + recursive true + end + + if new_resource.deploy_key + + if ::File.exists?(new_resource.deploy_key) + deploy_key = open(new_resource.deploy_key, &:read) + else + deploy_key = new_resource.deploy_key + end + + file "#{new_resource.path}/id_deploy" do + content deploy_key + owner new_resource.owner + group new_resource.group + mode '0600' + end + + template "#{new_resource.path}/deploy-ssh-wrapper" do + source "deploy-ssh-wrapper.erb" + cookbook "application" + owner new_resource.owner + group new_resource.group + mode "0755" + variables :id => new_resource.name, :deploy_to => new_resource.path, :strict_ssh => new_resource.strict_ssh + end + end + + ruby_block "#{new_resource.name} before_deploy" do + block do + new_resource.sub_resources.each do |resource| + resource.run_action :before_deploy + end + callback(:before_deploy, new_resource.before_deploy) + end + end +end + +def run_deploy(force = false) + # Alias to a variable so I can use in sub-resources + new_resource = @new_resource + # Also alias to variable so it can be used in sub-resources + app_provider = self + + @deploy_resource = send(new_resource.strategy.to_sym, new_resource.name) do + action force ? :force_deploy : :deploy + scm_provider new_resource.scm_provider + revision new_resource.revision + repository new_resource.repository + enable_submodules new_resource.enable_submodules + user new_resource.owner + group new_resource.group + keep_releases new_resource.keep_releases + deploy_to new_resource.path + ssh_wrapper "#{new_resource.path}/deploy-ssh-wrapper" if new_resource.deploy_key + shallow_clone new_resource.shallow_clone + rollback_on_error new_resource.rollback_on_error + all_environments = ([new_resource.environment]+new_resource.sub_resources.map{|res| res.environment}).inject({}){|acc, val| acc.merge(val)} + environment all_environments + migrate new_resource.migrate + all_migration_commands = ([new_resource.migration_command]+new_resource.sub_resources.map{|res| res.migration_command}).select{|cmd| cmd && !cmd.empty?} + migration_command all_migration_commands.join(' && ') + restart_command do + ([new_resource]+new_resource.sub_resources).each do |res| + cmd = res.restart_command + if cmd.is_a? Proc + app_provider.deploy_provider.instance_eval(&cmd) # @see libraries/default.rb + elsif cmd && !cmd.empty? + execute cmd do + user new_resource.owner + group new_resource.group + environment all_environments + end + end + end + end + purge_before_symlink (new_resource.purge_before_symlink + new_resource.sub_resources.map(&:purge_before_symlink)).flatten + create_dirs_before_symlink (new_resource.create_dirs_before_symlink + new_resource.sub_resources.map(&:create_dirs_before_symlink)).flatten + all_symlinks = [new_resource.symlinks]+new_resource.sub_resources.map{|res| res.symlinks} + symlinks all_symlinks.inject({}){|acc, val| acc.merge(val)} + all_symlinks_before_migrate = [new_resource.symlink_before_migrate]+new_resource.sub_resources.map{|res| res.symlink_before_migrate} + symlink_before_migrate all_symlinks_before_migrate.inject({}){|acc, val| acc.merge(val)} + before_migrate do + app_provider.send(:run_actions_with_context, :before_migrate, @run_context) + end + before_symlink do + app_provider.send(:run_actions_with_context, :before_symlink, @run_context) + end + before_restart do + app_provider.send(:run_actions_with_context, :before_restart, @run_context) + end + after_restart do + app_provider.send(:run_actions_with_context, :after_restart, @run_context) + end + end +end + +def run_actions_with_context(action, context) + new_resource.sub_resources.each do |resource| + saved_run_context = resource.instance_variable_get :@run_context + resource.instance_variable_set :@run_context, context + resource.run_action action + resource.instance_variable_set :@run_context, saved_run_context + end + callback(action, new_resource.send(action)) +end diff --git a/cookbooks/application/recipes/default.rb b/cookbooks/application/recipes/default.rb new file mode 100644 index 0000000..ec67eb6 --- /dev/null +++ b/cookbooks/application/recipes/default.rb @@ -0,0 +1,19 @@ +# +# Cookbook Name:: application +# Recipe:: default +# +# Copyright:: 2012, Opscode, Inc +# +# 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. +# +# Empty placeholder recipe, use the LWRPs, see README.md. diff --git a/cookbooks/application/resources/default.rb b/cookbooks/application/resources/default.rb new file mode 100644 index 0000000..ad19c50 --- /dev/null +++ b/cookbooks/application/resources/default.rb @@ -0,0 +1,178 @@ +# +# Author:: Noah Kantrowitz +# Cookbook Name:: application +# Resource:: default +# +# Copyright:: 2011-2012, Opscode, Inc +# +# 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. +# + +require 'weakref' + +include Chef::DSL::Recipe + +def initialize(*args) + super + @action = :deploy + @sub_resources = [] +end + +actions :deploy, :force_deploy + +attribute :name, :kind_of => String, :name_attribute => true +attribute :environment_name, :kind_of => String, :default => (node.chef_environment =~ /_default/ ? "production" : node.chef_environment) +attribute :path, :kind_of => String +attribute :owner, :kind_of => String +attribute :group, :kind_of => String +attribute :keep_releases, :kind_of => Integer, :default => 5 +attribute :strategy, :kind_of => [String, Symbol], :default => :deploy_revision +attribute :scm_provider, :kind_of => [Class, String, Symbol] +attribute :revision, :kind_of => String +attribute :repository, :kind_of => String +attribute :enable_submodules, :kind_of => [TrueClass, FalseClass], :default => false +attribute :environment, :kind_of => Hash, :default => {} +attribute :deploy_key, :kind_of => [String, NilClass], :default => nil +attribute :strict_ssh, :kind_of => [TrueClass, FalseClass], :default => false +attribute :shallow_clone, :kind_of => [TrueClass, FalseClass], :default => false +attribute :force, :kind_of => [TrueClass, FalseClass], :default => false +attribute :rollback_on_error, :kind_of => [TrueClass, FalseClass], :default => true +attribute :purge_before_symlink, :kind_of => Array, :default => [] +attribute :create_dirs_before_symlink, :kind_of => Array, :default => [] +attribute :symlinks, :kind_of => Hash, :default => {} +attribute :symlink_before_migrate, :kind_of => Hash, :default => {} +attribute :migrate, :kind_of => [TrueClass, FalseClass], :default => false +attribute :migration_command, :kind_of => [String, NilClass], :default => nil +attribute :packages, :kind_of => [Array, Hash], :default => [] +attribute :application_provider +attr_reader :sub_resources + +def restart_command(arg=nil, &block) + arg ||= block + set_or_return(:restart_command, arg, :kind_of => [Proc, String]) +end + +# Callback fires before deploy is started. +def before_deploy(arg=nil, &block) + arg ||= block + set_or_return(:before_deploy, arg, :kind_of => [Proc, String]) +end + +# Callback fires before migration is run. +def before_migrate(arg=nil, &block) + arg ||= block + set_or_return(:before_migrate, arg, :kind_of => [Proc, String]) +end + +# Callback fires before symlinking +def before_symlink(arg=nil, &block) + arg ||= block + set_or_return(:before_symlink, arg, :kind_of => [Proc, String]) +end + +# Callback fires before restart +def before_restart(arg=nil, &block) + arg ||= block + set_or_return(:before_restart, arg, :kind_of => [Proc, String]) +end + +# Callback fires after restart +def after_restart(arg=nil, &block) + arg ||= block + set_or_return(:after_restart, arg, :kind_of => [Proc, String]) +end + +def release_path + application_provider.release_path +end + +def shared_path + application_provider.shared_path +end + +def method_missing(name, *args, &block) + # Build the set of names to check for a valid resource + lookup_path = ["application_#{name}"] + run_context.cookbook_collection.each do |cookbook_name, cookbook_ver| + if cookbook_name.start_with?("application_") + lookup_path << "#{cookbook_name}_#{name}" + end + end + lookup_path << name + resource = nil + # Try to find our resource + lookup_path.each do |resource_name| + begin + Chef::Log.debug "Trying to load application resource #{resource_name} for #{name}" + resource = super(resource_name.to_sym, self.name, &block) + break + rescue NameError => e + # Works on any MRI ruby + if e.name == resource_name.to_sym || e.inspect =~ /\b#{resource_name}\b/ + next + else + raise e + end + end + end + raise NameError, "No resource found for #{name}. Tried #{lookup_path.join(', ')}" unless resource + # Enforce action :nothing in case people forget + resource.action :nothing + # Make this a weakref to prevent a cycle between the application resource and the sub resources + resource.application WeakRef.new(self) + resource.type name + @sub_resources << resource + resource +end + +def do_i_respond_to?(*args) + name = args.first.to_s + # Build the set of names to check for a valid resource + lookup_path = ["application_#{name}"] + run_context.cookbook_collection.each do |cookbook_name, cookbook_ver| + if cookbook_name.start_with?("application_") + lookup_path << "#{cookbook_name}_#{name}" + end + end + lookup_path << name + found = false + # Try to find our resource + lookup_path.each do |resource_name| + begin + Chef::Log.debug "Looking for application resource #{resource_name} for #{name}" + Chef::Resource.resource_for_node(resource_name.to_sym, node) + found = true + break + rescue NameError => e + # Keep calm and carry on + end + end + found +end + +# If we are using a current version of ruby, use respond_to_missing? +# instead of respond_to? so we provide proper behavior +if(Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('1.9.1')) + def respond_to_missing?(*args) + super || do_i_respond_to?(*args) + end +else + def respond_to?(*args) + super || do_i_respond_to?(*args) + end +end + +def to_ary + nil +end +alias :to_a :to_ary diff --git a/cookbooks/application/templates/default/deploy-ssh-wrapper.erb b/cookbooks/application/templates/default/deploy-ssh-wrapper.erb new file mode 100644 index 0000000..fcf0c81 --- /dev/null +++ b/cookbooks/application/templates/default/deploy-ssh-wrapper.erb @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# +# Deploy SSH Wrapper +# App: <%= @id %> +# +# Rendered by Chef - local changes will be replaced + +/usr/bin/env ssh <% unless @strict_ssh %>-o "StrictHostKeyChecking=no" <% end %>-i "<%= @deploy_to %>/id_deploy" $@ diff --git a/cookbooks/application_nodejs/LICENSE b/cookbooks/application_nodejs/LICENSE new file mode 100644 index 0000000..11069ed --- /dev/null +++ b/cookbooks/application_nodejs/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/cookbooks/application_nodejs/README.md b/cookbooks/application_nodejs/README.md new file mode 100644 index 0000000..0a1dba4 --- /dev/null +++ b/cookbooks/application_nodejs/README.md @@ -0,0 +1,64 @@ +## Description + +This cookbook is designed to be able to describe and deploy Node.js web applications using Upstart. + +Note that this cookbook provides the Node-specific bindings for the `application` cookbook; you will find general documentation in that cookbook. + +## Requirements + +Chef 0.10.0 or higher required (for Chef environment use). + +Upstart 1.4 or higher for the use of `setuid` in the default Upstart configuration template. +This requirement can be worked around by specifying a custom template. + +The following Opscode cookbooks are dependencies: + +* [application](https://github.com/opscode-cookbooks/application) +* [nodejs](https://github.com/mdxp/nodejs-cookbook) + +## Resources/Providers + +The `nodejs` sub-resource LWRP deals with deploying Node.js applications using Upstart. It is not meant to be used by itself; make sure you are familiar with the `application` cookbook before proceeding. + +### Attribute Parameters + +- **npm**: If `true`, `npm` will be used to install the dependencies specified in `packages.json`. Defaults to `true`. +- **template**: The name of the template that will be used to create the Upstart configuration file. If specified, it will be looked up in the application cookbook. Defaults to `nodejs.upstart.conf.erb` from this cookbook. +- **entry_point**: The argument to pass to node. Defaults to `app.js`. + +## Usage + +Here is an example hello world application using [Express](http://expressjs.com): + +``` +application "hello-world" do + path "/srv/node-hello-world" + owner "www-data" + group "www-data" + packages ["git"] + + repository "git://github.com/visionmedia/express.git" + + nodejs do + entry_point "examples/hello-world" + end +end +``` + +## License and Author + +Author:: Conrad Kramer () + +Copyright 2013, Kramer Software Productions, LLC. + +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/cookbooks/application_nodejs/metadata.rb b/cookbooks/application_nodejs/metadata.rb new file mode 100644 index 0000000..66dde51 --- /dev/null +++ b/cookbooks/application_nodejs/metadata.rb @@ -0,0 +1,12 @@ +name "application_nodejs" +maintainer "Conrad Kramer" +maintainer_email "conrad@kramerapps.com" +license "Apache 2.0" +description "Deploys and configures Node.js applications" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version "2.0.1" + +depends "nodejs" +depends "application" + +supports 'ubuntu', ">= 12.04" diff --git a/cookbooks/application_nodejs/providers/nodejs.rb b/cookbooks/application_nodejs/providers/nodejs.rb new file mode 100644 index 0000000..73ab654 --- /dev/null +++ b/cookbooks/application_nodejs/providers/nodejs.rb @@ -0,0 +1,132 @@ +# +# Author:: Conrad Kramer +# Cookbook Name:: application_node +# Resource:: node +# +# Copyright:: 2013, Kramer Software Productions, LLC. +# +# 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. +# + +include Chef::DSL::IncludeRecipe + +action :before_compile do + + include_recipe 'nodejs::nodejs_from_package' + + r = new_resource + + if new_resource.npm + include_recipe 'nodejs::npm' + end + + unless new_resource.restart_command + new_resource.restart_command do + + service "#{r.application.name}_nodejs" do + if platform?('ubuntu') && node[:platform_version].to_f >= 14.04 + provider Chef::Provider::Service::Systemd + else + provider Chef::Provider::Service::Upstart + end + supports :restart => true, :start => true, :stop => true + action [:enable, :restart] + end + + end + end + + new_resource.environment.update({ + 'NODE_ENV' => r.environment_name + }) + +end + +action :before_deploy do + + new_resource.environment['NODE_ENV'] = new_resource.environment_name + + r = new_resource + + service "#{r.application.name}_nodejs" do + if platform?('ubuntu') && node[:platform_version].to_f >= 14.04 + provider Chef::Provider::Service::Systemd + else + provider Chef::Provider::Service::Upstart + end + end + + if platform?('ubuntu') && node[:platform_version].to_f >= 14.04 + execute "systemctl daemon-reload" do + command "systemctl daemon-reload" + action :nothing + end + + template "#{r.application.name}_nodejs.systemd.service.erb" do + path "/lib/systemd/system/#{r.application.name}_nodejs.service" + source r.template ? r.template : 'nodejs.systemd.service.erb' + cookbook r.template ? r.cookbook_name.to_s : 'application_nodejs' + owner 'root' + group 'root' + mode '0644' + variables( + :user => r.owner, + :group => r.group, + :app_dir => r.release_path, + :entry => r.entry_point, + :environment => r.environment + ) + notifies :run, "execute[systemctl daemon-reload]", :delayed + notifies :restart, "service[#{r.application.name}_nodejs]", :delayed + end + else + template "#{new_resource.application.name}.upstart.conf" do + path "/etc/init/#{r.application.name}_nodejs.conf" + source r.template ? r.template : 'nodejs.upstart.conf.erb' + cookbook r.template ? r.cookbook_name.to_s : 'application_nodejs' + owner 'root' + group 'root' + mode '0644' + variables( + :user => r.owner, + :group => r.group, + :app_dir => r.release_path, + :entry => r.entry_point, + :environment => r.environment + ) + notifies :restart, "service[#{r.application.name}_nodejs]", :delayed + end + end +end + +action :before_migrate do + + if new_resource.npm + execute 'npm install' do + cwd new_resource.release_path + user new_resource.owner + group new_resource.group + environment new_resource.environment.merge({ 'HOME' => new_resource.shared_path }) + end + end + +end + +action :before_symlink do +end + +action :before_restart do +end + +action :after_restart do +end diff --git a/cookbooks/application_nodejs/resources/nodejs.rb b/cookbooks/application_nodejs/resources/nodejs.rb new file mode 100644 index 0000000..1ee52b3 --- /dev/null +++ b/cookbooks/application_nodejs/resources/nodejs.rb @@ -0,0 +1,25 @@ +# +# Author:: Conrad Kramer +# Cookbook Name:: application_node +# Resource:: node +# +# Copyright:: 2013, Kramer Software Productions, LLC. +# +# 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. +# + +include ApplicationCookbook::ResourceBase + +attribute :npm, :kind_of => [NilClass, TrueClass, FalseClass], :default => true +attribute :template, :kind_of => [String, NilClass], :default => nil +attribute :entry_point, :kind_of => String, :default => 'app.js' diff --git a/cookbooks/application_nodejs/templates/default/nodejs.systemd.service.erb b/cookbooks/application_nodejs/templates/default/nodejs.systemd.service.erb new file mode 100644 index 0000000..99fa0f0 --- /dev/null +++ b/cookbooks/application_nodejs/templates/default/nodejs.systemd.service.erb @@ -0,0 +1,4 @@ +[Service] +ExecStart=/usr/bin/env node <%= @entry %> +User=<%= @user %> +Group=<%= @group %> diff --git a/cookbooks/application_nodejs/templates/default/nodejs.upstart.conf.erb b/cookbooks/application_nodejs/templates/default/nodejs.upstart.conf.erb new file mode 100644 index 0000000..ab1e8be --- /dev/null +++ b/cookbooks/application_nodejs/templates/default/nodejs.upstart.conf.erb @@ -0,0 +1,19 @@ +#!upstart +description "Node.js Application Server" + +start on (local-filesystems and net-device-up IFACE!=lo) +stop on [!12345] + +console log + +<% @environment.each do |key, value| -%> +env <%= key %>="<%= value %>" +<% end -%> +<% unless @user.nil? -%> +setuid <%= @user %> +<% end -%> +<% unless @group.nil? -%> +setgid <%= @group %> +<% end -%> +chdir <%= @app_dir %> +exec /usr/bin/env node <%= @entry %> diff --git a/cookbooks/apt/.gitignore b/cookbooks/apt/.gitignore new file mode 100644 index 0000000..7a0d26f --- /dev/null +++ b/cookbooks/apt/.gitignore @@ -0,0 +1,15 @@ +.vagrant +Berksfile.lock +Gemfile.lock +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ +.bundle +.cache +.kitchen +bin +.kitchen.local.yml +.coverage \ No newline at end of file diff --git a/cookbooks/apt/.kitchen.cloud.yml b/cookbooks/apt/.kitchen.cloud.yml new file mode 100644 index 0000000..72e2457 --- /dev/null +++ b/cookbooks/apt/.kitchen.cloud.yml @@ -0,0 +1,47 @@ +--- +driver_config: + digitalocean_client_id: <%= ENV['DIGITAL_OCEAN_CLIENT_ID'] %> + digitalocean_api_key: <%= ENV['DIGITAL_OCEAN_API_KEY'] %> + +provisioner: + name: chef_zero + require_chef_omnibus: latest + +platforms: +- name: ubuntu-1004 + driver_plugin: digitalocean + driver_config: + image_id: 5566812 + flavor_id: 63 + region_id: 4 + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + run_list: + - recipe[apt] + +- name: ubuntu-1204 + driver_plugin: digitalocean + driver_config: + image_id: 5588928 + flavor_id: 63 + region_id: 4 + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + run_list: + - recipe[apt] + +- name: ubuntu-1404 + driver_plugin: digitalocean + driver_config: + image_id: 5141286 + flavor_id: 63 + region_id: 4 + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + run_list: + - recipe[apt] + +suites: + - name: default + run_list: + - recipe[apt] diff --git a/cookbooks/apt/.kitchen.yml b/cookbooks/apt/.kitchen.yml new file mode 100644 index 0000000..4a60a98 --- /dev/null +++ b/cookbooks/apt/.kitchen.yml @@ -0,0 +1,62 @@ +driver: + name: vagrant + +platforms: + - name: debian-7.2.0 + run_list: apt::default + # - name: debian-8.0 + # run_list: apt::default + - name: ubuntu-10.04 + run_list: apt::default + - name: ubuntu-12.04 + run_list: apt::default + - name: ubuntu-13.04 + run_list: apt::default + - name: ubuntu-13.10 + run_list: apt::default + - name: ubuntu-14.04 + run_list: apt::default + # driver: + # box: chef/ubuntu-14.04 + +suites: + - name: default + run_list: + - recipe[minitest-handler] + - recipe[apt_test] + + - name: cacher-client + run_list: + - recipe[minitest-handler] + - recipe[apt_test::cacher-client] + + - name: cacher-ng + run_list: + - recipe[minitest-handler] + - recipe[apt_test::cacher-ng] + + - name: cacher-ng-client + run_list: + - recipe[minitest-handler] + - recipe[apt_test::cacher-ng-client] + attributes: + apt: + cacher_dir: '/tmp/apt-cacher' + cacher_port: '9876' + cacher_interface: 'eth0' + compiletime: true + + - name: lwrps + run_list: + - recipe[minitest-handler] + - recipe[apt_test::lwrps] + + - name: unattended-upgrades + run_list: + - recipe[minitest-handler] + - recipe[apt_test::unattended-upgrades] + attributes: + apt: + unattended_upgrades: + enable: true + diff --git a/cookbooks/apt/.rubocop.yml b/cookbooks/apt/.rubocop.yml new file mode 100644 index 0000000..6c26265 --- /dev/null +++ b/cookbooks/apt/.rubocop.yml @@ -0,0 +1,37 @@ +AllCops: + Exclude: + - vendor/** + - metadata.rb + - Guardfile + - test/cookbooks/apt_test/metadata.rb + - .kitchen/** + +# Disable ABCSize because it doesn't fit well with resources +AbcSize: + Enabled: false +AssignmentInCondition: + Enabled: false +ClassAndModuleChildren: + Enabled: false +ClassLength: + Enabled: false +CyclomaticComplexity: + Max: 15 +Documentation: + Enabled: false +Encoding: + Enabled: false +FileName: + Enabled: false +HashSyntax: + Enabled: false +LineLength: + Enabled: false +MethodLength: + Enabled: false +ParameterLists: + Enabled: false +# StringLiterals: +# EnforcedStyle: double_quotes +TrailingComma: + Enabled: false diff --git a/cookbooks/apt/.travis.yml b/cookbooks/apt/.travis.yml new file mode 100644 index 0000000..d088058 --- /dev/null +++ b/cookbooks/apt/.travis.yml @@ -0,0 +1,44 @@ +language: ruby +bundler_args: --without kitchen_vagrant +rvm: +- 2.1.0 +before_install: +- echo -n $DO_KEY_CHUNK_{0..30} >> ~/.ssh/id_do.base64 +- cat ~/.ssh/id_do.base64 | tr -d ' ' | base64 --decode > ~/.ssh/id_do.pem +script: +- bundle exec rake travis --trace +after_script: +- bundle exec kitchen destroy +- cat .kitchen/logs/* +env: + global: + - secure: h2vmDV0AjfSWpUCerHIe9uAR9Od0QDoSRPidEiCjrhNCvaEIz+xFQl3M8eYmHBC31GABdEsiDLHQmj6bPtGOuvceKp37qc9V/h2/oPpPvW2HDjMT6mO6Qx1a5Pv4Xb0PhlUfseZCLDURi/0bM5czxGLH+oqluVEzgrM48m/YWaI= + - secure: fXvnhXK/ckP6EyFvGdYnh0YFwc1q+kF5HYUn3plOn7gytiERo+QwXqsnGtueiqrUzlCnqTWAj1T8wIuiPPmAUr3Ek/LUq1UwVcLYC9Wa2uGeTSooY6k1tzG1mtm+4njpIXxvOZ37NG2TwHLSG15iuJff6dtBE667/r88FjAGxgA= + - secure: NzFG53vCyET7REDbiDBA6AlKwgQtAUnb/2IyCyRwi/Svpf5UWdnqiHD9vepsaLQ+tnJPnCBelP5vM+H7Ln/uWLN39WPz4+36Dry6cWRgTIRG94jCKg3KQJvs6Z+V4bHwRdtvMO5HeAvJUCKRKsIW15odnnPPgPf3OrCHOfQK3Ko= + - secure: 3n0wmPKd+SBBP7prduer7jtIBLAvYP3T0/M+PupH6A8cKNL17kafQO9BwDSfcrRilX0UfOEND2Yq3Au6OfBjmKaFyScUdI5DOT+GWiBcYl9fbmtpz9KG6H8iWG8tIyJQ7vfV6pev8BGDQsmsIBu4SPYTUKUegtvkmmVoeV2je+4= + - secure: yrAlzIzT5sMpJ6VbjSjGLPlMH8LdPNBymaf/5FRxt1n5qLR2GQt9wxaCzGyjhgHo6iAdf3Dw9//YJ8vctnF61XqDOkpc6sW1d8IVZXHPOwb0mr94yQgYWXS/FLk7CFGuELYvKYMLIAXkq/QMOMyro2YLhTD25NblcxTpk5PuJXc= + - secure: 1FMkzei96uga+Fhqb3BK7dIiFj+ItiQhh1vHlotoLecDlDDT0o1YV8jBueLyZiVuFo/n4rKD8zMeCh3g5gfvWGQgZXbxEwMOLixtrW8bnOt/qAGs3qI6H57zXzWU2voUeyWa+ExorBMf1WL1RfIE6S/MlZNJW2OmWKceEaYrsLI= + - secure: ulI/7FdP1JVs61bi7CX3UwmG2v7SzHKfjf3P/wWvbCAO8Z/By/gnHmUn6I0EKsUEA4Gx3kXH8DmVtOZdcYLiNTnWGS38AxPnOfLYa0Lv/h7qqze4MFo5FliNB0iKaq0qn+L/eGYQSlr9e5Opf1Qdp2E47UUFd9VMaCSRKvIpqG4= + - secure: bcfXOTCxjA5Gv2BZWkjO8ukm5Z+t8AZglfvw7VRSqAe4BkyO62WmjONi2qYduB8nAX31IzKMEMOsA8zy9V96B+iAhdc3K5LTaa9VIocaNKoq0lgbLrqw3gW969p1vEbBzSiIn+3bDs7arX1LQ98e9UVt2hBQodSYicRzUuscbSw= + - secure: 2AeqJEMU9QYs40mfX6JppzAMsFJwrtFzYJVwgiN9EGCSqINzEIvnNB2z//nHsMlL6puP0VvapkCYHLDVHi2WHL0fSkkwyyGAfQVR01iM3CSjCl4j9h9Nv6bG78zNItQX64vg9FarTptqrZO/OnaT7dXUfGcAbr8cx9zJRv2fyMw= + - secure: Vx4VZUEF5ptw0lwHtLyMKcBRZwcpApsfAMgj/amqzAhFswjJoafHJ4Ryee+mrg46yXkaXed18xRu7pU/tXLGdp6vuvWFaC/1zCNfsdQBv+BAr46Ig4OB5mE/rwGOiNwbhSj9iHpKGPtUTwOHHqCqP/7ktR1TDIAmB5Esp0QBNFI= + - secure: 0ygYNLFO7ZBI7SH1PBt2ALmwtJfZ9ltCxOARP8ILkgCwLhczolcoT81/kfKigkP75dwYXU9LHiROm2GxFFH9reQdb5X2G3ik6/Xxn6KC1ewIuf8M7+qZx8p//ByazW7OZcvFrfGhsX/LJ5NfAC56Wii88oCUTYEGdM+MIPk8rzQ= + - secure: a0vsypNUkFkdnB2JiI/ZYd+hBfGC2pJt6JovUJr2sglZ0XvU7gyNT3iUmL3I22pM1gh+iAFPtS++OY0OSKRWnEbe7nMDY41soQW9UnfroexBVd+c1sYbJwbLJyTS2I3HxjIikWC2fGhySCX7ryghTQwJddGSh+q9eM0LgbvJ9Tc= + - secure: NUocMJTpGO7PWIMih4kjHGTRvb2vc6ep+fclviipkPzlQ5Ciabco1wW0HQJTX16JINgGVnzwFY16HFylyM74bcZoiSfGsN6E5GAmg5ZRxtpVs2wLHmsrMJxiT3VVMPHkBnZJXBNIcuMw6PAtiAcrOCyNY3Zuig1IuOERt12U2BY= + - secure: oLMuVgRvxDjYCb/hnA3YMJPDAAxyG2a7aUoGQHijSSBxL8VSW7BjfplUViCpWCsQADZgxLGHgfNUETAzHwheDm1TJT1KHVrYUXPDnLXgO89DvzrkLXlrr6JbaDMGUjG7fEEBNDnz5qycLiaoItX7x4/GPhSPOZ45q/64rW3Jvl4= + - secure: OnTKGDs568hSzE5sT9gQhY+nB1xHpnEMoT24UQybPn7Za79tJCkl3WlnqF2sd3+ImsT62xf6PNqRUue8TLVQLCVXCeStrIFPkdp0sps9xtFdNbi6Vb3yrq8QjU4RAQEz5+g8KcmycYMvF3M09lt7jAv8woebXkXdnzHz3IWhwTs= + - secure: 341IG8qb2JKqGDXGsx2a8xEVlkjILA6bSkWqZb9uhoEyW4je7PsqZdCfmKoAcau4I6+sBANu++qARJ58ZpWu+DJzuaKXkhSkdzo/MSykPK04I62v2qhRXUrhkpkXYAB4xK4wKFaCQWVHiCeV5jhEAayZxMB1gLwtxnZRRYXEhY0= + - secure: tvd71+q0xvglcUj6ugSw7oPlruTuNH/XGVh/k6B991zM27NQInmNeMWQa/WE7f8s2xSfG8f9cOtf11uDbPSHgyZj3T6CphfIl5sbT04zFO/1MfI5rbMRBXHvFpUWCb4gS+XUJ146DccuZInF9NI1e3aXNK6u3uPgxmn6DomVRlY= + - secure: BrMErE+J4TneCAGtDe5s8LQjhOJ6fTJSlA/dtmVx+LhzhyUA303wHCziPxrU2PJDL5fGe3r5zX83uoIXwKmU3kb2jRpy7SxF0kdsxqgdbzCnWINRDX5o0TH7AAViUA+nRccWF8wqNWsvkIhv6Pbr1u8B5xScPvBXhEuHJX2iVxo= + - secure: W3o/ae9BZDFNKe0UHGGDuYzriEvf/Eon+miqSwSZ/+rBuTeiX++3dyAMG/4XHeuNDgZ6H7qGtxlkqODK9AHZps5tFZ/zmVzXfzqRItIrGmGLKD7UvbIoS/C5fovhxIwMyWnlXdWeNf4o0QWJed6I188IlDumCxrmnWIWlueap6I= + - secure: rSCNg1LnxNjk/ux80iLQrcHqagWf80PBQf0kM9Wj5dD1nLWvbRMSSeXhiOdNY0ZD9RMROdjupsbFShdF788wAi7ITfhrMf09ys0D3/8ZDmCd51WAUvuutxMEz/TJKTWKItr2gbuRoXvv/hQ9DEWXyHx1A9DaDjwYGBH9bnYmgfs= + - secure: bHD0y307k3vUyA5cYdNc62Tq78r4HX8F4RG8bkgDAP0Z0u8SCfYunk89kw2NCF+qlo+ux84lhh2n/HKAwIdkupQSJaPGO4i241i8pUd1RA0T+CfjvdmMk6KjgbItauAhctgy61BTRJzoLAZQ75JurHLAjc5JNfSxsa1xQGsWIVQ= + - secure: A7NVQrmbAZhwIz+lkDcwX1zw+GJjLbwnW4/A0cCGcZObQxTqM7W6+B6UG9efm2vmxER9xtjstiH9wsLtJYerKxv05jwXoKlq/J+BVu2dTI9S6SqLas6Lo09XFfqtmYKgbV6R2CKDt8hT2a5A/Wp1hK4URjifu2gel/3MO6eeiJs= + - secure: BQCOwcb4u4spzd20vaUSkJycJ0oaojdyucmUV9pRYADH+jDEcCiL52L+bMxGZ+5vYPITG9wG2Kjv8VroyIuYfADMjZJjzMOMiwpjTWxoH7gA/12D8p7FcP9npllJgNg0TMvZUULVx2w2JQEGyq3Kfp2oKHfbgkBhtiSDH8mjSqc= + - secure: ODDYK3EogzOZ4rd/IW3HRAn+Ynpi1ob/lG7udBiiFhOZB8IWzZkNniRBZv60pOVq62YF0EidkNR4MK3Ln+wh3KLkqBWuR86ORgFmGazGxYlUbAfBfwt75FdK2+WAwyLGR3H7eqgTN+Y4U+GyPMUFfMBXbE73sX8Si2ldLy7n5ZE= + - secure: Mk6OHiJ5i4T+/3X5mLOhRuqif7M2cyTPbjNxNhW0oDQG4KB8M+18hDklwnQPpiXOL4LmuuSGDWgOZYnlZHFdLTzj5/nmbfh2qbr30Aqj8OgRnO/jjjU/BrcgBM2zrlH/TOKl5HqHp7bLesHkfTNzNy5IeIuRwZN/8qKNV1HZdtk= + - secure: GyPuciPuxMTNxr1igDPQAAvZdTE4bGIzVM4YpURvZngvhxQgWtvF09nV1FfNQAz643aq1bjbZ1ThfuOagWwTRUVqTgstxwCau/EGOAnoMXt1wDfvBuxpxLK2WDnO8PHYTDtpcnes5D6+45K5Z4bFAs0gIw/XoF0tZiCKVEo+OR0= + - secure: ix+m/F8qUKdjGpBLUW+okt00kmxFOAi7FKi0ndnjQPnHdygMec00tCxcvW4P16QsjpQq7w098Fsjc2V28hMo4RpH0JFPxnnfFttDZfk15UydrYD65EXhpyvh/xmQYd1cCK+YhymhPc0bOz0d7Ava7H7AGfBUkC0DzMdizpbB/pQ= + - secure: ZjxBwneeNa1whozgua2Jx3K9EA0EfaFCjsyB5SGmS8cALzLY4EJawH8iiSGapJrCxz58jK1z3ISdu9a7l5ne85fYI+WuHyTC7QVbW5OpRrOJMwTXf2/hRTVuavp9fA5W7B5nhoqgHMR56YXSaEO6juXiSztsYF7kJiGdCO0f6fQ= + - secure: zavu1UqfqRVh5hFaGdopn32B1ysW1sK769L+cSQnEQprDXB11uBcTJgBX104sw1zUnB0/QTfuZ3eKkhSpDpFg66I7IpqW/Aw7iWVa2EI/eGnQ5vOJwxWA/Bd08H5tpeXSCnjSOQp/Ac/0vhZy2DmhToKDPJakEtRP+/eaqbFNgc= + - secure: omEb6OGAUVSwHvFqUqqw3z16wDv0YrJzQZgHLZuKD8CvC3HvPDQaykqzvFtqrEWAUl5rZf1bSZ/jylximogKzx2+ENn5TjveJQTzQQwVw9FO/Jn8XVM0x7A3K86JpI0azG4LtFAaqpd4mWIAH5ZFeNYB2x6D2jrjXOajLoJ6zmM= diff --git a/cookbooks/apt/Berksfile b/cookbooks/apt/Berksfile new file mode 100644 index 0000000..4f6ada4 --- /dev/null +++ b/cookbooks/apt/Berksfile @@ -0,0 +1,8 @@ +source 'https://supermarket.chef.io' + +metadata + +group :integration do + cookbook 'minitest-handler' + cookbook 'apt_test', :path => './test/cookbooks/apt_test' +end diff --git a/cookbooks/apt/CHANGELOG.md b/cookbooks/apt/CHANGELOG.md new file mode 100644 index 0000000..ac15bb4 --- /dev/null +++ b/cookbooks/apt/CHANGELOG.md @@ -0,0 +1,217 @@ +apt Cookbook CHANGELOG +====================== + +v2.7.0 (2015-03-23) +------------------- +- Support Debian 8.0 +- Filename verification for LWRPs +- Support SSL enabled apt repositories + +v2.6.1 (2014-12-29) +------------------- +- Remove old preference files without .pref extension from previous versions + +v2.6.0 (2014-09-09) +------------------- +- Always update on first run - check +- Adding ppa support for apt_repository + +v2.5.3 (2014-08-14) +------------------- +- #87 - Improve default settings, account for non-linux platforms + +v2.5.2 (2014-08-14) +------------------- +- Fully restore fully restore 2.3.10 behaviour + +v2.5.1 (2014-08-14) +------------------- +- fix breakage introduced in apt 2.5.0 + +v2.5.0 (2014-08-12) +------------------- +- Add unattended-upgrades recipe +- Only update the cache for the created repository +- Added ChefSpec matchers and default_action for resources +- Avoid cloning resource attributes +- Minor documentation updates + +v2.4.0 (2014-05-15) +------------------- +- [COOK-4534]: Add option to update apt cache at compile time + + +v2.3.10 (2014-04-23) +-------------------- +- [COOK-4512] Bugfix: Use empty PATH if PATH is nil + + +v2.3.8 (2014-02-14) +------------------- +### Bug +- **[COOK-4287](https://tickets.chef.io/browse/COOK-4287)** - Cleanup the Kitchen + + +v2.3.6 +------ +* [COOK-4154] - Add chefspec matchers.rb file to apt cookbook +* [COOK-4102] - Only index created repository + + +v2.3.6 +------ +* [COOK-4154] - Add chefspec matchers.rb file to apt cookbook +* [COOK-4102] - Only index created repository + + +v2.3.4 +------ +No change. Version bump for toolchain sanity + + +v2.3.2 +------ +- [COOK-3905] apt-get-update-periodic: configuration for the update period +- Updating style for rubocops +- Updating test-kitchen harness + + +v2.3.0 +------ +### Bug +- **[COOK-3812](https://tickets.chef.io/browse/COOK-3812)** - Add a way to bypass the apt existence check + +### Improvement +- **[COOK-3567](https://tickets.chef.io/browse/COOK-3567)** - Allow users to bypass apt-cache via attributes + + +v2.2.1 +------ +### Improvement +- **[COOK-664](https://tickets.chef.io/browse/COOK-664)** - Check platform before running apt-specific commands + + +v2.2.0 +------ +### Bug +- **[COOK-3707](https://tickets.chef.io/browse/COOK-3707)** - multiple nics confuse apt::cacher-client + +v2.1.2 +------ +### Improvement +- **[COOK-3551](https://tickets.chef.io/browse/COOK-3551)** - Allow user to set up a trusted APT repository + +v2.1.1 +------ +### Bug +- **[COOK-1856](https://tickets.chef.io/browse/COOK-1856)** - Match GPG keys without case sensitivity + +v2.1.0 +------ +- [COOK-3426]: cacher-ng fails with restrict_environment set to true +- [COOK-2859]: cacher-client executes out of order +- [COOK-3052]: Long GPG keys are downloaded on every run +- [COOK-1856]: apt cookbook should match keys without case sensitivity +- [COOK-3255]: Attribute name incorrect in README +- [COOK-3225]: Call use_inline_resources only if defined +- [COOK-3386]: Cache dir for apt-cacher-ng +- [COOK-3291]: apt_repository: enable usage of a keyserver on port 80 +- Greatly expanded test coverage with ChefSpec and Test-Kitchen + +v2.0.0 +------ +### Bug + +- [COOK-2258]: apt: LWRP results in error under why-run mode in apt 1.9.0 cookbook + +v1.10.0 +------- +### Improvement + +- [COOK-2885]: Improvements for apt cache server search + +### Bug + +- [COOK-2441]: Apt recipe broken in new chef version +- [COOK-2660]: Create Debian 6.0 "squeeze" specific template for + apt-cacher-ng + +v1.9.2 +------ +- [COOK-2631] - Create Ubuntu 10.04 specific template for apt-cacher-ng + +v1.9.0 +------ +- [COOK-2185] - Proxy for apt-key +- [COOK-2338] - Support pinning by glob() or regexp + +v1.8.4 +------ +- [COOK-2171] - Update README to clarify required Chef version: 10.18.0 + or higher. + +v1.8.2 +------ +- [COOK-2112] - need [] around "arch" in sources.list entries +- [COOK-2171] - fixes a regression in the notification + +v1.8.0 +------ +- [COOK-2143] - Allow for a custom cacher-ng port +- [COOK-2171] - On `apt_repository.run_action(:add)` the source file + is not created. +- [COOK-2184] - apt::cacher-ng, use `cacher_port` attribute in + acng.conf + +v1.7.0 +------ +- [COOK-2082] - add "arch" parameter to apt_repository LWRP + +v1.6.0 +------ +- [COOK-1893] - `apt_preference` use "`package_name`" resource instead of "name" +- [COOK-1894] - change filename for sources.list.d files +- [COOK-1914] - Wrong dir permissions for /etc/apt/preferences.d/ +- [COOK-1942] - README.md has wrong name for the keyserver attribute +- [COOK-2019] - create 01proxy before any other apt-get updates get executed + +v1.5.2 +------ +- [COOK-1682] - use template instead of file resource in apt::cacher-client +- [COOK-1875] - cacher-client should be Environment-aware + +V1.5.0 +------ +- [COOK-1500] - Avoid triggering apt-get update +- [COOK-1548] - Add execute commands for autoclean and autoremove +- [COOK-1591] - Setting up the apt proxy should leave https + connections direct +- [COOK-1596] - execute[apt-get-update-periodic] never runs +- [COOK-1762] - create /etc/apt/preferences.d directory +- [COOK-1776] - apt key check isn't idempotent + +v1.4.8 +------ +* Adds test-kitchen support +- [COOK-1435] - repository lwrp is not idempotent with http key + +v1.4.6 +------ +- [COOK-1530] - apt_repository isn't aware of update-success-stamp + file (also reverts COOK-1382 patch). + +v1.4.4 +------ +- [COOK-1229] - Allow cacher IP to be set manually in non-Chef Solo + environments +- [COOK-1530] - Immediately update apt-cache when sources.list file is dropped off + +v1.4.2 +------ +- [COOK-1155] - LWRP for apt pinning + +v1.4.0 +------ +- [COOK-889] - overwrite existing repo source files +- [COOK-921] - optionally use cookbook\_file or remote\_file for key +- [COOK-1032] - fixes problem with apt repository key installation diff --git a/cookbooks/apt/CONTRIBUTING b/cookbooks/apt/CONTRIBUTING new file mode 100644 index 0000000..e781c97 --- /dev/null +++ b/cookbooks/apt/CONTRIBUTING @@ -0,0 +1,29 @@ +If you would like to contribute, please open a ticket in JIRA: + +* http://tickets.chef.io + +Create the ticket in the COOK project and use the cookbook name as the +component. + +For all code contributions, we ask that contributors sign a +contributor license agreement (CLA). Instructions may be found here: + +* http://wiki.chef.io/display/chef/How+to+Contribute + +When contributing changes to individual cookbooks, please do not +modify the version number in the metadata.rb. Also please do not +update the CHANGELOG.md for a new version. Not all changes to a +cookbook may be merged and released in the same versions. Chef Software will +handle the version updates during the release process. You are welcome +to correct typos or otherwise make updates to documentation in the +README. + +If a contribution adds new platforms or platform versions, indicate +such in the body of the commit message(s), and update the relevant +COOK ticket. When writing commit messages, it is helpful for others if +you indicate the COOK ticket. For example: + + git commit -m '[COOK-1041] Updated pool resource to correctly delete.' + +In the ticket itself, it is also helpful if you include log output of +a successful Chef run, but this is not absolutely required. diff --git a/cookbooks/apt/Gemfile b/cookbooks/apt/Gemfile new file mode 100644 index 0000000..e1b8fa0 --- /dev/null +++ b/cookbooks/apt/Gemfile @@ -0,0 +1,37 @@ +source 'https://rubygems.org' + +group :lint do + gem 'foodcritic', '~> 3.0' + gem 'rubocop', '~> 0.23' + gem 'rainbow', '< 2.0' +end + +group :unit do + gem 'berkshelf', '~> 3.0.0.beta6' + gem 'chefspec', '~> 4.0' +end + +group :kitchen_common do + gem 'test-kitchen', '~> 1.2' +end + +group :kitchen_vagrant do + gem 'kitchen-vagrant', '~> 0.11' +end + +group :kitchen_cloud do + gem 'kitchen-digitalocean' + gem 'kitchen-ec2' +end + +group :development do + gem 'ruby_gntp' + gem 'growl' + gem 'rb-fsevent' + gem 'guard', '~> 2.4' + gem 'guard-kitchen' + gem 'guard-foodcritic' + gem 'guard-rspec' + gem 'guard-rubocop' + gem 'rake' +end diff --git a/cookbooks/apt/Guardfile b/cookbooks/apt/Guardfile new file mode 100644 index 0000000..11dc1de --- /dev/null +++ b/cookbooks/apt/Guardfile @@ -0,0 +1,35 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +# guard 'kitchen' do +# watch(%r{test/.+}) +# watch(%r{^recipes/(.+)\.rb$}) +# watch(%r{^attributes/(.+)\.rb$}) +# watch(%r{^files/(.+)}) +# watch(%r{^templates/(.+)}) +# watch(%r{^providers/(.+)\.rb}) +# watch(%r{^resources/(.+)\.rb}) +# end + +guard 'foodcritic', cookbook_paths: '.', all_on_start: false do + watch(%r{attributes/.+\.rb$}) + watch(%r{providers/.+\.rb$}) + watch(%r{recipes/.+\.rb$}) + watch(%r{resources/.+\.rb$}) + watch('metadata.rb') +end + +guard 'rubocop', all_on_start: false do + watch(%r{attributes/.+\.rb$}) + watch(%r{providers/.+\.rb$}) + watch(%r{recipes/.+\.rb$}) + watch(%r{resources/.+\.rb$}) + watch('metadata.rb') +end + +guard :rspec, cmd: 'bundle exec rspec', all_on_start: false, notification: false do + watch(%r{^libraries/(.+)\.rb$}) + watch(%r{^spec/(.+)_spec\.rb$}) + watch(%r{^(recipes)/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { 'spec' } +end diff --git a/cookbooks/apt/LICENSE b/cookbooks/apt/LICENSE new file mode 100644 index 0000000..11069ed --- /dev/null +++ b/cookbooks/apt/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/cookbooks/apt/README.md b/cookbooks/apt/README.md new file mode 100644 index 0000000..dc2e798 --- /dev/null +++ b/cookbooks/apt/README.md @@ -0,0 +1,281 @@ +apt Cookbook +============ +[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/chef-cookbooks/apt?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Cookbook Version](https://img.shields.io/cookbook/v/apt.svg)][cookbook] +[![Build Status](https://img.shields.io/travis/opscode-cookbooks/apt.svg)][travis] + +[cookbook]: https://community.chef.io/cookbooks/apt +[travis]: https://travis-ci.org/opscode-cookbooks/apt + +This cookbook includes recipes to execute apt-get update to ensure the local APT package cache is up to date. There are recipes for managing the apt-cacher-ng caching proxy and proxy clients. It also includes a LWRP for managing APT repositories in /etc/apt/sources.list.d as well as an LWRP for pinning packages via /etc/apt/preferences.d. + + +Requirements +------------ +**Version 2.0.0+ of this cookbook requires Chef 11.0.0 or later**. If your Chef version is earlier than 11.0.0, use version 1.10.0 of this cookbook. + +Version 1.8.2 to 1.10.0 of this cookbook requires **Chef 10.16.4** or later. + +If your Chef version is earlier than 10.16.4, use version 1.7.0 of this cookbook. + +### Platform +Please refer to the [TESTING file](TESTING.md) to see the currently (and passing) tested platforms. The release was tested on: + +* Ubuntu 10.04 +* Ubuntu 12.04 +* Ubuntu 13.04 +* Debian 7.1 +* Debian 6.0 (have with manual testing) + +May work with or without modification on other Debian derivatives. + + +------- +### default +This recipe manually updates the timestamp file used to only run `apt-get update` if the cache is more than one day old. + +This recipe should appear first in the run list of Debian or Ubuntu nodes to ensure that the package cache is up to date before managing any `package` resources with Chef. + +This recipe also sets up a local cache directory for preseeding packages. + +**Including the default recipe on a node that does not support apt (such as Windows) results in a noop.** + +### cacher-client +Configures the node to use the `apt-cacher-ng` server as a client. + +#### Bypassing the cache +Occasionally you may come across repositories that do not play nicely when the node is using an `apt-cacher-ng` server. You can configure `cacher-client` to bypass the server and connect directly to the repository with the `cache_bypass` attribute. + +To do this, you need to override the `cache_bypass` attribute with an array of repositories, with each array key as the repository URL and value as the protocol to use: + +```json +{ + ..., + 'apt': { + ..., + 'cache_bypass': { + URL: PROTOCOL + } + } +} +``` + +For example, to prevent caching and directly connect to the repository at `download.oracle.com` via http: + +```json +{ + 'apt': { + 'cache_bypass': { + 'download.oracle.com': 'http' + } + } +} +``` + +### cacher-ng +Installs the `apt-cacher-ng` package and service so the system can provide APT caching. You can check the usage report at http://{hostname}:3142/acng-report.html. + +If you wish to help the `cacher-ng` recipe seed itself, you must now explicitly include the `cacher-client` recipe in your run list **after** `cacher-ng` or you will block your ability to install any packages (ie. `apt-cacher-ng`). + +### unattended-upgrades + +Installs and configures the `unattended-upgrades` package to provide automatic package updates. This can be configured to upgrade all packages or to just install security updates by setting `['apt']['unattended_upgrades']['allowed_origins']`. + +To pull just security updates, you'd set `allowed_origins` to something link `["Ubuntu trusty-security"]` (for Ubuntu trusty) or `["Debian wheezy-security"]` (for Debian wheezy). + + +Attributes +---------- + +### General +* `['apt']['compile_time_update']` - force the default recipe to run `apt-get update` at compile time. +* `['apt']['periodic_update_min_delay']` - minimum delay (in seconds) beetween two actual executions of `apt-get update` by the `execute[apt-get-update-periodic]` resource, default is '86400' (24 hours) + +### Caching + +* `['apt']['cacher_ipaddress']` - use a cacher server (or standard proxy server) not available via search +* `['apt']['cacher_interface]` - interface to connect to the cacher-ng service, no default. +* `['apt']['cacher_port']` - port for the cacher-ng service (either client or server), default is '3142' +* `['apt']['cacher_ssl_support']` - indicates whether the cacher supports upstream SSL servers, default is 'false' +* `['apt']['cacher_dir']` - directory used by cacher-ng service, default is '/var/cache/apt-cacher-ng' +* `['apt']['cacher-client']['restrict_environment']` - restrict your node to using the `apt-cacher-ng` server in your Environment, default is 'false' +* `['apt']['compiletime']` - force the `cacher-client` recipe to run before other recipes. It forces apt to use the proxy before other recipes run. Useful if your nodes have limited access to public apt repositories. This is overridden if the `cacher-ng` recipe is in your run list. Default is 'false' +* `['apt']['cache_bypass']` - array of URLs to bypass the cache. Accepts the URL and protocol to fetch directly from the remote repository and not attempt to cache + +### Unattended Upgrades + +* `['apt']['unattended_upgrades']['enable']` - enables unattended upgrades, default is false +* `['apt']['unattended_upgrades']['update_package_lists']` — automatically update package list (`apt-get update`) daily, default is true +* `['apt']['unattended_upgrades']['allowed_origins']` — array of allowed apt origins from which to pull automatic upgrades, defaults to a guess at the system's main origin and should almost always be overridden +* `['apt']['unattended_upgrades']['package_blacklist']` — an array of package which should never be automatically upgraded, defaults to none +* `['apt']['unattended_upgrades']['auto_fix_interrupted_dpkg']` — attempts to repair dpkg state with `dpkg --force-confold --configure -a` if it exits uncleanly, defaults to false (contrary to the unattended-upgrades default) +* `['apt']['unattended_upgrades']['minimal_steps']` — Split the upgrade into the smallest possible chunks. This makes the upgrade a bit slower but it has the benefit that shutdown while a upgrade is running is possible (with a small delay). Defaults to false. +* `['apt']['unattended_upgrades']['install_on_shutdown']` — Install upgrades when the machine is shuting down instead of doing it in the background while the machine is running. This will (obviously) make shutdown slower. Defaults to false. +* `['apt']['unattended_upgrades']['mail']` — Send email to this address for problems or packages upgrades. Defaults to no email. +* `['apt']['unattended_upgrades']['mail_only_on_error']` — If set, email will only be set on upgrade errors. Otherwise, an email will be sent after each upgrade. Defaults to true. +* `['apt']['unattended_upgrades']['remove_unused_dependencies']` Do automatic removal of new unused dependencies after the upgrade. Defaults to false. +* `['apt']['unattended_upgrades']['automatic_reboot']` — Automatically reboots *without confirmation* if a restart is required after the upgrade. Defaults to false. +* `['apt']['unattended_upgrades']['dl_limit']` — Limits the bandwidth used by apt to download packages. Value given as an integer in kb/sec. Defaults to nil (no limit). + +Libraries +--------- +There is an `interface_ipaddress` method that returns the IP address for a particular host and interface, used by the `cacher-client` recipe. To enable it on the server use the `['apt']['cacher_interface']` attribute. + +Resources/Providers +------------------- +### `apt_repository` +This LWRP provides an easy way to manage additional APT repositories. Adding a new repository will notify running the `execute[apt-get-update]` resource immediately. + +#### Actions +- :add: creates a repository file and builds the repository listing (default) +- :remove: removes the repository file + +#### Attribute Parameters +- repo_name: name attribute. The name of the channel to discover +- uri: the base of the Debian distribution +- distribution: this is usually your release's codename...ie something like `karmic`, `lucid` or `maverick` +- components: package groupings... when in doubt use `main` +- arch: constrain package to a particular arch like `i386`, `amd64` or even `armhf` or `powerpc`. Defaults to nil. +- trusted: treat all packages from this repository as authenticated regardless of signature +- deb_src: whether or not to add the repository as a source repo as well - value can be `true` or `false`, default `false`. +- keyserver: the GPG keyserver where the key for the repo should be retrieved +- key: if a `keyserver` is provided, this is assumed to be the fingerprint, otherwise it can be either the URI to the GPG key for the repo, or a cookbook_file. +- key_proxy: if set, pass the specified proxy via `http-proxy=` to GPG. +- cookbook: if key should be a cookbook_file, specify a cookbook where the key is located for files/default. Defaults to nil, so it will use the cookbook where the resource is used. + +#### Examples + +Add the Zenoss repo: + +```ruby +apt_repository 'zenoss' do + uri 'http://dev.zenoss.org/deb' + components ['main', 'stable'] +end +``` + +Add the Nginx PPA, autodetect the key and repository url: + +```ruby +apt_repository 'nginx-php' do + uri 'ppa:nginx/stable' + distribution node['lsb']['codename'] +end +``` + +Add the JuJu PPA, grab the key from the keyserver, and add source repo: + +```ruby +apt_repository 'juju' do + uri 'http://ppa.launchpad.net/juju/stable/ubuntu' + components ['main'] + distribution 'trusty' + key 'C8068B11' + keyserver 'keyserver.ubuntu.com' + action :add + deb_src true +end +``` + +Add the Cloudera Repo of CDH4 packages for Ubuntu 12.04 on AMD64: + +```ruby +apt_repository 'cloudera' do + uri 'http://archive.cloudera.com/cdh4/ubuntu/precise/amd64/cdh' + arch 'amd64' + distribution 'precise-cdh4' + components ['contrib'] + key 'http://archive.cloudera.com/debian/archive.key' +end +``` + +Remove Zenoss repo: + +```ruby +apt_repository 'zenoss' do + action :remove +end +``` + +### `apt_preference` +This LWRP provides an easy way to pin packages in /etc/apt/preferences.d. Although apt-pinning is quite helpful from time to time please note that Debian does not encourage its use without thorough consideration. + +Further information regarding apt-pinning is available via http://wiki.debian.org/AptPreferences. + +#### Actions +- :add: creates a preferences file under /etc/apt/preferences.d +- :remove: Removes the file, therefore unpin the package + +#### Attribute Parameters +- package_name: name attribute. The name of the package +- glob: Pin by glob() expression or regexp surrounded by /. +- pin: The package version/repository to pin +- pin_priority: The pinning priority aka "the highest package version wins" + +#### Examples +Pin libmysqlclient16 to version 5.1.49-3: + +```ruby +apt_preference 'libmysqlclient16' do + pin 'version 5.1.49-3' + pin_priority '700' +end +``` + +Unpin libmysqlclient16: + +```ruby +apt_preference 'libmysqlclient16' do + action :remove +end +``` + +Pin all packages from dotdeb.org: + +```ruby +apt_preference 'dotdeb' do + glob '*' + pin 'origin packages.dotdeb.org' + pin_priority '700' +end +``` + + +Usage +----- +Put `recipe[apt]` first in the run list. If you have other recipes that you want to use to configure how apt behaves, like new sources, notify the execute resource to run, e.g.: + +```ruby +template '/etc/apt/sources.list.d/my_apt_sources.list' do + notifies :run, 'execute[apt-get update]', :immediately +end +``` + +The above will run during execution phase since it is a normal template resource, and should appear before other package resources that need the sources in the template. + +Put `recipe[apt::cacher-ng]` in the run_list for a server to provide APT caching and add `recipe[apt::cacher-client]` on the rest of the Debian-based nodes to take advantage of the caching server. + +If you want to cleanup unused packages, there is also the `apt-get autoclean` and `apt-get autoremove` resources provided for automated cleanup. + + +License & Authors +----------------- +- Author:: Joshua Timberman (joshua@chef.io) +- Author:: Matt Ray (matt@chef.io) +- Author:: Seth Chisamore (schisamo@chef.io) + +```text +Copyright 2009-2013, Chef Software, Inc. + +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/cookbooks/apt/Rakefile b/cookbooks/apt/Rakefile new file mode 100644 index 0000000..965b4bf --- /dev/null +++ b/cookbooks/apt/Rakefile @@ -0,0 +1,59 @@ +require 'rspec/core/rake_task' +require 'rubocop/rake_task' +require 'foodcritic' +require 'kitchen' + +# Style tests. Rubocop and Foodcritic +namespace :style do + desc 'Run Ruby style checks' + RuboCop::RakeTask.new(:ruby) + + desc 'Run Chef style checks' + FoodCritic::Rake::LintTask.new(:chef) do |t| + t.options = { + fail_tags: ['any'], + tags: ['~FC005'] + } + end +end + +desc 'Run all style checks' +task style: ['style:chef', 'style:ruby'] + +# Rspec and ChefSpec +desc 'Run ChefSpec examples' +RSpec::Core::RakeTask.new(:spec) + +# Integration tests. Kitchen.ci +namespace :integration do + desc 'Run Test Kitchen with Vagrant' + task :vagrant do + Kitchen.logger = Kitchen.default_file_logger + Kitchen::Config.new.instances.each do |instance| + instance.test(:always) + end + end + + desc 'Run Test Kitchen with cloud plugins' + task :cloud do + run_kitchen = true + if ENV['TRAVIS'] == 'true' && ENV['TRAVIS_PULL_REQUEST'] != 'false' + run_kitchen = false + end + + if run_kitchen + Kitchen.logger = Kitchen.default_file_logger + @loader = Kitchen::Loader::YAML.new(project_config: './.kitchen.cloud.yml') + config = Kitchen::Config.new(loader: @loader) + config.instances.each do |instance| + instance.test(:always) + end + end + end +end + +desc 'Run all tests on Travis' +task travis: ['style', 'spec', 'integration:cloud'] + +# Default +task default: ['style', 'spec', 'integration:vagrant'] diff --git a/cookbooks/apt/TESTING.md b/cookbooks/apt/TESTING.md new file mode 100644 index 0000000..8036473 --- /dev/null +++ b/cookbooks/apt/TESTING.md @@ -0,0 +1,187 @@ +TESTING doc +======================== + +Bundler +------- +A ruby environment with Bundler installed is a prerequisite for using +the testing harness shipped with this cookbook. At the time of this +writing, it works with Ruby 2.0 and Bundler 1.5.3. All programs +involved, with the exception of Vagrant, can be installed by cd'ing +into the parent directory of this cookbook and running "bundle install" + +Rakefile +-------- +The Rakefile ships with a number of tasks, each of which can be ran +individually, or in groups. Typing "rake" by itself will perform style +checks with Rubocop and Foodcritic, ChefSpec with rspec, and +integration with Test Kitchen using the Vagrant driver by +default.Alternatively, integration tests can be ran with Test Kitchen +cloud drivers. + +``` +$ rake -T +rake integration:cloud # Run Test Kitchen with cloud plugins +rake integration:vagrant # Run Test Kitchen with Vagrant +rake spec # Run ChefSpec examples +rake style # Run all style checks +rake style:chef # Lint Chef cookbooks +rake style:ruby # Run Ruby style checks +rake travis # Run all tests on Travis +``` + +Style Testing +------------- +Ruby style tests can be performed by Rubocop by issuing either +``` +bundle exec rubocop +``` +or +``` +rake style:ruby +``` + +Chef style tests can be performed with Foodcritic by issuing either +``` +bundle exec foodcritic +``` +or +``` +rake style:chef +``` + +Spec Testing +------------- +Unit testing is done by running Rspec examples. Rspec will test any +libraries, then test recipes using ChefSpec. This works by compiling a +recipe (but not converging it), and allowing the user to make +assertions about the resource_collection. + +Integration Testing +------------------- +Integration testing is performed by Test Kitchen. Test Kitchen will +use either the Vagrant driver or various cloud drivers to instantiate +machines and apply cookbooks. After a successful converge, tests are +uploaded and ran out of band of Chef. Tests should be designed to +ensure that a recipe has accomplished its goal. + +Integration Testing using Vagrant +--------------------------------- +Integration tests can be performed on a local workstation using +Virtualbox or VMWare. Detailed instructions for setting this up can be +found at the [Bento](https://github.com/chef/bento) project web site. + +Integration tests using Vagrant can be performed with either +``` +bundle exec kitchen test +``` +or +``` +rake integration:vagrant +``` + +Integration Testing using Cloud providers +----------------------------------------- +Integration tests can be performed on cloud providers using +Test Kitchen plugins. This cookbook ships a ```.kitchen.cloud.yml``` +that references environmental variables present in the shell that +```kitchen test``` is ran from. These usually contain authentication +tokens for driving IaaS APIs, as well as the paths to ssh private keys +needed for Test Kitchen log into them after they've been created. + +Examples of environment variables being set in ```~/.bash_profile```: +``` +# digital_ocean +export DIGITAL_OCEAN_CLIENT_ID='your_bits_here' +export DIGITAL_OCEAN_API_KEY='your_bits_here' +export DIGITAL_OCEAN_SSH_KEY_IDS='your_bits_here' + +# aws +export AWS_ACCESS_KEY_ID='your_bits_here' +export AWS_SECRET_ACCESS_KEY='your_bits_here' +export AWS_KEYPAIR_NAME='your_bits_here' + +# joyent +export SDC_CLI_ACCOUNT='your_bits_here' +export SDC_CLI_IDENTITY='your_bits_here' +export SDC_CLI_KEY_ID='your_bits_here' +``` + +Integration tests using cloud drivers can be performed with either +``` +export KITCHEN_YAML=.kitchen.cloud.yml +bundle exec kitchen test +``` +or +``` +rake integration:cloud +``` + +Digital Ocean Hint +------------------ +At the time of this writing, you cannot find the numerical values +needed for your SSH_KEY_IDS from the GUI. Instead, you will need to +access the API from the command line. + + curl -L 'https://api.digitalocean.com/ssh_keys/?client_id=your_bits_here&api_key=your_bits_here' + +Words about .travis.yml +----------------------- +In order for Travis to perform integration tests on public cloud +providers, two major things need to happen. First, the environment +variables referenced by ```.kitchen.cloud.yml``` need to be made +available. Second, the private half of the ssh keys needed to log into +machines need to be dropped off on the machine. + +The first part is straight forward. The travis gem can encrypt +environment variables against the public key on the Travis repository +and add them to the .travis.yml. + +``` +gem install travis +travis encrypt AWS_ACCESS_KEY_ID='your_bits_here' --add +travis encrypt AWS_SECRET_ACCESS_'your_bits_here' --add +travis encrypt AWS_KEYPAIR_NAME='your_bits_here' --add +travis encrypt EC2_SSH_KEY_PATH='~/.ssh/id_ec2.pem' --add + +travis encrypt DIGITAL_OCEAN_CLIENT_ID='your_bits_here' --add +travis encrypt DIGITAL_OCEAN_API_KEY='your_bits_here' --add +travis encrypt DIGITAL_OCEAN_SSH_KEY_IDS='your_bits_here' --add +travis encrypt DIGITAL_OCEAN_SSH_KEY_PATH='~/.ssh/id_do.pem' --add +``` + +The second part is a little more complicated. Travis ENV variables are +restricted to 90 bytes, and will not fit an entire SSH key. This can +be worked around by breaking them up into 90 byte chunks, stashing +them into ENV variables, then digging them out in the +```before_install``` section of .travis.yml + +Here is an AWK script to do the encoding. +``` +base64 ~/.ssh/travisci_cook_digitalocean.pem | \ +awk '{ + j=0; + for( i=1; i> ~/.ssh/id_do.base64 +- cat ~/.ssh/id_do.base64 | tr -d ' ' | base64 --decode > ~/.ssh/id_do.pem + - echo -n $EC2_KEY_CHUNK_{0..30} >> ~/.ssh/id_ec2.base64 + - cat ~/.ssh/id_ec2.base64 | tr -d ' ' | base64 --decode > ~/.ssh/id_ec2.pem +``` + diff --git a/cookbooks/apt/attributes/default.rb b/cookbooks/apt/attributes/default.rb new file mode 100644 index 0000000..02a4442 --- /dev/null +++ b/cookbooks/apt/attributes/default.rb @@ -0,0 +1,48 @@ +# +# Cookbook Name:: apt +# Attributes:: default +# +# Copyright 2009-2013, Chef Software, Inc. +# +# 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. +# + +default['apt']['cacher-client']['restrict_environment'] = false +default['apt']['cacher_dir'] = '/var/cache/apt-cacher-ng' +default['apt']['cacher_interface'] = nil +default['apt']['cacher_port'] = 3142 +default['apt']['cacher_ssl_support'] = false +default['apt']['caching_server'] = false +default['apt']['compiletime'] = false +default['apt']['compile_time_update'] = false +default['apt']['key_proxy'] = '' +default['apt']['cache_bypass'] = {} +default['apt']['periodic_update_min_delay'] = 86_400 +default['apt']['launchpad_api_version'] = '1.0' +default['apt']['unattended_upgrades']['enable'] = false +default['apt']['unattended_upgrades']['update_package_lists'] = true +# this needs a good default +codename = node.attribute?('lsb') ? node['lsb']['codename'] : 'notlinux' +default['apt']['unattended_upgrades']['allowed_origins'] = [ + "#{node['platform'].capitalize} #{codename}" +] +default['apt']['unattended_upgrades']['package_blacklist'] = [] +default['apt']['unattended_upgrades']['auto_fix_interrupted_dpkg'] = false +default['apt']['unattended_upgrades']['minimal_steps'] = false +default['apt']['unattended_upgrades']['install_on_shutdown'] = false +default['apt']['unattended_upgrades']['mail'] = nil +default['apt']['unattended_upgrades']['mail_only_on_error'] = true +default['apt']['unattended_upgrades']['remove_unused_dependencies'] = false +default['apt']['unattended_upgrades']['automatic_reboot'] = false +default['apt']['unattended_upgrades']['automatic_reboot_time'] = 'now' +default['apt']['unattended_upgrades']['dl_limit'] = nil diff --git a/cookbooks/apt/files/default/15update-stamp b/cookbooks/apt/files/default/15update-stamp new file mode 100644 index 0000000..14ead83 --- /dev/null +++ b/cookbooks/apt/files/default/15update-stamp @@ -0,0 +1 @@ +APT::Update::Post-Invoke-Success {"touch /var/lib/apt/periodic/update-success-stamp 2>/dev/null || true";}; diff --git a/cookbooks/apt/files/default/apt-proxy-v2.conf b/cookbooks/apt/files/default/apt-proxy-v2.conf new file mode 100644 index 0000000..6954004 --- /dev/null +++ b/cookbooks/apt/files/default/apt-proxy-v2.conf @@ -0,0 +1,50 @@ +[DEFAULT] +;; All times are in seconds, but you can add a suffix +;; for minutes(m), hours(h) or days(d) + +;; commented out address so apt-proxy will listen on all IPs +;; address = 127.0.0.1 +port = 9999 +cache_dir = /var/cache/apt-proxy + +;; Control files (Packages/Sources/Contents) refresh rate +min_refresh_delay = 1s +complete_clientless_downloads = 1 + +;; Debugging settings. +debug = all:4 db:0 + +time = 30 +passive_ftp = on + +;;-------------------------------------------------------------- +;; Cache housekeeping + +cleanup_freq = 1d +max_age = 120d +max_versions = 3 + +;;--------------------------------------------------------------- +;; Backend servers +;; +;; Place each server in its own [section] + +[ubuntu] +; Ubuntu archive +backends = + http://us.archive.ubuntu.com/ubuntu + +[ubuntu-security] +; Ubuntu security updates +backends = http://security.ubuntu.com/ubuntu + +[debian] +;; Backend servers, in order of preference +backends = + http://debian.osuosl.org/debian/ + +[security] +;; Debian security archive +backends = + http://security.debian.org/debian-security + http://ftp2.de.debian.org/debian-security diff --git a/cookbooks/apt/libraries/helpers.rb b/cookbooks/apt/libraries/helpers.rb new file mode 100644 index 0000000..2adf9d2 --- /dev/null +++ b/cookbooks/apt/libraries/helpers.rb @@ -0,0 +1,49 @@ +# +# Cookbook Name:: apt +# Library:: helpers +# +# Copyright 2013 Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +module Apt + # Helpers for apt + module Helpers + # Determines if apt is installed on a system. + # + # @return [Boolean] + def apt_installed? + !which('apt-get').nil? + end + + # Finds a command in $PATH + # + # @return [String, nil] + def which(cmd) + ENV['PATH'] = '' if ENV['PATH'].nil? + paths = (ENV['PATH'].split(::File::PATH_SEPARATOR) + %w(/bin /usr/bin /sbin /usr/sbin)) + + paths.each do |path| + possible = File.join(path, cmd) + return possible if File.executable?(possible) + end + + nil + end + end +end + +Chef::Recipe.send(:include, ::Apt::Helpers) +Chef::Resource.send(:include, ::Apt::Helpers) +Chef::Provider.send(:include, ::Apt::Helpers) diff --git a/cookbooks/apt/libraries/matchers.rb b/cookbooks/apt/libraries/matchers.rb new file mode 100644 index 0000000..aafce4d --- /dev/null +++ b/cookbooks/apt/libraries/matchers.rb @@ -0,0 +1,17 @@ +if defined?(ChefSpec) + def add_apt_preference(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:apt_preference, :add, resource_name) + end + + def remove_apt_preference(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:apt_preference, :remove, resource_name) + end + + def add_apt_repository(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:apt_repository, :add, resource_name) + end + + def remove_apt_repository(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:apt_repository, :remove, resource_name) + end +end diff --git a/cookbooks/apt/libraries/network.rb b/cookbooks/apt/libraries/network.rb new file mode 100644 index 0000000..828bf03 --- /dev/null +++ b/cookbooks/apt/libraries/network.rb @@ -0,0 +1,31 @@ +# +# Cookbook Name:: apt +# library:: network +# +# Copyright 2013, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +module ::Apt + def interface_ipaddress(host, interface) + if interface + addresses = host['network']['interfaces'][interface]['addresses'] + addresses.select do |ip, data| + return ip if data['family'].eql?('inet') + end + else + return host.ipaddress + end + end +end diff --git a/cookbooks/apt/metadata.json b/cookbooks/apt/metadata.json new file mode 100644 index 0000000..7885fd0 --- /dev/null +++ b/cookbooks/apt/metadata.json @@ -0,0 +1,121 @@ +{ + "name": "apt", + "description": "Configures apt and apt services and LWRPs for managing apt repositories and preferences", + "long_description": "apt Cookbook\n============\n[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/chef-cookbooks/apt?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n[![Cookbook Version](https://img.shields.io/cookbook/v/apt.svg)][cookbook]\n[![Build Status](https://img.shields.io/travis/opscode-cookbooks/apt.svg)][travis]\n\n[cookbook]: https://community.chef.io/cookbooks/apt\n[travis]: https://travis-ci.org/opscode-cookbooks/apt\n\nThis cookbook includes recipes to execute apt-get update to ensure the local APT package cache is up to date. There are recipes for managing the apt-cacher-ng caching proxy and proxy clients. It also includes a LWRP for managing APT repositories in /etc/apt/sources.list.d as well as an LWRP for pinning packages via /etc/apt/preferences.d.\n\n\nRequirements\n------------\n**Version 2.0.0+ of this cookbook requires Chef 11.0.0 or later**. If your Chef version is earlier than 11.0.0, use version 1.10.0 of this cookbook.\n\nVersion 1.8.2 to 1.10.0 of this cookbook requires **Chef 10.16.4** or later.\n\nIf your Chef version is earlier than 10.16.4, use version 1.7.0 of this cookbook.\n\n### Platform\nPlease refer to the [TESTING file](TESTING.md) to see the currently (and passing) tested platforms. The release was tested on:\n\n* Ubuntu 10.04\n* Ubuntu 12.04\n* Ubuntu 13.04\n* Debian 7.1\n* Debian 6.0 (have with manual testing)\n\nMay work with or without modification on other Debian derivatives.\n\n\n-------\n### default\nThis recipe manually updates the timestamp file used to only run `apt-get update` if the cache is more than one day old.\n\nThis recipe should appear first in the run list of Debian or Ubuntu nodes to ensure that the package cache is up to date before managing any `package` resources with Chef.\n\nThis recipe also sets up a local cache directory for preseeding packages.\n\n**Including the default recipe on a node that does not support apt (such as Windows) results in a noop.**\n\n### cacher-client\nConfigures the node to use the `apt-cacher-ng` server as a client.\n\n#### Bypassing the cache\nOccasionally you may come across repositories that do not play nicely when the node is using an `apt-cacher-ng` server. You can configure `cacher-client` to bypass the server and connect directly to the repository with the `cache_bypass` attribute.\n\nTo do this, you need to override the `cache_bypass` attribute with an array of repositories, with each array key as the repository URL and value as the protocol to use:\n\n```json\n{\n ...,\n 'apt': {\n ...,\n 'cache_bypass': {\n URL: PROTOCOL\n }\n }\n}\n```\n\nFor example, to prevent caching and directly connect to the repository at `download.oracle.com` via http:\n\n```json\n{\n 'apt': {\n 'cache_bypass': {\n 'download.oracle.com': 'http'\n }\n }\n}\n```\n\n### cacher-ng\nInstalls the `apt-cacher-ng` package and service so the system can provide APT caching. You can check the usage report at http://{hostname}:3142/acng-report.html.\n\nIf you wish to help the `cacher-ng` recipe seed itself, you must now explicitly include the `cacher-client` recipe in your run list **after** `cacher-ng` or you will block your ability to install any packages (ie. `apt-cacher-ng`).\n\n### unattended-upgrades\n\nInstalls and configures the `unattended-upgrades` package to provide automatic package updates. This can be configured to upgrade all packages or to just install security updates by setting `['apt']['unattended_upgrades']['allowed_origins']`.\n\nTo pull just security updates, you'd set `allowed_origins` to something link `[\"Ubuntu trusty-security\"]` (for Ubuntu trusty) or `[\"Debian wheezy-security\"]` (for Debian wheezy). \n\n\nAttributes\n----------\n\n### General \n* `['apt']['compile_time_update']` - force the default recipe to run `apt-get update` at compile time.\n* `['apt']['periodic_update_min_delay']` - minimum delay (in seconds) beetween two actual executions of `apt-get update` by the `execute[apt-get-update-periodic]` resource, default is '86400' (24 hours)\n\n### Caching\n\n* `['apt']['cacher_ipaddress']` - use a cacher server (or standard proxy server) not available via search\n* `['apt']['cacher_interface]` - interface to connect to the cacher-ng service, no default.\n* `['apt']['cacher_port']` - port for the cacher-ng service (either client or server), default is '3142'\n* `['apt']['cacher_ssl_support']` - indicates whether the cacher supports upstream SSL servers, default is 'false'\n* `['apt']['cacher_dir']` - directory used by cacher-ng service, default is '/var/cache/apt-cacher-ng'\n* `['apt']['cacher-client']['restrict_environment']` - restrict your node to using the `apt-cacher-ng` server in your Environment, default is 'false'\n* `['apt']['compiletime']` - force the `cacher-client` recipe to run before other recipes. It forces apt to use the proxy before other recipes run. Useful if your nodes have limited access to public apt repositories. This is overridden if the `cacher-ng` recipe is in your run list. Default is 'false'\n* `['apt']['cache_bypass']` - array of URLs to bypass the cache. Accepts the URL and protocol to fetch directly from the remote repository and not attempt to cache\n\n### Unattended Upgrades\n\n* `['apt']['unattended_upgrades']['enable']` - enables unattended upgrades, default is false\n* `['apt']['unattended_upgrades']['update_package_lists']` — automatically update package list (`apt-get update`) daily, default is true\n* `['apt']['unattended_upgrades']['allowed_origins']` — array of allowed apt origins from which to pull automatic upgrades, defaults to a guess at the system's main origin and should almost always be overridden\n* `['apt']['unattended_upgrades']['package_blacklist']` — an array of package which should never be automatically upgraded, defaults to none\n* `['apt']['unattended_upgrades']['auto_fix_interrupted_dpkg']` — attempts to repair dpkg state with `dpkg --force-confold --configure -a` if it exits uncleanly, defaults to false (contrary to the unattended-upgrades default)\n* `['apt']['unattended_upgrades']['minimal_steps']` — Split the upgrade into the smallest possible chunks. This makes the upgrade a bit slower but it has the benefit that shutdown while a upgrade is running is possible (with a small delay). Defaults to false.\n* `['apt']['unattended_upgrades']['install_on_shutdown']` — Install upgrades when the machine is shuting down instead of doing it in the background while the machine is running. This will (obviously) make shutdown slower. Defaults to false.\n* `['apt']['unattended_upgrades']['mail']` — Send email to this address for problems or packages upgrades. Defaults to no email.\n* `['apt']['unattended_upgrades']['mail_only_on_error']` — If set, email will only be set on upgrade errors. Otherwise, an email will be sent after each upgrade. Defaults to true.\n* `['apt']['unattended_upgrades']['remove_unused_dependencies']` Do automatic removal of new unused dependencies after the upgrade. Defaults to false.\n* `['apt']['unattended_upgrades']['automatic_reboot']` — Automatically reboots *without confirmation* if a restart is required after the upgrade. Defaults to false.\n* `['apt']['unattended_upgrades']['dl_limit']` — Limits the bandwidth used by apt to download packages. Value given as an integer in kb/sec. Defaults to nil (no limit).\n\nLibraries\n---------\nThere is an `interface_ipaddress` method that returns the IP address for a particular host and interface, used by the `cacher-client` recipe. To enable it on the server use the `['apt']['cacher_interface']` attribute.\n\nResources/Providers\n-------------------\n### `apt_repository`\nThis LWRP provides an easy way to manage additional APT repositories. Adding a new repository will notify running the `execute[apt-get-update]` resource immediately.\n\n#### Actions\n- :add: creates a repository file and builds the repository listing (default)\n- :remove: removes the repository file\n\n#### Attribute Parameters\n- repo_name: name attribute. The name of the channel to discover\n- uri: the base of the Debian distribution\n- distribution: this is usually your release's codename...ie something like `karmic`, `lucid` or `maverick`\n- components: package groupings... when in doubt use `main`\n- arch: constrain package to a particular arch like `i386`, `amd64` or even `armhf` or `powerpc`. Defaults to nil.\n- trusted: treat all packages from this repository as authenticated regardless of signature\n- deb_src: whether or not to add the repository as a source repo as well - value can be `true` or `false`, default `false`.\n- keyserver: the GPG keyserver where the key for the repo should be retrieved\n- key: if a `keyserver` is provided, this is assumed to be the fingerprint, otherwise it can be either the URI to the GPG key for the repo, or a cookbook_file.\n- key_proxy: if set, pass the specified proxy via `http-proxy=` to GPG.\n- cookbook: if key should be a cookbook_file, specify a cookbook where the key is located for files/default. Defaults to nil, so it will use the cookbook where the resource is used.\n\n#### Examples\n\nAdd the Zenoss repo:\n\n```ruby\napt_repository 'zenoss' do\n uri 'http://dev.zenoss.org/deb'\n components ['main', 'stable']\nend\n```\n\nAdd the Nginx PPA, autodetect the key and repository url:\n\n```ruby\napt_repository 'nginx-php' do\n uri 'ppa:nginx/stable'\n distribution node['lsb']['codename']\nend\n```\n\nAdd the JuJu PPA, grab the key from the keyserver, and add source repo:\n\n```ruby\napt_repository 'juju' do\n uri 'http://ppa.launchpad.net/juju/stable/ubuntu'\n components ['main']\n distribution 'trusty'\n key 'C8068B11'\n keyserver 'keyserver.ubuntu.com'\n action :add\n deb_src true\nend\n```\n\nAdd the Cloudera Repo of CDH4 packages for Ubuntu 12.04 on AMD64:\n\n```ruby\napt_repository 'cloudera' do\n uri 'http://archive.cloudera.com/cdh4/ubuntu/precise/amd64/cdh'\n arch 'amd64'\n distribution 'precise-cdh4'\n components ['contrib']\n key 'http://archive.cloudera.com/debian/archive.key'\nend\n```\n\nRemove Zenoss repo:\n\n```ruby\napt_repository 'zenoss' do\n action :remove\nend\n```\n\n### `apt_preference`\nThis LWRP provides an easy way to pin packages in /etc/apt/preferences.d. Although apt-pinning is quite helpful from time to time please note that Debian does not encourage its use without thorough consideration.\n\nFurther information regarding apt-pinning is available via http://wiki.debian.org/AptPreferences.\n\n#### Actions\n- :add: creates a preferences file under /etc/apt/preferences.d\n- :remove: Removes the file, therefore unpin the package\n\n#### Attribute Parameters\n- package_name: name attribute. The name of the package\n- glob: Pin by glob() expression or regexp surrounded by /.\n- pin: The package version/repository to pin\n- pin_priority: The pinning priority aka \"the highest package version wins\"\n\n#### Examples\nPin libmysqlclient16 to version 5.1.49-3:\n\n```ruby\napt_preference 'libmysqlclient16' do\n pin 'version 5.1.49-3'\n pin_priority '700'\nend\n```\n\nUnpin libmysqlclient16:\n\n```ruby\napt_preference 'libmysqlclient16' do\n action :remove\nend\n```\n\nPin all packages from dotdeb.org:\n\n```ruby\napt_preference 'dotdeb' do\n glob '*'\n pin 'origin packages.dotdeb.org'\n pin_priority '700'\nend\n```\n\n\nUsage\n-----\nPut `recipe[apt]` first in the run list. If you have other recipes that you want to use to configure how apt behaves, like new sources, notify the execute resource to run, e.g.:\n\n```ruby\ntemplate '/etc/apt/sources.list.d/my_apt_sources.list' do\n notifies :run, 'execute[apt-get update]', :immediately\nend\n```\n\nThe above will run during execution phase since it is a normal template resource, and should appear before other package resources that need the sources in the template.\n\nPut `recipe[apt::cacher-ng]` in the run_list for a server to provide APT caching and add `recipe[apt::cacher-client]` on the rest of the Debian-based nodes to take advantage of the caching server.\n\nIf you want to cleanup unused packages, there is also the `apt-get autoclean` and `apt-get autoremove` resources provided for automated cleanup.\n\n\nLicense & Authors\n-----------------\n- Author:: Joshua Timberman (joshua@chef.io)\n- Author:: Matt Ray (matt@chef.io)\n- Author:: Seth Chisamore (schisamo@chef.io)\n\n```text\nCopyright 2009-2013, Chef Software, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "maintainer": "Chef Software, Inc.", + "maintainer_email": "cookbooks@chef.io", + "license": "Apache 2.0", + "platforms": { + "ubuntu": ">= 0.0.0", + "debian": ">= 0.0.0" + }, + "dependencies": { + + }, + "recommendations": { + + }, + "suggestions": { + + }, + "conflicting": { + + }, + "providing": { + + }, + "replacing": { + + }, + "attributes": { + "apt/cacher-client/restrict_environment": { + "description": "Whether to restrict the search for the caching server to the same environment as this node", + "default": "false", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "apt/cacher_port": { + "description": "Default listen port for the caching server", + "default": "3142", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "apt/cacher_ssl_support": { + "description": "The caching server supports upstream SSL servers via CONNECT", + "default": "false", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "apt/cacher_interface": { + "description": "Default listen interface for the caching server", + "default": null, + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "apt/key_proxy": { + "description": "Passed as the proxy passed to GPG for the apt_repository resource", + "default": "", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "apt/caching_server": { + "description": "Set this to true if the node is a caching server", + "default": "false", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + } + }, + "groupings": { + + }, + "recipes": { + "apt": "Runs apt-get update during compile phase and sets up preseed directories", + "apt::cacher-ng": "Set up an apt-cacher-ng caching proxy", + "apt::cacher-client": "Client for the apt::cacher-ng caching proxy" + }, + "version": "2.7.0", + "source_url": "", + "issues_url": "" +} diff --git a/cookbooks/apt/metadata.rb b/cookbooks/apt/metadata.rb new file mode 100644 index 0000000..752b98c --- /dev/null +++ b/cookbooks/apt/metadata.rb @@ -0,0 +1,38 @@ +name 'apt' +maintainer 'Chef Software, Inc.' +maintainer_email 'cookbooks@chef.io' +license 'Apache 2.0' +description 'Configures apt and apt services and LWRPs for managing apt repositories and preferences' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '2.7.0' +recipe 'apt', 'Runs apt-get update during compile phase and sets up preseed directories' +recipe 'apt::cacher-ng', 'Set up an apt-cacher-ng caching proxy' +recipe 'apt::cacher-client', 'Client for the apt::cacher-ng caching proxy' + +%w{ ubuntu debian }.each do |os| + supports os +end + +attribute 'apt/cacher-client/restrict_environment', + :description => 'Whether to restrict the search for the caching server to the same environment as this node', + :default => 'false' + +attribute 'apt/cacher_port', + :description => 'Default listen port for the caching server', + :default => '3142' + +attribute 'apt/cacher_ssl_support', + :description => 'The caching server supports upstream SSL servers via CONNECT', + :default => 'false' + +attribute 'apt/cacher_interface', + :description => 'Default listen interface for the caching server', + :default => nil + +attribute 'apt/key_proxy', + :description => 'Passed as the proxy passed to GPG for the apt_repository resource', + :default => '' + +attribute 'apt/caching_server', + :description => 'Set this to true if the node is a caching server', + :default => 'false' diff --git a/cookbooks/apt/providers/preference.rb b/cookbooks/apt/providers/preference.rb new file mode 100644 index 0000000..52d473b --- /dev/null +++ b/cookbooks/apt/providers/preference.rb @@ -0,0 +1,69 @@ +# +# Cookbook Name:: apt +# Provider:: preference +# +# Copyright 2010-2011, Chef Software, Inc. +# +# 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. +# + +use_inline_resources if defined?(use_inline_resources) + +def whyrun_supported? + true +end + +# Build preferences.d file contents +def build_pref(package_name, pin, pin_priority) + "Package: #{package_name}\nPin: #{pin}\nPin-Priority: #{pin_priority}\n" +end + +action :add do + preference = build_pref( + new_resource.glob || new_resource.package_name, + new_resource.pin, + new_resource.pin_priority + ) + + directory '/etc/apt/preferences.d' do + owner 'root' + group 'root' + mode 00755 + recursive true + action :create + end + + file "/etc/apt/preferences.d/#{new_resource.name}" do + action :delete + if ::File.exist?("/etc/apt/preferences.d/#{new_resource.name}") + Chef::Log.warn "Replacing #{new_resource.name} with #{new_resource.name}.pref in /etc/apt/preferences.d/" + end + end + + file "/etc/apt/preferences.d/#{new_resource.name}.pref" do + owner 'root' + group 'root' + mode 00644 + content preference + action :create + end +end + +action :remove do + if ::File.exist?("/etc/apt/preferences.d/#{new_resource.name}.pref") + Chef::Log.info "Un-pinning #{new_resource.name} from /etc/apt/preferences.d/" + file "/etc/apt/preferences.d/#{new_resource.name}.pref" do + action :delete + end + end +end diff --git a/cookbooks/apt/providers/repository.rb b/cookbooks/apt/providers/repository.rb new file mode 100644 index 0000000..6f2895f --- /dev/null +++ b/cookbooks/apt/providers/repository.rb @@ -0,0 +1,203 @@ +# +# Cookbook Name:: apt +# Provider:: repository +# +# Copyright 2010-2011, Chef Software, Inc. +# +# 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. +# + +use_inline_resources if defined?(use_inline_resources) + +def whyrun_supported? + true +end + +# install apt key from keyserver +def install_key_from_keyserver(key, keyserver) + execute "install-key #{key}" do + if !node['apt']['key_proxy'].empty? + command "apt-key adv --keyserver-options http-proxy=#{node['apt']['key_proxy']} --keyserver hkp://#{keyserver}:80 --recv #{key}" + else + command "apt-key adv --keyserver #{keyserver} --recv #{key}" + end + action :run + not_if do + extract_fingerprints_from_cmd('apt-key finger').any? do |fingerprint| + fingerprint.end_with?(key.upcase) + end + end + end +end + +# run command and extract gpg ids +def extract_fingerprints_from_cmd(cmd) + so = Mixlib::ShellOut.new(cmd, env: { 'LANG' => 'en_US' }) + so.run_command + so.stdout.split(/\n/).map do |t| + if z = t.match(/^ +Key fingerprint = ([0-9A-F ]+)/) + z[1].split.join + end + end.compact +end + +# install apt key from URI +def install_key_from_uri(uri) + key_name = uri.split(/\//).last + cached_keyfile = "#{Chef::Config[:file_cache_path]}/#{key_name}" + if new_resource.key =~ /http/ + remote_file cached_keyfile do + source new_resource.key + mode 00644 + action :create + end + else + cookbook_file cached_keyfile do + source new_resource.key + cookbook new_resource.cookbook + mode 00644 + action :create + end + end + + execute "install-key #{key_name}" do + command "apt-key add #{cached_keyfile}" + action :run + not_if do + installed_keys = extract_fingerprints_from_cmd('apt-key finger') + proposed_keys = extract_fingerprints_from_cmd("gpg --with-fingerprint #{cached_keyfile}") + (installed_keys & proposed_keys).sort == proposed_keys.sort + end + end +end + +# build repo file contents +def build_repo(uri, distribution, components, trusted, arch, add_deb_src) + components = components.join(' ') if components.respond_to?(:join) + repo_options = [] + repo_options << "arch=#{arch}" if arch + repo_options << 'trusted=yes' if trusted + repo_options = '[' + repo_options.join(' ') + ']' unless repo_options.empty? + repo_info = "#{uri} #{distribution} #{components}\n" + repo_info = "#{repo_options} #{repo_info}" unless repo_options.empty? + repo = "deb #{repo_info}" + repo << "deb-src #{repo_info}" if add_deb_src + repo +end + +def get_ppa_key(ppa_owner, ppa_repo) + # Launchpad has currently only one stable API which is marked as EOL April 2015. + # The new api in devel still uses the same api call for +archive, so I made the version + # configurable to provide some sort of workaround if api 1.0 ceases to exist. + # See https://launchpad.net/+apidoc/ + launchpad_ppa_api = "https://launchpad.net/api/#{node['apt']['launchpad_api_version']}/~%s/+archive/%s" + default_keyserver = 'keyserver.ubuntu.com' + + require 'open-uri' + api_query = format("#{launchpad_ppa_api}/signing_key_fingerprint", ppa_owner, ppa_repo) + begin + key_id = open(api_query).read.delete('"') + rescue OpenURI::HTTPError => e + error = 'Could not access launchpad ppa key api: HttpError: ' + e.message + raise error + rescue SocketError => e + error = 'Could not access launchpad ppa key api: SocketError: ' + e.message + raise error + end + + install_key_from_keyserver(key_id, default_keyserver) +end + +# fetch ppa key, return full repo url +def get_ppa_url(ppa) + repo_schema = 'http://ppa.launchpad.net/%s/%s/ubuntu' + + # ppa:user/repo logic ported from + # http://bazaar.launchpad.net/~ubuntu-core-dev/software-properties/main/view/head:/softwareproperties/ppa.py#L86 + return false unless ppa.start_with?('ppa:') + + ppa_name = ppa.split(':')[1] + ppa_owner = ppa_name.split('/')[0] + ppa_repo = ppa_name.split('/')[1] + ppa_repo = 'ppa' if ppa_repo.nil? + + get_ppa_key(ppa_owner, ppa_repo) + + format(repo_schema, ppa_owner, ppa_repo) +end + +action :add do + # add key + if new_resource.keyserver && new_resource.key + install_key_from_keyserver(new_resource.key, new_resource.keyserver) + elsif new_resource.key + install_key_from_uri(new_resource.key) + end + + file '/var/lib/apt/periodic/update-success-stamp' do + action :nothing + end + + execute 'apt-cache gencaches' do + ignore_failure true + action :nothing + end + + execute 'apt-get update' do + command "apt-get update -o Dir::Etc::sourcelist='sources.list.d/#{new_resource.name}.list' -o Dir::Etc::sourceparts='-' -o APT::Get::List-Cleanup='0'" + ignore_failure true + action :nothing + notifies :run, 'execute[apt-cache gencaches]', :immediately + end + + if new_resource.uri.start_with?('ppa:') + # build ppa repo file + repository = build_repo( + get_ppa_url(new_resource.uri), + new_resource.distribution, + 'main', + new_resource.trusted, + new_resource.arch, + new_resource.deb_src + ) + else + # build repo file + repository = build_repo( + new_resource.uri, + new_resource.distribution, + new_resource.components, + new_resource.trusted, + new_resource.arch, + new_resource.deb_src + ) + end + + file "/etc/apt/sources.list.d/#{new_resource.name}.list" do + owner 'root' + group 'root' + mode 00644 + content repository + action :create + notifies :delete, 'file[/var/lib/apt/periodic/update-success-stamp]', :immediately + notifies :run, 'execute[apt-get update]', :immediately if new_resource.cache_rebuild + end +end + +action :remove do + if ::File.exist?("/etc/apt/sources.list.d/#{new_resource.name}.list") + Chef::Log.info "Removing #{new_resource.name} repository from /etc/apt/sources.list.d/" + file "/etc/apt/sources.list.d/#{new_resource.name}.list" do + action :delete + end + end +end diff --git a/cookbooks/apt/recipes/cacher-client.rb b/cookbooks/apt/recipes/cacher-client.rb new file mode 100644 index 0000000..0f5b93e --- /dev/null +++ b/cookbooks/apt/recipes/cacher-client.rb @@ -0,0 +1,83 @@ +# +# Cookbook Name:: apt +# Recipe:: cacher-client +# +# Copyright 2011-2013 Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class ::Chef::Recipe + include ::Apt +end + +# remove Acquire::http::Proxy lines from /etc/apt/apt.conf since we use 01proxy +# these are leftover from preseed installs +execute 'Remove proxy from /etc/apt/apt.conf' do + command "sed --in-place '/^Acquire::http::Proxy/d' /etc/apt/apt.conf" + only_if 'grep Acquire::http::Proxy /etc/apt/apt.conf' +end + +servers = [] +if node['apt'] + if node['apt']['cacher_ipaddress'] + cacher = Chef::Node.new + cacher.default.name = node['apt']['cacher_ipaddress'] + cacher.default.ipaddress = node['apt']['cacher_ipaddress'] + cacher.default.apt.cacher_port = node['apt']['cacher_port'] + cacher.default.apt.cacher_interface = node['apt']['cacher_interface'] + cacher.default.apt.cacher_ssl_support = node['apt']['cacher_ssl_support'] + servers << cacher + elsif node['apt']['caching_server'] + node.override['apt']['compiletime'] = false + servers << node + end +end + +unless Chef::Config[:solo] || servers.length > 0 + query = 'apt_caching_server:true' + query += " AND chef_environment:#{node.chef_environment}" if node['apt']['cacher-client']['restrict_environment'] + Chef::Log.debug("apt::cacher-client searching for '#{query}'") + servers += search(:node, query) +end + +if servers.length > 0 + Chef::Log.info("apt-cacher-ng server found on #{servers[0]}.") + if servers[0]['apt']['cacher_interface'] + cacher_ipaddress = interface_ipaddress(servers[0], servers[0]['apt']['cacher_interface']) + else + cacher_ipaddress = servers[0].ipaddress + end + t = template '/etc/apt/apt.conf.d/01proxy' do + source '01proxy.erb' + owner 'root' + group 'root' + mode 00644 + variables( + :proxy => cacher_ipaddress, + :port => servers[0]['apt']['cacher_port'], + :proxy_ssl => servers[0]['apt']['cacher_ssl_support'], + :bypass => node['apt']['cache_bypass'] + ) + action(node['apt']['compiletime'] ? :nothing : :create) + notifies :run, 'execute[apt-get update]', :immediately + end + t.run_action(:create) if node['apt']['compiletime'] +else + Chef::Log.info('No apt-cacher-ng server found.') + file '/etc/apt/apt.conf.d/01proxy' do + action :delete + end +end + +include_recipe 'apt::default' diff --git a/cookbooks/apt/recipes/cacher-ng.rb b/cookbooks/apt/recipes/cacher-ng.rb new file mode 100644 index 0000000..c20d5c9 --- /dev/null +++ b/cookbooks/apt/recipes/cacher-ng.rb @@ -0,0 +1,43 @@ +# +# Cookbook Name:: apt +# Recipe:: cacher-ng +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +node.set['apt']['caching_server'] = true + +package 'apt-cacher-ng' do + action :install +end + +directory node['apt']['cacher_dir'] do + owner 'apt-cacher-ng' + group 'apt-cacher-ng' + mode 0755 +end + +template '/etc/apt-cacher-ng/acng.conf' do + source 'acng.conf.erb' + owner 'root' + group 'root' + mode 00644 + notifies :restart, 'service[apt-cacher-ng]', :immediately +end + +service 'apt-cacher-ng' do + supports :restart => true, :status => false + action [:enable, :start] +end diff --git a/cookbooks/apt/recipes/default.rb b/cookbooks/apt/recipes/default.rb new file mode 100644 index 0000000..bfeabbd --- /dev/null +++ b/cookbooks/apt/recipes/default.rb @@ -0,0 +1,112 @@ +# +# Cookbook Name:: apt +# Recipe:: default +# +# Copyright 2008-2013, Chef Software, Inc. +# Copyright 2009, Bryan McLellan +# +# 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. +# + +# On systems where apt is not installed, the resources in this recipe are not +# executed. However, they _must_ still be present in the resource collection +# or other cookbooks which notify these resources will fail on non-apt-enabled +# systems. + +Chef::Log.debug 'apt is not installed. Apt-specific resources will not be executed.' unless apt_installed? + +first_run_file = File.join(Chef::Config[:file_cache_path], 'apt_compile_time_update_first_run') + +file '/var/lib/apt/periodic/update-success-stamp' do + owner 'root' + group 'root' + only_if { apt_installed? } + action :nothing +end + +# If compile_time_update run apt-get update at compile time +if node['apt']['compile_time_update'] && (!::File.exist?('/var/lib/apt/periodic/update-success-stamp') || !::File.exist?(first_run_file)) + e = bash 'apt-get-update at compile time' do + code <<-EOH + apt-get update + touch #{first_run_file} + EOH + ignore_failure true + only_if { apt_installed? } + action :nothing + notifies :touch, 'file[/var/lib/apt/periodic/update-success-stamp]', :immediately + end + e.run_action(:run) +end + +# Updates 'apt-get update' timestamp after each update success +directory '/etc/apt/apt.conf.d' do + recursive true +end + +cookbook_file '/etc/apt/apt.conf.d/15update-stamp' do + source '15update-stamp' +end + +# Run apt-get update to create the stamp file +execute 'apt-get-update' do + command 'apt-get update' + ignore_failure true + only_if { apt_installed? } + not_if { ::File.exist?('/var/lib/apt/periodic/update-success-stamp') } + notifies :touch, 'file[/var/lib/apt/periodic/update-success-stamp]', :immediately +end + +# For other recipes to call to force an update +execute 'apt-get update' do + command 'apt-get update' + ignore_failure true + only_if { apt_installed? } + action :nothing + notifies :touch, 'file[/var/lib/apt/periodic/update-success-stamp]', :immediately +end + +# Automatically remove packages that are no longer needed for dependencies +execute 'apt-get autoremove' do + command 'apt-get -y autoremove' + only_if { apt_installed? } + action :nothing +end + +# Automatically remove .deb files for packages no longer on your system +execute 'apt-get autoclean' do + command 'apt-get -y autoclean' + only_if { apt_installed? } + action :nothing +end + +execute 'apt-get-update-periodic' do + command 'apt-get update' + ignore_failure true + only_if do + apt_installed? && + ::File.exist?('/var/lib/apt/periodic/update-success-stamp') && + ::File.mtime('/var/lib/apt/periodic/update-success-stamp') < Time.now - node['apt']['periodic_update_min_delay'] + end + notifies :touch, 'file[/var/lib/apt/periodic/update-success-stamp]', :immediately +end + +%w(/var/cache/local /var/cache/local/preseeding).each do |dirname| + directory dirname do + owner 'root' + group 'root' + mode 00755 + action :create + only_if { apt_installed? } + end +end diff --git a/cookbooks/apt/recipes/unattended-upgrades.rb b/cookbooks/apt/recipes/unattended-upgrades.rb new file mode 100644 index 0000000..88fccd8 --- /dev/null +++ b/cookbooks/apt/recipes/unattended-upgrades.rb @@ -0,0 +1,47 @@ +# +# Cookbook Name:: apt +# Recipe:: unattended-upgrades +# +# Copyright 2014, Chef Software, Inc. +# +# 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. +# + +# On systems where apt is not installed, the resources in this recipe are not +# executed. However, they _must_ still be present in the resource collection +# or other cookbooks which notify these resources will fail on non-apt-enabled +# systems. +# + +package 'unattended-upgrades' do + response_file 'unattended-upgrades.seed.erb' + action :install +end + +package 'bsd-mailx' do + only_if { node['apt']['unattended_upgrades']['mail'] } +end + +template '/etc/apt/apt.conf.d/20auto-upgrades' do + owner 'root' + group 'root' + mode '644' + source '20auto-upgrades.erb' +end + +template '/etc/apt/apt.conf.d/50unattended-upgrades' do + owner 'root' + group 'root' + mode '644' + source '50unattended-upgrades.erb' +end diff --git a/cookbooks/apt/resources/preference.rb b/cookbooks/apt/resources/preference.rb new file mode 100644 index 0000000..64eb34f --- /dev/null +++ b/cookbooks/apt/resources/preference.rb @@ -0,0 +1,37 @@ +# +# Cookbook Name:: apt +# Resource:: preference +# +# Copyright 2010-2013, Chef Software, Inc. +# +# 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. +# + +actions :add, :remove +default_action :add if defined?(default_action) # Chef > 10.8 + +# Needed for Chef versions < 0.10.10 +def initialize(*args) + super + @action = :add +end + +state_attrs :glob, + :package_name, + :pin, + :pin_priority + +attribute :package_name, :kind_of => String, :name_attribute => true, :regex => [/^([a-z]|[A-Z]|[0-9]|_|-|\.)+$/] +attribute :glob, :kind_of => String +attribute :pin, :kind_of => String +attribute :pin_priority, :kind_of => String diff --git a/cookbooks/apt/resources/repository.rb b/cookbooks/apt/resources/repository.rb new file mode 100644 index 0000000..b9268ee --- /dev/null +++ b/cookbooks/apt/resources/repository.rb @@ -0,0 +1,55 @@ +# +# Cookbook Name:: apt +# Resource:: repository +# +# Copyright 2010-2013, Chef Software, Inc. +# +# 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. +# + +actions :add, :remove +default_action :add if defined?(default_action) # Chef > 10.8 + +# Needed for Chef versions < 0.10.10 +def initialize(*args) + super + @action = :add +end + +state_attrs :arch, + :cache_rebuild, + :components, + :cookbook, + :deb_src, + :distribution, + :key, + :keyserver, + :repo_name, + :trusted, + :uri + +# name of the repo, used for source.list filename +attribute :repo_name, :kind_of => String, :name_attribute => true, :regex => [/^([a-z]|[A-Z]|[0-9]|_|-|\.)+$/] +attribute :uri, :kind_of => String +attribute :distribution, :kind_of => String +attribute :components, :kind_of => Array, :default => [] +attribute :arch, :kind_of => String, :default => nil +attribute :trusted, :kind_of => [TrueClass, FalseClass], :default => false +# whether or not to add the repository as a source repo as well +attribute :deb_src, :default => false +attribute :keyserver, :kind_of => String, :default => nil +attribute :key, :kind_of => String, :default => nil +attribute :cookbook, :kind_of => String, :default => nil +# trigger cache rebuild +# If not you can trigger in the recipe itself after checking the status of resource.updated{_by_last_action}? +attribute :cache_rebuild, :kind_of => [TrueClass, FalseClass], :default => true diff --git a/cookbooks/apt/templates/debian-6.0/acng.conf.erb b/cookbooks/apt/templates/debian-6.0/acng.conf.erb new file mode 100644 index 0000000..98a681c --- /dev/null +++ b/cookbooks/apt/templates/debian-6.0/acng.conf.erb @@ -0,0 +1,173 @@ +# Letter case in directive names does not matter. Must be separated with colons. +# Valid boolean values are a zero number for false, non-zero numbers for true. + +CacheDir: <%= node['apt']['cacher_dir'] %> + +# set empty to disable logging +LogDir: /var/log/apt-cacher-ng + +# TCP (http) port +# Set to 9999 to emulate apt-proxy +Port:<%= node['apt']['cacher_port'] %> + +# Addresses or hostnames to listen on. Multiple addresses must be separated by +# spaces. Each entry must be associated with a local interface. DNS resolution +# is performed using getaddrinfo(3) for all available protocols (i.e. IPv4 and +# IPv6 if available). +# +# Default: not set, will listen on all interfaces. +# +# BindAddress: localhost 192.168.7.254 publicNameOnMainInterface + +#Proxy: http://www-proxy.example.net:80 +#proxy: http://username:proxypassword@proxy.example.net:3128 + +# Repository remapping. See manual for details. +# In this example, backends file is generated during package installation. +Remap-debrep: file:deb_mirror*.gz /debian ; file:backends_debian +Remap-uburep: file:ubuntu_mirrors /ubuntu ; file:backends_ubuntu +Remap-debvol: file:debvol_mirror*.gz /debian-volatile ; file:backends_debvol +Remap-cygwin: file:cygwin_mirrors /cygwin # ; file:backends_cygwin # incomplete, please create this file + +# Virtual page accessible in a web browser to see statistics and status +# information, i.e. under http://localhost:3142/acng-report.html +ReportPage: acng-report.html + +# Socket file for accessing through local UNIX socket instead of TCP/IP. Can be +# used with inetd bridge or cron client. +# SocketPath:/var/run/apt-cacher-ng/socket + +# Forces log file to be written to disk after every line when set to 1. Default +# is 0, buffer flush happens after client disconnects. +# +# (technically, this is an alias to the Debug option provided for convenience) +# +# UnbufferLogs: 0 + +# Set to 0 to store only type, time and transfer sizes. +# 1 -> client IP and relative local path are logged too +# VerboseLog: 1 + +# Don't detach from the console +# ForeGround: 0 + +# Store the pid of the daemon process therein +# PidFile: /var/run/apt-cacher-ng/pid + +# Forbid outgoing connections, work around them or respond with 503 error +# offlinemode:0 + +# Forbid all downloads that don't run through preconfigured backends (.where) +#ForceManaged: 0 + +# Days before considering an unreferenced file expired (to be deleted). +# Warning: if the value is set too low and particular index files are not +# available for some days (mirror downtime) there is a risk of deletion of +# still usefull package files. +ExTreshold: 4 + +# Stop expiration when a critical problem appeared. Currently only failed +# refresh of an index file is considered as critical. +# +# WARNING: don't touch this option or set to a non-zero number. +# Anything else is DANGEROUS and may cause data loss. +# +# ExAbortOnProblems: 1 + +# Replace some Windows/DOS-FS incompatible chars when storing +# StupidFs: 0 + +# Experimental feature for apt-listbugs: pass-through SOAP requests and +# responses to/from bugs.debian.org. If not set, default is true if +# ForceManaged is enabled and false otherwise. +# ForwardBtsSoap: 1 + +# The daemon has a small cache for DNS data, to speed up resolution. The +# expiration time of the DNS entries can be configured in seconds. +# DnsCacheSeconds: 3600 + +# Don't touch the following values without good consideration! +# +# Max. count of connection threads kept ready (for faster response in the +# future). Should be a sane value between 0 and average number of connections, +# and depend on the amount of spare RAM. +# MaxStandbyConThreads: 8 +# +# Hard limit of active thread count for incomming connections, i.e. operation +# is refused when this value is reached (below zero = unlimited). +# MaxConThreads: -1 +# +#VfilePattern = (^|.*?/)(Index|Packages\.bz2|Packages\.gz|Packages|Release|Release\.gpg|Sources\.bz2|Sources\.gz|Sources|release|index\.db-.*\.gz|Contents-[^/]*\.gz|pkglist[^/]*\.bz2|rclist[^/]*\.bz2|/meta-release[^/]*|Translation[^/]*\.bz2)$ +#PfilePattern = .*(\.deb|\.rpm|\.dsc|\.tar\.gz\.gpg|\.tar\.gz|\.diff\.gz|\.diff\.bz2|\.jigdo|\.template|changelog|copyright|\.udeb|\.diff/.*\.gz|vmlinuz|initrd\.gz|(Devel)?ReleaseAnnouncement(\\?.*)?)$ +# Whitelist for expiration, file types not to be removed even when being +# unreferenced. Default: same as VfilePattern which is a safe bed. When and +# only when the only used mirrors are official repositories (with working +# Release files) then it might be set to something more restrictive, like +# (^|.*?/)(Release|Release\.gpg|release|meta-release|Translation[^/]*\.bz2)$ +#WfilePattern = (^|.*?/)(Index|Packages\.bz2|Packages\.gz|Packages|Release|Release\.gpg|Sources\.bz2|Sources\.gz|Sources|release|index\.db-.*\.gz|Contents-[^/]*\.gz|pkglist[^/]*\.bz2|rclist[^/]*\.bz2|/meta-release[^/]*|Translation[^/]*\.bz2)$ + +# Higher modes only working with the debug version +# Warning, writes a lot into apt-cacher.err logfile +# Value overwrites UnbufferLogs setting (aliased) +# Debug:3 + +# Usually, general purpose proxies like Squid expose the IP adress of the +# client user to the remote server using the X-Forwarded-For HTTP header. This +# behaviour can be optionally turned on with the Expose-Origin option. +# ExposeOrigin: 0 + +# When logging the originating IP address, trust the information supplied by +# the client in the X-Forwarded-For header. +# LogSubmittedOrigin: 0 + +# The version string reported to the peer, to be displayed as HTTP client (and +# version) in the logs of the mirror. +# WARNING: some archives use this header to detect/guess capabilities of the +# client (i.e. redirection support) and change the behaviour accordingly, while +# ACNG might not support the expected features. Expect side effects. +# +# UserAgent: Yet Another HTTP Client/1.2.3p4 + +# In some cases the Import and Expiration tasks might create fresh volatile +# data for internal use by reconstructing them using patch files. This +# by-product might be recompressed with bzip2 and with some luck the resulting +# file becomes identical to the *.bz2 file on the server, usable for APT +# clients trying to fetch the full .bz2 compressed version. Injection of the +# generated files into the cache has however a disadvantage on underpowered +# servers: bzip2 compession can create high load on the server system and the +# visible download of the busy .bz2 files also becomes slower. +# +# RecompBz2: 0 + +# Network timeout for outgoing connections. +# NetworkTimeout: 60 + +# Sometimes it makes sense to not store the data in cache and just return the +# package data to client as it comes in. DontCache parameters can enable this +# behaviour for certain URL types. The tokens are extended regular expressions +# that URLs are matched against. +# +# DontCacheRequested is applied to the URL as it comes in from the client. +# Example: exclude packages built with kernel-package for x86 +# DontCacheRequested: linux-.*_10\...\.Custo._i386 +# Example usecase: exclude popular private IP ranges from caching +# DontCacheRequested: 192.168.0 ^10\..* 172.30 +# +# DontCacheResolved is applied to URLs after mapping to the target server. If +# multiple backend servers are specified then it's only matched against the +# download link for the FIRST possible source (due to implementation limits). +# Example usecase: all Ubuntu stuff comes from a local mirror (specified as +# backend), don't cache it again: +# DontCacheResolved: ubuntumirror.local.net +# +# DontCache directive sets (overrides) both, DontCacheResolved and +# DontCacheRequested. Provided for convenience, see those directives for +# details. +# +# Default permission set of freshly created files and directories, as octal +# numbers (see chmod(1) for details). +# Can by limited by the umask value (see umask(2) for details) if it's set in +# the environment of the starting shell, e.g. in apt-cacher-ng init script or +# in its configuration file. +# DirPerms: 00755 +# FilePerms: 00664 diff --git a/cookbooks/apt/templates/default/01proxy.erb b/cookbooks/apt/templates/default/01proxy.erb new file mode 100644 index 0000000..1cd2256 --- /dev/null +++ b/cookbooks/apt/templates/default/01proxy.erb @@ -0,0 +1,9 @@ +Acquire::http::Proxy "http://<%= @proxy %>:<%= @port %>"; +<% if @proxy_ssl %> +Acquire::https::Proxy "http://<%= @proxy %>:<%= @port %>"; +<% else %> +Acquire::https::Proxy "DIRECT"; +<% end %> +<% @bypass.each do |bypass, type| %> +Acquire::<%= type %>::Proxy::<%= bypass %> "DIRECT"; +<% end %> diff --git a/cookbooks/apt/templates/default/20auto-upgrades.erb b/cookbooks/apt/templates/default/20auto-upgrades.erb new file mode 100644 index 0000000..54449b6 --- /dev/null +++ b/cookbooks/apt/templates/default/20auto-upgrades.erb @@ -0,0 +1,2 @@ +APT::Periodic::Update-Package-Lists "<%= node['apt']['unattended_upgrades']['update_package_lists'] ? 1 : 0 %>"; +APT::Periodic::Unattended-Upgrade "<%= node['apt']['unattended_upgrades']['enable'] ? 1 : 0 %>"; diff --git a/cookbooks/apt/templates/default/50unattended-upgrades.erb b/cookbooks/apt/templates/default/50unattended-upgrades.erb new file mode 100644 index 0000000..9984973 --- /dev/null +++ b/cookbooks/apt/templates/default/50unattended-upgrades.erb @@ -0,0 +1,68 @@ +// Automatically upgrade packages from these (origin:archive) pairs +Unattended-Upgrade::Allowed-Origins { +<% unless node['apt']['unattended_upgrades']['allowed_origins'].empty? -%> +<% node['apt']['unattended_upgrades']['allowed_origins'].each do |origin| -%> + "<%= origin %>"; +<% end -%> +<% end -%> +}; + + +// List of packages to not update +Unattended-Upgrade::Package-Blacklist { +<% unless node['apt']['unattended_upgrades']['package_blacklist'].empty? -%> +<% node['apt']['unattended_upgrades']['package_blacklist'].each do |package| -%> + "<%= package %>"; +<% end -%> +<% end -%> +}; + +// This option allows you to control if on a unclean dpkg exit +// unattended-upgrades will automatically run +// dpkg --force-confold --configure -a +// The default is true, to ensure updates keep getting installed +Unattended-Upgrade::AutoFixInterruptedDpkg "<%= node['apt']['unattended_upgrades']['auto_fix_interrupted_dpkg'] ? 'true' : 'false' %>"; + +// Split the upgrade into the smallest possible chunks so that +// they can be interrupted with SIGUSR1. This makes the upgrade +// a bit slower but it has the benefit that shutdown while a upgrade +// is running is possible (with a small delay) +Unattended-Upgrade::MinimalSteps "<%= node['apt']['unattended_upgrades']['minimal_steps'] ? 'true' : 'false' %>"; + +// Install all unattended-upgrades when the machine is shuting down +// instead of doing it in the background while the machine is running +// This will (obviously) make shutdown slower +Unattended-Upgrade::InstallOnShutdown "<%= node['apt']['unattended_upgrades']['install_on_shutdown'] ? 'true' : 'false' %>"; + +// Send email to this address for problems or packages upgrades +// If empty or unset then no email is sent, make sure that you +// have a working mail setup on your system. A package that provides +// 'mailx' must be installed. +<% if node['apt']['unattended_upgrades']['mail'] -%> +Unattended-Upgrade::Mail "<%= node['apt']['unattended_upgrades']['mail'] %>"; +<% end -%> + +// Set this value to "true" to get emails only on errors. Default +// is to always send a mail if Unattended-Upgrade::Mail is set +Unattended-Upgrade::MailOnlyOnError "<%= node['apt']['unattended_upgrades']['mail_only_on_error'] ? 'true' : 'false' %>"; + +// Do automatic removal of new unused dependencies after the upgrade +// (equivalent to apt-get autoremove) +Unattended-Upgrade::Remove-Unused-Dependencies "<%= node['apt']['unattended_upgrades']['remove_unused_dependencies'] ? 'true' : 'false' %>"; + +// Automatically reboot *WITHOUT CONFIRMATION* if a +// the file /var/run/reboot-required is found after the upgrade +Unattended-Upgrade::Automatic-Reboot "<%= node['apt']['unattended_upgrades']['automatic_reboot'] ? 'true' : 'false' %>"; + +// If automatic reboot is enabled and needed, reboot at the specific +// time instead of immediately +// Default: "now" +<% if node['apt']['unattended_upgrades']['automatic_reboot'] -%> +Unattended-Upgrade::Automatic-Reboot-Time "<%= node['apt']['unattended_upgrades']['automatic_reboot_time'] %>"; +<% end %> + +// Use apt bandwidth limit feature, this example limits the download +// speed to 70kb/sec +<% if node['apt']['unattended_upgrades']['dl_limit'] -%> +Acquire::http::Dl-Limit "<%= node['apt']['unattended_upgrades']['dl_limit'] %>"; +<% end -%> diff --git a/cookbooks/apt/templates/default/acng.conf.erb b/cookbooks/apt/templates/default/acng.conf.erb new file mode 100644 index 0000000..3aa0c92 --- /dev/null +++ b/cookbooks/apt/templates/default/acng.conf.erb @@ -0,0 +1,275 @@ +# Letter case in directive names does not matter. Must be separated with colons. +# Valid boolean values are a zero number for false, non-zero numbers for true. + +CacheDir: <%= node['apt']['cacher_dir'] %> + +# set empty to disable logging +LogDir: /var/log/apt-cacher-ng + +# place to look for additional configuration and resource files if they are not +# found in the configuration directory +# SupportDir: /usr/lib/apt-cacher-ng + +# TCP (http) port +# Set to 9999 to emulate apt-proxy +Port:<%= node['apt']['cacher_port'] %> + +# Addresses or hostnames to listen on. Multiple addresses must be separated by +# spaces. Each entry must be an exact local address which is associated with a +# local interface. DNS resolution is performed using getaddrinfo(3) for all +# available protocols (IPv4, IPv6, ...). Using a protocol specific format will +# create binding(s) only on protocol specific socket(s) (e.g. 0.0.0.0 will listen +# only to IPv4). +# +# Default: not set, will listen on all interfaces and protocols +# +# BindAddress: localhost 192.168.7.254 publicNameOnMainInterface + +# The specification of another proxy which shall be used for downloads. +# Username and password are, and see manual for limitations. +# +#Proxy: http://www-proxy.example.net:80 +#proxy: username:proxypassword@proxy.example.net:3128 + +# Repository remapping. See manual for details. +# In this example, some backends files might be generated during package +# installation using information collected on the system. +Remap-debrep: file:deb_mirror*.gz /debian ; file:backends_debian # Debian Archives +Remap-uburep: file:ubuntu_mirrors /ubuntu ; file:backends_ubuntu # Ubuntu Archives +Remap-debvol: file:debvol_mirror*.gz /debian-volatile ; file:backends_debvol # Debian Volatile Archives +Remap-cygwin: file:cygwin_mirrors /cygwin # ; file:backends_cygwin # incomplete, please create this file or specify preferred mirrors here +Remap-sfnet: file:sfnet_mirrors # ; file:backends_sfnet # incomplete, please create this file or specify preferred mirrors here +Remap-alxrep: file:archlx_mirrors /archlinux # ; file:backend_archlx # Arch Linux +Remap-fedora: file:fedora_mirrors # Fedora Linux +Remap-epel: file:epel_mirrors # Fedora EPEL +Remap-slrep: file:sl_mirrors # Scientific Linux + +# This is usually not needed for security.debian.org because it's always the +# same DNS hostname. However, it might be enabled in order to use hooks, +# ForceManaged mode or special flags in this context. +# Remap-secdeb: security.debian.org + +# Virtual page accessible in a web browser to see statistics and status +# information, i.e. under http://localhost:3142/acng-report.html +ReportPage: acng-report.html + +# Socket file for accessing through local UNIX socket instead of TCP/IP. Can be +# used with inetd bridge or cron client. +# SocketPath:/var/run/apt-cacher-ng/socket + +# Forces log file to be written to disk after every line when set to 1. Default +# is 0, buffers are flushed when the client disconnects. +# +# (technically, alias to the Debug option, see its documentation for details) +# +# UnbufferLogs: 0 + +# Set to 0 to store only type, time and transfer sizes. +# 1 -> client IP and relative local path are logged too +# VerboseLog: 1 + +# Don't detach from the console +# ForeGround: 0 + +# Store the pid of the daemon process therein +# PidFile: /var/run/apt-cacher-ng/pid + +# Forbid outgoing connections, work around them or respond with 503 error +# offlinemode:0 + +# Forbid all downloads that don't run through preconfigured backends (.where) +#ForceManaged: 0 + +# Days before considering an unreferenced file expired (to be deleted). +# Warning: if the value is set too low and particular index files are not +# available for some days (mirror downtime) there is a risk of deletion of +# still useful package files. +ExTreshold: 4 + +# Stop expiration when a critical problem appeared. Currently only failed +# refresh of an index file is considered as critical. +# +# WARNING: don't touch this option or set to zero. +# Anything else is DANGEROUS and may cause data loss. +# +# ExAbortOnProblems: 1 + +# Replace some Windows/DOS-FS incompatible chars when storing +# StupidFs: 0 + +# Experimental feature for apt-listbugs: pass-through SOAP requests and +# responses to/from bugs.debian.org. If not set, default is true if +# ForceManaged is enabled and false otherwise. +# ForwardBtsSoap: 1 + +# The daemon has a small cache for DNS data, to speed up resolution. The +# expiration time of the DNS entries can be configured in seconds. +# DnsCacheSeconds: 3600 + +# Don't touch the following values without good consideration! +# +# Max. count of connection threads kept ready (for faster response in the +# future). Should be a sane value between 0 and average number of connections, +# and depend on the amount of spare RAM. +# MaxStandbyConThreads: 8 +# +# Hard limit of active thread count for incoming connections, i.e. operation +# is refused when this value is reached (below zero = unlimited). +# MaxConThreads: -1 +# +# Pigeonholing files with regular expressions (static/volatile). Can be +# overriden here but not should not be done permanently because future update +# of default settings would not be applied later. +# VfilePattern = (^|.*?/)(Index|Packages(\.gz|\.bz2|\.lzma|\.xz)?|InRelease|Release|Release\.gpg|Sources(\.gz|\.bz2|\.lzma|\.xz)?|release|index\.db-.*\.gz|Contents-[^/]*(\.gz|\.bz2|\.lzma|\.xz)?|pkglist[^/]*\.bz2|rclist[^/]*\.bz2|/meta-release[^/]*|Translation[^/]*(\.gz|\.bz2|\.lzma|\.xz)?|MD5SUMS|SHA1SUMS|((setup|setup-legacy)(\.ini|\.bz2|\.hint)(\.sig)?)|mirrors\.lst|repo(index|md)\.xml(\.asc|\.key)?|directory\.yast|products|content(\.asc|\.key)?|media|filelists\.xml\.gz|filelists\.sqlite\.bz2|repomd\.xml|packages\.[a-zA-Z][a-zA-Z]\.gz|info\.txt|license\.tar\.gz|license\.zip|.*\.db(\.tar\.gz)?|.*\.files\.tar\.gz|.*\.abs\.tar\.gz|metalink\?repo|.*prestodelta\.xml\.gz)$|/dists/.*/installer-[^/]+/[^0-9][^/]+/images/.* +# PfilePattern = .*(\.d?deb|\.rpm|\.dsc|\.tar(\.gz|\.bz2|\.lzma|\.xz)(\.gpg)?|\.diff(\.gz|\.bz2|\.lzma|\.xz)|\.jigdo|\.template|changelog|copyright|\.udeb|\.debdelta|\.diff/.*\.gz|(Devel)?ReleaseAnnouncement(\?.*)?|[a-f0-9]+-(susedata|updateinfo|primary|deltainfo).xml.gz|fonts/(final/)?[a-z]+32.exe(\?download.*)?|/dists/.*/installer-[^/]+/[0-9][^/]+/images/.*)$ +# Whitelist for expiration, file types not to be removed even when being +# unreferenced. Default: many parts from VfilePattern where no parent index +# exists or might be unknown. +# WfilePattern = (^|.*?/)(Release|InRelease|Release\.gpg|(Packages|Sources)(\.gz|\.bz2|\.lzma|\.xz)?|Translation[^/]*(\.gz|\.bz2|\.lzma|\.xz)?|MD5SUMS|SHA1SUMS|.*\.xml|.*\.db\.tar\.gz|.*\.files\.tar\.gz|.*\.abs\.tar\.gz|[a-z]+32.exe)$|/dists/.*/installer-.*/images/.* + +# Higher modes only working with the debug version +# Warning, writes a lot into apt-cacher.err logfile +# Value overwrites UnbufferLogs setting (aliased) +# Debug:3 + +# Usually, general purpose proxies like Squid expose the IP address of the +# client user to the remote server using the X-Forwarded-For HTTP header. This +# behaviour can be optionally turned on with the Expose-Origin option. +# ExposeOrigin: 0 + +# When logging the originating IP address, trust the information supplied by +# the client in the X-Forwarded-For header. +# LogSubmittedOrigin: 0 + +# The version string reported to the peer, to be displayed as HTTP client (and +# version) in the logs of the mirror. +# WARNING: some archives use this header to detect/guess capabilities of the +# client (i.e. redirection support) and change the behaviour accordingly, while +# ACNG might not support the expected features. Expect side effects. +# +# UserAgent: Yet Another HTTP Client/1.2.3p4 + +# In some cases the Import and Expiration tasks might create fresh volatile +# data for internal use by reconstructing them using patch files. This +# by-product might be recompressed with bzip2 and with some luck the resulting +# file becomes identical to the *.bz2 file on the server, usable for APT +# clients trying to fetch the full .bz2 compressed version. Injection of the +# generated files into the cache has however a disadvantage on underpowered +# servers: bzip2 compression can create high load on the server system and the +# visible download of the busy .bz2 files also becomes slower. +# +# RecompBz2: 0 + +# Network timeout for outgoing connections. +# NetworkTimeout: 60 + +# Sometimes it makes sense to not store the data in cache and just return the +# package data to client as it comes in. DontCache parameters can enable this +# behaviour for certain URL types. The tokens are extended regular expressions +# that URLs are matched against. +# +# DontCacheRequested is applied to the URL as it comes in from the client. +# Example: exclude packages built with kernel-package for x86 +# DontCacheRequested: linux-.*_10\...\.Custo._i386 +# Example usecase: exclude popular private IP ranges from caching +# DontCacheRequested: 192.168.0 ^10\..* 172.30 +# +# DontCacheResolved is applied to URLs after mapping to the target server. If +# multiple backend servers are specified then it's only matched against the +# download link for the FIRST possible source (due to implementation limits). +# Example usecase: all Ubuntu stuff comes from a local mirror (specified as +# backend), don't cache it again: +# DontCacheResolved: ubuntumirror.local.net +# +# DontCache directive sets (overrides) both, DontCacheResolved and +# DontCacheRequested. Provided for convenience, see those directives for +# details. +# +# Default permission set of freshly created files and directories, as octal +# numbers (see chmod(1) for details). +# Can by limited by the umask value (see umask(2) for details) if it's set in +# the environment of the starting shell, e.g. in apt-cacher-ng init script or +# in its configuration file. +# DirPerms: 00755 +# FilePerms: 00664 +# +# +# It's possible to use use apt-cacher-ng as a regular web server with limited +# feature set, i.e. +# including directory browsing and download of any file; +# excluding sorting, mime types/encodings, CGI execution, index page +# redirection and other funny things. +# To get this behavior, mappings between virtual directories and real +# directories on the server must be defined with the LocalDirs directive. +# Virtual and real dirs are separated by spaces, multiple pairs are separated +# by semi-colons. Real directories must be absolute paths. +# NOTE: Since the names of that key directories share the same namespace as +# repository names (see Remap-...) it's administrators job to avoid such +# collisions on them (unless created deliberately). +# +# LocalDirs: woo /data/debarchive/woody ; hamm /data/debarchive/hamm + +# Precache a set of files referenced by specified index files. This can be used +# to create a partial mirror usable for offline work. There are certain limits +# and restrictions on the path specification, see manual for details. A list of +# (maybe) relevant index files could be retrieved via +# "apt-get --print-uris update" on a client machine. +# +# PrecacheFor: debrep/dists/unstable/*/source/Sources* debrep/dists/unstable/*/binary-amd64/Packages* + +# Arbitrary set of data to append to request headers sent over the wire. Should +# be a well formated HTTP headers part including newlines (DOS style) which +# can be entered as escape sequences (\r\n). +# RequestAppendix: X-Tracking-Choice: do-not-track\r\n + +# Specifies the IP protocol families to use for remote connections. Order does +# matter, first specified are considered first. Possible combinations: +# v6 v4 +# v4 v6 +# v6 +# v4 +# (empty or not set: use system default) +# +# ConnectProto: v6 v4 + +# Regular expiration algorithm finds package files which are no longer listed +# in any index file and removes them of them after a safety period. +# This option allows to keep more versions of a package in the cache after +# safety period is over. +# KeepExtraVersions: 1 + +# Optionally uses TCP access control provided by libwrap, see hosts_access(5) +# for details. Daemon name is apt-cacher-ng. Default if not set: decided on +# startup by looking for explicit mentioning of apt-cacher-ng in +# /etc/hosts.allow or /etc/hosts.deny files. +# UseWrap: 0 + +# If many machines from the same local network attempt to update index files +# (apt-get update) at nearly the same time, the known state of these index file +# is temporarily frozen and multiple requests receive the cached response +# without contacting the server. This parameter (in seconds) specifies the +# length of this period before the files are considered outdated. +# Setting it too low transfers more data and increases remote server load, +# setting it too high (more than a couple of minutes) increases the risk of +# delivering inconsistent responses to the clients. +# FreshIndexMaxAge: 27 + +# Usually the users are not allowed to specify custom TCP ports of remote +# mirrors in the requests, only the default HTTP port can be used (instead, +# proxy administrator can create Remap- rules with custom ports). This +# restriction can be disabled by specifying a list of allowed ports or 0 for +# any port. +# +# AllowUserPorts: 80 + +# Normally the HTTP redirection responses are forwarded to the original caller +# (i.e. APT) which starts a new download attempt from the new URL. This +# solution is ok for client configurations with proxy mode but doesn't work +# well with configurations using URL prefixes. To work around this the server +# can restart its own download with another URL. However, this might be used to +# circumvent download source policies by malicious users. +# The RedirMax option specifies how many such redirects the server should +# follow per request, 0 disables the internal redirection. If not set, +# default value is 0 if ForceManaged is used and 5 otherwise. +# +# RedirMax: 5 diff --git a/cookbooks/apt/templates/default/unattended-upgrades.seed.erb b/cookbooks/apt/templates/default/unattended-upgrades.seed.erb new file mode 100644 index 0000000..5ee5e93 --- /dev/null +++ b/cookbooks/apt/templates/default/unattended-upgrades.seed.erb @@ -0,0 +1 @@ +unattended-upgrades unattended-upgrades/enable_auto_updates boolean <%= node['apt']['unattended_upgrades']['enable'] ? 'true' : 'false' %> diff --git a/cookbooks/apt/templates/ubuntu-10.04/acng.conf.erb b/cookbooks/apt/templates/ubuntu-10.04/acng.conf.erb new file mode 100644 index 0000000..0e7c779 --- /dev/null +++ b/cookbooks/apt/templates/ubuntu-10.04/acng.conf.erb @@ -0,0 +1,269 @@ +# Letter case in directive names does not matter. Must be separated with colons. +# Valid boolean values are a zero number for false, non-zero numbers for true. + +CacheDir: <%= node['apt']['cacher_dir'] %> + +# set empty to disable logging +LogDir: /var/log/apt-cacher-ng + +# place to look for additional configuration and resource files if they are not +# found in the configuration directory +# SupportDir: /usr/lib/apt-cacher-ng + +# TCP (http) port +# Set to 9999 to emulate apt-proxy +Port:<%= node['apt']['cacher_port'] %> + +# Addresses or hostnames to listen on. Multiple addresses must be separated by +# spaces. Each entry must be an exact local address which is associated with a +# local interface. DNS resolution is performed using getaddrinfo(3) for all +# available protocols (IPv4, IPv6, ...). Using a protocol specific format will +# create binding(s) only on protocol specific socket(s) (e.g. 0.0.0.0 will listen +# only to IPv4). +# +# Default: not set, will listen on all interfaces and protocols +# +# BindAddress: localhost 192.168.7.254 publicNameOnMainInterface + +# The specification of another proxy which shall be used for downloads. +# Username and password are, and see manual for limitations. +# +#Proxy: http://www-proxy.example.net:80 +#proxy: username:proxypassword@proxy.example.net:3128 + +# Repository remapping. See manual for details. +# In this example, some backends files might be generated during package +# installation using information collected on the system. +Remap-debrep: file:deb_mirror*.gz /debian ; file:backends_debian # Debian Archives +Remap-uburep: file:ubuntu_mirrors /ubuntu ; file:backends_ubuntu # Ubuntu Archives +Remap-debvol: file:debvol_mirror*.gz /debian-volatile ; file:backends_debvol # Debian Volatile Archives + +# This is usually not needed for security.debian.org because it's always the +# same DNS hostname. However, it might be enabled in order to use hooks, +# ForceManaged mode or special flags in this context. +# Remap-secdeb: security.debian.org + +# Virtual page accessible in a web browser to see statistics and status +# information, i.e. under http://localhost:3142/acng-report.html +ReportPage: acng-report.html + +# Socket file for accessing through local UNIX socket instead of TCP/IP. Can be +# used with inetd bridge or cron client. +# SocketPath:/var/run/apt-cacher-ng/socket + +# Forces log file to be written to disk after every line when set to 1. Default +# is 0, buffers are flushed when the client disconnects. +# +# (technically, alias to the Debug option, see its documentation for details) +# +# UnbufferLogs: 0 + +# Set to 0 to store only type, time and transfer sizes. +# 1 -> client IP and relative local path are logged too +# VerboseLog: 1 + +# Don't detach from the console +# ForeGround: 0 + +# Store the pid of the daemon process therein +# PidFile: /var/run/apt-cacher-ng/pid + +# Forbid outgoing connections, work around them or respond with 503 error +# offlinemode:0 + +# Forbid all downloads that don't run through preconfigured backends (.where) +#ForceManaged: 0 + +# Days before considering an unreferenced file expired (to be deleted). +# Warning: if the value is set too low and particular index files are not +# available for some days (mirror downtime) there is a risk of deletion of +# still useful package files. +ExTreshold: 4 + +# Stop expiration when a critical problem appeared. Currently only failed +# refresh of an index file is considered as critical. +# +# WARNING: don't touch this option or set to zero. +# Anything else is DANGEROUS and may cause data loss. +# +# ExAbortOnProblems: 1 + +# Replace some Windows/DOS-FS incompatible chars when storing +# StupidFs: 0 + +# Experimental feature for apt-listbugs: pass-through SOAP requests and +# responses to/from bugs.debian.org. If not set, default is true if +# ForceManaged is enabled and false otherwise. +# ForwardBtsSoap: 1 + +# The daemon has a small cache for DNS data, to speed up resolution. The +# expiration time of the DNS entries can be configured in seconds. +# DnsCacheSeconds: 3600 + +# Don't touch the following values without good consideration! +# +# Max. count of connection threads kept ready (for faster response in the +# future). Should be a sane value between 0 and average number of connections, +# and depend on the amount of spare RAM. +# MaxStandbyConThreads: 8 +# +# Hard limit of active thread count for incoming connections, i.e. operation +# is refused when this value is reached (below zero = unlimited). +# MaxConThreads: -1 +# +# Pigeonholing files with regular expressions (static/volatile). Can be +# overriden here but not should not be done permanently because future update +# of default settings would not be applied later. +# VfilePattern = (^|.*?/)(Index|Packages(\.gz|\.bz2|\.lzma|\.xz)?|InRelease|Release|Release\.gpg|Sources(\.gz|\.bz2|\.lzma|\.xz)?|release|index\.db-.*\.gz|Contents-[^/]*(\.gz|\.bz2|\.lzma|\.xz)?|pkglist[^/]*\.bz2|rclist[^/]*\.bz2|/meta-release[^/]*|Translation[^/]*(\.gz|\.bz2|\.lzma|\.xz)?|MD5SUMS|SHA1SUMS|((setup|setup-legacy)(\.ini|\.bz2|\.hint)(\.sig)?)|mirrors\.lst|repo(index|md)\.xml(\.asc|\.key)?|directory\.yast|products|content(\.asc|\.key)?|media|filelists\.xml\.gz|filelists\.sqlite\.bz2|repomd\.xml|packages\.[a-zA-Z][a-zA-Z]\.gz|info\.txt|license\.tar\.gz|license\.zip|.*\.db(\.tar\.gz)?|.*\.files\.tar\.gz|.*\.abs\.tar\.gz|metalink\?repo|.*prestodelta\.xml\.gz)$|/dists/.*/installer-[^/]+/[^0-9][^/]+/images/.* +# PfilePattern = .*(\.d?deb|\.rpm|\.dsc|\.tar(\.gz|\.bz2|\.lzma|\.xz)(\.gpg)?|\.diff(\.gz|\.bz2|\.lzma|\.xz)|\.jigdo|\.template|changelog|copyright|\.udeb|\.debdelta|\.diff/.*\.gz|(Devel)?ReleaseAnnouncement(\?.*)?|[a-f0-9]+-(susedata|updateinfo|primary|deltainfo).xml.gz|fonts/(final/)?[a-z]+32.exe(\?download.*)?|/dists/.*/installer-[^/]+/[0-9][^/]+/images/.*)$ +# Whitelist for expiration, file types not to be removed even when being +# unreferenced. Default: many parts from VfilePattern where no parent index +# exists or might be unknown. +# WfilePattern = (^|.*?/)(Release|InRelease|Release\.gpg|(Packages|Sources)(\.gz|\.bz2|\.lzma|\.xz)?|Translation[^/]*(\.gz|\.bz2|\.lzma|\.xz)?|MD5SUMS|SHA1SUMS|.*\.xml|.*\.db\.tar\.gz|.*\.files\.tar\.gz|.*\.abs\.tar\.gz|[a-z]+32.exe)$|/dists/.*/installer-.*/images/.* + +# Higher modes only working with the debug version +# Warning, writes a lot into apt-cacher.err logfile +# Value overwrites UnbufferLogs setting (aliased) +# Debug:3 + +# Usually, general purpose proxies like Squid expose the IP address of the +# client user to the remote server using the X-Forwarded-For HTTP header. This +# behaviour can be optionally turned on with the Expose-Origin option. +# ExposeOrigin: 0 + +# When logging the originating IP address, trust the information supplied by +# the client in the X-Forwarded-For header. +# LogSubmittedOrigin: 0 + +# The version string reported to the peer, to be displayed as HTTP client (and +# version) in the logs of the mirror. +# WARNING: some archives use this header to detect/guess capabilities of the +# client (i.e. redirection support) and change the behaviour accordingly, while +# ACNG might not support the expected features. Expect side effects. +# +# UserAgent: Yet Another HTTP Client/1.2.3p4 + +# In some cases the Import and Expiration tasks might create fresh volatile +# data for internal use by reconstructing them using patch files. This +# by-product might be recompressed with bzip2 and with some luck the resulting +# file becomes identical to the *.bz2 file on the server, usable for APT +# clients trying to fetch the full .bz2 compressed version. Injection of the +# generated files into the cache has however a disadvantage on underpowered +# servers: bzip2 compression can create high load on the server system and the +# visible download of the busy .bz2 files also becomes slower. +# +# RecompBz2: 0 + +# Network timeout for outgoing connections. +# NetworkTimeout: 60 + +# Sometimes it makes sense to not store the data in cache and just return the +# package data to client as it comes in. DontCache parameters can enable this +# behaviour for certain URL types. The tokens are extended regular expressions +# that URLs are matched against. +# +# DontCacheRequested is applied to the URL as it comes in from the client. +# Example: exclude packages built with kernel-package for x86 +# DontCacheRequested: linux-.*_10\...\.Custo._i386 +# Example usecase: exclude popular private IP ranges from caching +# DontCacheRequested: 192.168.0 ^10\..* 172.30 +# +# DontCacheResolved is applied to URLs after mapping to the target server. If +# multiple backend servers are specified then it's only matched against the +# download link for the FIRST possible source (due to implementation limits). +# Example usecase: all Ubuntu stuff comes from a local mirror (specified as +# backend), don't cache it again: +# DontCacheResolved: ubuntumirror.local.net +# +# DontCache directive sets (overrides) both, DontCacheResolved and +# DontCacheRequested. Provided for convenience, see those directives for +# details. +# +# Default permission set of freshly created files and directories, as octal +# numbers (see chmod(1) for details). +# Can by limited by the umask value (see umask(2) for details) if it's set in +# the environment of the starting shell, e.g. in apt-cacher-ng init script or +# in its configuration file. +# DirPerms: 00755 +# FilePerms: 00664 +# +# +# It's possible to use use apt-cacher-ng as a regular web server with limited +# feature set, i.e. +# including directory browsing and download of any file; +# excluding sorting, mime types/encodings, CGI execution, index page +# redirection and other funny things. +# To get this behavior, mappings between virtual directories and real +# directories on the server must be defined with the LocalDirs directive. +# Virtual and real dirs are separated by spaces, multiple pairs are separated +# by semi-colons. Real directories must be absolute paths. +# NOTE: Since the names of that key directories share the same namespace as +# repository names (see Remap-...) it's administrators job to avoid such +# collisions on them (unless created deliberately). +# +# LocalDirs: woo /data/debarchive/woody ; hamm /data/debarchive/hamm + +# Precache a set of files referenced by specified index files. This can be used +# to create a partial mirror usable for offline work. There are certain limits +# and restrictions on the path specification, see manual for details. A list of +# (maybe) relevant index files could be retrieved via +# "apt-get --print-uris update" on a client machine. +# +# PrecacheFor: debrep/dists/unstable/*/source/Sources* debrep/dists/unstable/*/binary-amd64/Packages* + +# Arbitrary set of data to append to request headers sent over the wire. Should +# be a well formated HTTP headers part including newlines (DOS style) which +# can be entered as escape sequences (\r\n). +# RequestAppendix: X-Tracking-Choice: do-not-track\r\n + +# Specifies the IP protocol families to use for remote connections. Order does +# matter, first specified are considered first. Possible combinations: +# v6 v4 +# v4 v6 +# v6 +# v4 +# (empty or not set: use system default) +# +# ConnectProto: v6 v4 + +# Regular expiration algorithm finds package files which are no longer listed +# in any index file and removes them of them after a safety period. +# This option allows to keep more versions of a package in the cache after +# safety period is over. +# KeepExtraVersions: 1 + +# Optionally uses TCP access control provided by libwrap, see hosts_access(5) +# for details. Daemon name is apt-cacher-ng. Default if not set: decided on +# startup by looking for explicit mentioning of apt-cacher-ng in +# /etc/hosts.allow or /etc/hosts.deny files. +# UseWrap: 0 + +# If many machines from the same local network attempt to update index files +# (apt-get update) at nearly the same time, the known state of these index file +# is temporarily frozen and multiple requests receive the cached response +# without contacting the server. This parameter (in seconds) specifies the +# length of this period before the files are considered outdated. +# Setting it too low transfers more data and increases remote server load, +# setting it too high (more than a couple of minutes) increases the risk of +# delivering inconsistent responses to the clients. +# FreshIndexMaxAge: 27 + +# Usually the users are not allowed to specify custom TCP ports of remote +# mirrors in the requests, only the default HTTP port can be used (instead, +# proxy administrator can create Remap- rules with custom ports). This +# restriction can be disabled by specifying a list of allowed ports or 0 for +# any port. +# +# AllowUserPorts: 80 + +# Normally the HTTP redirection responses are forwarded to the original caller +# (i.e. APT) which starts a new download attempt from the new URL. This +# solution is ok for client configurations with proxy mode but doesn't work +# well with configurations using URL prefixes. To work around this the server +# can restart its own download with another URL. However, this might be used to +# circumvent download source policies by malicious users. +# The RedirMax option specifies how many such redirects the server should +# follow per request, 0 disables the internal redirection. If not set, +# default value is 0 if ForceManaged is used and 5 otherwise. +# +# RedirMax: 5 diff --git a/cookbooks/ark/CHANGELOG.md b/cookbooks/ark/CHANGELOG.md new file mode 100644 index 0000000..98539d4 --- /dev/null +++ b/cookbooks/ark/CHANGELOG.md @@ -0,0 +1,135 @@ +ark Cookbook CHANGELOG +====================== +This file is used to list changes made in each version of the ark cookbook. + + +v0.9.0 (2014-06-06) +------------------- +* [COOK-3642] Add Windows support + + +v0.8.2 (2014-04-23) +------------------- +- [COOK-4514] - Support for SLES with the Ark cookbook + + +v0.8.0 (2014-04-10) +------------------- +- [COOK-2771] - Add support for XZ compression + + +v0.7.2 (2014-03-28) +------------------- +- [COOK-4477] - Fix failing test suite +- [COOK-4484] - Replace strip_leading_dir attribute with more general strip_components + + +v0.7.0 (2014-03-18) +------------------- +- [COOK-4437] - configure and install_with_make should chown after unpack + + +v0.6.0 (2014-02-27) +------------------- +[COOK-3786] - Unable to install multiple versions of archive without duplication + + +v0.5.0 (2014-02-21) +------------------- +### Bug +- **[COOK-4288](https://tickets.opscode.com/browse/COOK-4288)** - Cleanup the Kitchen + +### Improvement +- **[COOK-4264](https://tickets.opscode.com/browse/COOK-4264)** - Add node['ark']['package_dependencies'] to allow tuning packages. + + +v0.4.2 +------ +### Improvement +- **[COOK-3854](https://tickets.opscode.com/browse/COOK-3854)** - Capability with mac_os_x: '/bin/chown' - No such file or directory +- Cleaning up some style for rubucop +- Updating test harness + + +v0.4.0 +------ +### Improvement +- **[COOK-3539](https://tickets.opscode.com/browse/COOK-3539)** - Allow dumping of bz2 and gzip files + +v0.3.2 +------ +### Bug +- **[COOK-3191](https://tickets.opscode.com/browse/COOK-3191)** - Propogate unzip failures +- **[COOK-3118](https://tickets.opscode.com/browse/COOK-3118)** - Set cookbook attribute in provider +- **[COOK-3055](https://tickets.opscode.com/browse/COOK-3055)** - Use proper scope in helper module +- **[COOK-3054](https://tickets.opscode.com/browse/COOK-3054)** - Fix notification resource updating + +### Improvement +- **[COOK-3179](https://tickets.opscode.com/browse/COOK-3179)** - README updates and refactor + +v0.3.0 +------ +### Improvement + +- [COOK-3087]: Can't use ark with chef < 11 + +### Bug + +- [COOK-3064]: `only_if` statements in ark's `install_with_make` and configure actions are not testing for file existence correctly. +- [COOK-3067]: ark kitchen test for `cherry_pick` is expecting the binary to be in the same parent folder as in the archive. + +v0.2.4 +------ +### Bug + +- [COOK-3048]: Ark provider contains a `ruby_block` resource without a block attribute +- [COOK-3063]: Ark cookbook `cherry_pick` action's unzip command does not close if statement +- [COOK-3065]: Ark install action does not symlink binaries correctly + +v0.2.2 +------ +- Update the README to reflect the requirement for Chef 11 to use the ark resource (`use_inline_resources`). +- Making this a release so it will also appear on the community site page. + +v0.2.0 +------ +### Bug + +- [COOK-2772]: Ark cookbook has foodcritic failures in provides/default.rb + +### Improvement + +- [COOK-2520]: Refactor ark providers to use the '`use_inline_resources`' LWRP DSL feature + +v0.1.0 +------ +- [COOK-2335] - ark resource broken on Chef 11 + +v0.0.1 +------ +- [COOK-2026] - Allow `cherry_pick` action to be used for directories as well as files + +v0.0.1 +------ +- [COOK-1593] - README formatting updates for better display on Community Site + +v0.0.1 +------ +### Bug +- dangling "unless" + +### Improvement +- add `setup_py_*` actions +- add vagrantfile +- add foodcritic test +- travis.ci support + +v0.0.10 (May 23, 2012 +------ +### Bug +- `strip_leading_dir` not working for zip files https://github.com/bryanwb/chef-ark/issues/19 + +### Improvement +- use autogen.sh to generate configure script for configure action https://github.com/bryanwb/chef-ark/issues/16 +- support more file extensions https://github.com/bryanwb/chef-ark/pull/18 +- add extension attribute which allows you to download files which do not have the file extension as part of the URL diff --git a/cookbooks/ark/README.md b/cookbooks/ark/README.md new file mode 100644 index 0000000..1aba35b --- /dev/null +++ b/cookbooks/ark/README.md @@ -0,0 +1,305 @@ +# chef-ark [![Build Status](https://secure.travis-ci.org/opscode-cookbooks/ark.png?branch=master)](https://travis-ci.org/opscode-cookbooks/ark) + +Overview +======== + +This cookbook provides `ark`, a resource for managing software +archives. It manages the fetch-unpack-configure-build-install process +common to installing software from source, or from binary +distributions that are not fully fledged OS packages. + +This is a modified verion of Infochimp's awesome +[install_from cookbook](http://github.com/infochimps-cookbooks/install_from). +It has been heavily refactored and extended to meet different use +cases. + +Given a simple project archive available at a url: + + ark 'pig' do + url 'http://apache.org/pig/pig-0.8.0.tar.gz' + end + +The provider will: + +* fetch it to to `/var/cache/chef/` +* unpack it to the default path (`/usr/local/pig-0.8.0`) +* create a symlink for `:home_dir` (`/usr/local/pig`) pointing to path +* add specified binary commands to the enviroment `PATH` variable + +By default, the ark will not run again if the `:path` is not empty. +Ark provides many actions to accommodate different use cases, such as +`:dump`, `:cherry_pick`, `:put`, and `:install_with_make`. + +At this time ark only handles files available from URLs. It does not +handle local files. + +Requirements +============ + +This cookbook requires Chef 11 for the provider, as it uses the +`use_inline_resources` method. + +More about +[use_inline_resources](http://docs.opscode.com/lwrp_common_inline_compile.html) +in the Chef documentation. + +Should work on common Unix/Linux systems with typical userland +utilities like tar, gzip, etc. May require the installation of build +tools for compiling from source, but that installation is outside the +scope of this cookbook. + +Attributes +========== + +Customize the attributes to suit site specific conventions and +defaults. + +* `node['ark']['apache_mirror']` - if the URL is an apache mirror, + use the attribute as the default. +* `node['ark']['prefix_root']` - default base location if the + `prefix_root` is not passed into the resource. +* `node['ark']['prefix_bin']` - default binary location if the + `prefix_bin` is not passed into the resource. +* `node['ark']['prefix_home']` - default home location if the + `prefix_home` is not passed into the resource. +* `node['ark']['win_install_dir']` - directory where the files will + be put on windows +* `node['ark']['package_dependencies']` - prerequisite system + packages that need to be installed to support ark. + +Resources/Providers +=================== + +* `ark` - does the extract/build/configure dance + +Actions +------- + +- `:install`: extracts the file and creates a 'friendly' symbolic link + to the extracted directory path +- `:configure`: configure ahead of the install action +- `:install_with_make`: extracts the archive to a path, runs `make`, + and `make install`. It does _not_ run the configure step at this + time +- `:dump`: strips all directories from the archive and dumps the + contained files into a specified path +- `:cherry_pick`: extract a specified file from an archive and places + in specified path +- `:put`: extract the archive to a specified path, does not create any + symbolic links +- `:remove`: removes the extracted directory and related symlink #TODO +- `:setup_py_build`: runs the command "python setup.py build" in the + extracted directory +- `:setup_py_install`: runs the comand "python setup.py install" in + the extracted directory + +## :cherry_pick + +Extract a specified file from an archive and places in specified path. + +### Relevant Attribute Parameters for :cherry_pick + +- `path`: directory to place file in. +- `creates`: specific file to cherry-pick. + +## :dump + +Strips all directories from the archive and dumps the contained files +into a specified path. + +NOTE: This currently only works for zip archives + +### Attribute Parameters for :dump + +- `path`: path to dump files to. +- `mode`: file mode for `app_home`, as an integer. + - Example: `0775` +- `creates`: if you are appending files to a given directory, ark + needs a condition to test whether the file has already been + extracted. You can specify with creates, a file whose existence + indicates the ark has previously been extracted and does not need to + be extracted again. + +## :put + +Extract the archive to a specified path, does not create any symbolic +links. + +### Attribute Parameters for :put + +- `path`: path to extract to. + - Default: `/usr/local` +- `has_binaries`: array of binary commands to symlink into + `/usr/local/bin/`, you must specify the relative path. + - Example: `[ 'bin/java', 'bin/javaws' ]` +- `append_env_path`: boolean, if true, append the `./bin` directory of + the extracted directory to the global `PATH` variable for all users. + +Attribute Parameters +-------------------- + +- `name`: name of the package, defaults to the resource name. +- `url`: url for tarball, `.tar.gz`, `.bin` (oracle-specific), `.war`, + and `.zip` currently supported. Also supports special syntax + `:name:version:apache_mirror:` that will auto-magically construct + download url from the apache mirrors site. +- `version`: software version, defaults to `1`. +- `checksum`: sha256 checksum, used for security . +- `mode`: file mode for `app_home`, is an integer. +- `prefix_root`: default `prefix_root`, for use with `:install*` + actions. +- `prefix_home`: default directory prefix for a friendly symlink to + the path. + - Example: `/usr/local/maven` -> `/usr/local/maven-2.2.1` +- `prefix_bin`: default directory to place a symlink to a binary + command. + - Example: `/opt/bin/mvn` -> `/opt/maven-2.2.1/bin/mvn`, where the + `prefix_bin` is `/opt/bin` +- `path`: path to extract the ark to. The `:install*` actions + overwrite any user-provided values for `:path`. + - Default: `/usr/local/-` for the `:install`, + `:install_with_make` actions +- `home_dir`: symbolic link to the path `:prefix_root/:name-:version`, + does not apply to `:dump`, `:put`, or `:cherry_pick` actions. + - Default: `:prefix_root/:name` +- `has_binaries`: array of binary commands to symlink into + `/usr/local/bin/`, you must specify the relative path. + - Example: `[ 'bin/java', 'bin/javaws' ]` +- `append_env_path`: boolean, similar to `has_binaries` but less + granular. If true, append the `./bin` directory of the extracted + directory to. the `PATH` environment variable for all users, by + placing a file in `/etc/profile.d/`. The commands are symbolically + linked into `/usr/bin/*`. This option provides more granularity than + the boolean option. + - Example: `mvn`, `java`, `javac`, etc. +- `environment`: hash of environment variables to pass to invoked + shell commands like `tar`, `unzip`, `configure`, and `make`. +- `strip_components`: number of components in path to strip when extracting archive. + With default value of `1`, ark strips the leading directory from an archive, + which is the default for both `unzip` and `tar` commands. +- `autoconf_opts`: an array of command line options for use with the + GNU `autoconf` script. + - Example: `[ '--include=/opt/local/include', '--force' ]` +- `make_opts`: an array of command line options for use with `make`. + - Example: `[ '--warn-undefined-variables', '--load-average=2' ]` +- `owner`: owner of extracted directory. + - Default: `root` + +### Examples + +This example copies `ivy.tar.gz` to +`/var/cache/chef/ivy-2.2.0.tar.gz`, unpacks its contents to +`/usr/local/ivy-2.2.0/` -- stripping the leading directory, and +symlinks `/usr/local/ivy` to `/usr/local/ivy-2.2.0` + + # install Apache Ivy dependency resolution tool + ark "ivy" do + url 'http://someurl.example.com/ivy.tar.gz' + version '2.2.0' + checksum '89ba5fde0c596db388c3bbd265b63007a9cc3df3a8e6d79a46780c1a39408cb5' + action :install + end + +This example copies `jdk-7u2-linux-x64.tar.gz` to +`/var/cache/chef/jdk-7.2.tar.gz`, unpacks its contents to +`/usr/local/jvm/jdk-7.2/` -- stripping the leading directory, symlinks +`/usr/local/jvm/default` to `/usr/local/jvm/jdk-7.2`, and adds +`/usr/local/jvm/jdk-7.2/bin/` to the global `PATH` for all users. The +user 'foobar' is the owner of the `/usr/local/jvm/jdk-7.2` directory: + + ark 'jdk' do + url 'http://download.example.com/jdk-7u2-linux-x64.tar.gz' + version '7.2' + path "/usr/local/jvm/" + home_dir "/usr/local/jvm/default" + checksum '89ba5fde0c596db388c3bbd265b63007a9cc3df3a8e6d79a46780c1a39408cb5' + append_env_path true + owner 'foobar' + end + +Install Apache Ivy dependency resolution tool in /resource_name in this case +`/usr/local/ivy`, do not symlink, and strip any leading directory if one +exists in the tarball: + + ark "ivy" do + url 'http://someurl.example.com/ivy.tar.gz' + checksum '89ba5fde0c596db388c3bbd265b63007a9cc3df3a8e6d79a46780c1a39408cb5' + action :put + end + +Install Apache Ivy dependency resolution tool in /home/foobar/ivy, strip any +leading directory if one exists: + + ark "ivy" do + path "/home/foobar + url 'http://someurl.example.com/ivy.tar.gz' + checksum '89ba5fde0c596db388c3bbd265b63007a9cc3df3a8e6d79a46780c1a39408cb5' + action :put + end + + Strip all directories and dump files into path specified by the path attribute. + You must specify the `creates` attribute in order to keep the extraction from + running every time. The directory path will be created if it doesn't already exist: + + ark "my_jars" do + url "http://example.com/bunch_of_jars.zip" + path "/usr/local/tomcat/lib" + creates "mysql.jar" + owner "tomcat" + action :dump + end + +Extract specific files from a tarball (currently only handles one named file): + + ark 'mysql-connector-java' do + url 'http://oracle.com/mysql-connector.zip' + creates 'mysql-connector-java-5.0.8-bin.jar' + path '/usr/local/tomcat/lib' + action :cherry_pick + end + +Build and install haproxy and use alternave values for `prefix_root`, `prefix_home`, and `prefix_bin`: + + ark "haproxy" do + url "http://haproxy.1wt.eu/download/1.5/src/snapshot/haproxy-ss-20120403.tar.gz" + version "1.5" + checksum 'ba0424bf7d23b3a607ee24bbb855bb0ea347d7ffde0bec0cb12a89623cbaf911' + make_opts [ 'TARGET=linux26' ] + prefix_root '/opt' + prefix_home '/opt' + prefix_bin '/opt/bin' + action :install_with_make + end + +You can also pass multiple actions to ark and supply the file extension in case +the file extension can not be determined by the URL: + + ark "test_autogen" do + url 'https://github.com/zeromq/libzmq/tarball/master' + extension "tar.gz" + action [ :configure, :install_with_make ] + end + +License and Author +================== + +- Author: Philip (flip) Kromer - Infochimps, Inc() +- Author: Bryan W. Berry () +- Author: Denis Barishev () +- Author: Sean OMeara () +- Copyright: 2011, Philip (flip) Kromer - Infochimps, Inc +- Copyright: 2012, Bryan W. Berry +- Copyright: 2012, Denis Barishev +- Copyright: 2013, Opscode, Inc + +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/cookbooks/ark/attributes/default.rb b/cookbooks/ark/attributes/default.rb new file mode 100644 index 0000000..c86cd03 --- /dev/null +++ b/cookbooks/ark/attributes/default.rb @@ -0,0 +1,16 @@ +default['ark']['apache_mirror'] = 'http://apache.mirrors.tds.net' +default['ark']['prefix_root'] = '/usr/local' +default['ark']['prefix_bin'] = '/usr/local/bin' +default['ark']['prefix_home'] = '/usr/local' +if node['platform_family'] === 'windows' + default['ark']['tar'] = "\"#{default['7-zip']['home']}\\7z.exe\"" +else + default['ark']['tar'] = '/bin/tar' +end + +pkgs = %w(libtool autoconf) unless platform_family?('mac_os_x','windows') +pkgs += %w(unzip rsync make gcc) unless platform_family?('mac_os_x','windows') +pkgs += %w(autogen) unless platform_family?('rhel', 'fedora', 'mac_os_x', 'suse','windows') +pkgs += %w(gtar) if platform?('freebsd') + +default['ark']['package_dependencies'] = pkgs diff --git a/cookbooks/ark/files/default/foo.tar.gz b/cookbooks/ark/files/default/foo.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..16aa5a72262ee8ffdd8ed6523d796fc310148c7c GIT binary patch literal 219 zcmV<103`n(iwFQRj;2ol1MSu83W6{gfZ<(=S5V*P4wn-}N&Zm519bUKNDv_`a8mF- ze})Ix;B~WTno~Dijw&IELU7}#gOm9d zf512Ae=uWz<8{vYzlVAMYjcX1+t2ag{-5>FL(uI1JuLcHO?%t?Bly0*lX3sk9{>OV V000000000!_XJS<(X{|5005gLZx8?g literal 0 HcmV?d00001 diff --git a/cookbooks/ark/files/default/foo.tbz b/cookbooks/ark/files/default/foo.tbz new file mode 100644 index 0000000000000000000000000000000000000000..6f29777ec1e157f8fd531ae4e57e4eea364a525e GIT binary patch literal 163 zcmV;U09^k^00IC2fCwM}l!nwI zk7+|ek(1N_ijsP2Z4*Ngp^?2)S7V+vCP0BIDyMn&kkZ4GR&Aq$Btga;z~)@=Rc^W& z9hoCFq=`ZpWLt?bLhh1c7?LDc-ViZmMN-O|84n^=s9_ng2a?xUaiHuurs-qD1TZ`h R)aZzR7ji{7P>>s)4CiZ8KuiDt literal 0 HcmV?d00001 diff --git a/cookbooks/ark/files/default/foo.tgz b/cookbooks/ark/files/default/foo.tgz new file mode 100644 index 0000000000000000000000000000000000000000..f78dd037253c3c2350fd9ed480320bea6be31966 GIT binary patch literal 152 zcmb2|=3sbOxXYh``R&DnTuh1r4G$|t<}Yw7p7Z?X=}U513wf&kd=Yq~I*rfi;QvOB z#S1>1e_b`@;FiMe``pd%zRMT7y(A^vdS^zD=Vlh^zZ>5buD`6yVyXYtDAxY|ZR`KR z#jo#&*<4AN{p!Xar&5v1QL#Of5|vCpFx9x0RSFs BMTr0a literal 0 HcmV?d00001 diff --git a/cookbooks/ark/files/default/foo.txz b/cookbooks/ark/files/default/foo.txz new file mode 100644 index 0000000000000000000000000000000000000000..7f742da55c07756dff398f33fa25681310ea7fdf GIT binary patch literal 200 zcmV;(05|{rH+ooF000E$*0e?f03iVu0001VFXf})C;tG4T>vv1>|OxfG-0AA4iM3v zBfN0Wv4-Tm_tGCGrz781mq2|@ZlR!$J;&($Z%Ow~ zLcHt}C|L&dMaH^;GLMF>0002FsZM0o*@zV3HB>Ci$ZIiU-L}Ntt;NpIrpH#i$RD z&(KW~LNg^LKOX2ou-`ytfiTWcLN`ks)2!V5qEx~LGBU|A7Xe0_S{B;B|5fRA> ziAW6RAxy)JMwn>~3|ktVkW52FD$rC=q>^oF52~rq2qtVQdSLP}z}yP*`86a{A(0I< z5o_cie2;FT5ZpwN?`0SfVF@x37V)H;D~@7r2Bx`)M1ae1RyLsj7}$W2k%@s}C(sB6 F1^~sT!)^co literal 0 HcmV?d00001 diff --git a/cookbooks/ark/files/default/foo_sub.tar.gz b/cookbooks/ark/files/default/foo_sub.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..258d205f7711c7f2198f1471b184c3d02e12c4ef GIT binary patch literal 193 zcmV;y06za8iwFQ!yE0P%1MSmG3WG2dg<+SH6|^_zCRvXT#3EGi0bPF6T01DT;2?tZ z|0ZV=%#9~$`u<#GCL+;$^UScMl)B`Lv8A)>o!8)u*fLltjL$vT!#qyakVdJxwri_l z{xYJLGB6wOHVR&_-zd1Wf}4SnxCSH=1q^sP5v@B!~&NgiJuUp9jf=q|7`r-64Qv3eY!(dL||p1Hu?SVq}tI z#uXwG(6A9;cLtO4;WdkZ_U;#o0U@{Q^Vg?2P5ffmE literal 0 HcmV?d00001 diff --git a/cookbooks/ark/files/default/tests/minitest/default_test.rb b/cookbooks/ark/files/default/tests/minitest/default_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/cookbooks/ark/files/default/tests/minitest/support/helpers.rb b/cookbooks/ark/files/default/tests/minitest/support/helpers.rb new file mode 100644 index 0000000..e69de29 diff --git a/cookbooks/ark/files/default/tests/minitest/test_test.rb b/cookbooks/ark/files/default/tests/minitest/test_test.rb new file mode 100644 index 0000000..c46812d --- /dev/null +++ b/cookbooks/ark/files/default/tests/minitest/test_test.rb @@ -0,0 +1,102 @@ +require 'minitest/spec' + +describe_recipe 'ark::test' do + + # It's often convenient to load these includes in a separate + # helper along with + # your own helper methods, but here we just include them directly: + include MiniTest::Chef::Assertions + include MiniTest::Chef::Context + include MiniTest::Chef::Resources + + it 'installed the unzip package' do + package('unzip').must_be_installed + end + + if RUBY_PLATFORM =~ /freebsd/ + it 'installs the gnu tar package on freebsc' do + package('gtar').must_be_installed + end + end + + it 'puts an ark in the desired directory w/out symlinks' do + directory('/usr/local/test_put').must_exist + end + + it 'dumps the correct files into place with correct owner and group' do + file('/usr/local/foo_dump/foo1.txt').must_have(:owner, 'foobarbaz').and(:group, 'foobarbaz') + end + + it 'cherrypicks the mysql connector and set the correct owner and group' do + file('/usr/local/foo_cherry_pick/foo1.txt').must_have(:owner, 'foobarbaz').and(:group, 'foobarbaz') + end + + it 'cherrypicks the file from a zip' do + file('/usr/local/foo_cherry_pick_from_zip/foo1.txt').must_exist + end + + it 'creates directory and symlink properly for the full ark install' do + directory('/usr/local/foo-2').must_have(:owner, 'foobarbaz').and(:group, 'foobarbaz') + link('/usr/local/foo').must_exist.with(:link_type, :symbolic).and(:to, '/usr/local/foo-2') + end + + it 'symlinks multiple binary commands' do + link('/usr/local/bin/do_foo').must_exist.with(:link_type, :symbolic).and(:to, '/usr/local/foo-2/bin/do_foo') + link('/usr/local/bin/do_more_foo').must_exist.with(:link_type, :symbolic).and(:to, '/usr/local/foo-2/bin/do_more_foo') + end + + it 'appends to the environment PATH' do + unless RUBY_PLATFORM =~ /freebsd/ + file('/etc/profile.d/foo_append_env.sh').must_include '/usr/local/foo_append_env-7.0.26/bin' + + bin_path_present = !ENV['PATH'].scan('/usr/local/foo_append_env-7.0.26/bin').empty? + assert bin_path_present + end + end + + it 'doesnt strip top-level directory if specified' do + directory('/usr/local/foo_dont_strip/foo_sub').must_exist + end + + it 'does strip for zip file' do + file('/usr/local/foo_zip_strip/foo1.txt').must_exist + end + + it 'successfully compiles haproxy' do + file('/usr/local/haproxy-1.5/haproxy').must_exist + end + + unless RUBY_PLATFORM =~ /freebsd/ + it 'installs haproxy binary' do + file('/usr/local/sbin/haproxy').must_exist + directory('/usr/local/doc/haproxy').must_exist + end + end + + it 'creates an alternate prefix_bin' do + link('/opt/bin/do_foo').must_exist.with(:link_type, :symbolic).and(:to, '/opt/foo_alt_bin-3/bin/do_foo') + end + + it 'properly unpacks .tbz and .tgz archives' do + file('/usr/local/foo_tbz/foo1.txt').must_exist + file('/usr/local/foo_tgz/foo1.txt').must_exist + end + + it 'sends notification when resource updated' do + file('/tmp/foobarbaz/notification_successful.txt').must_exist + end + + it 'uses autogen.sh to generate configure script' do + file('/usr/local/test_autogen-1/configure').must_exist + end + + it 'strips 2 components out of foo_sub.tar.gz archive path' do + directory('/usr/local/foo_sub-1/bin').must_exist + file('/usr/local/foo_sub-1/foo1.txt').must_exist + end + + it 'strips 2 components out of foo_sub.zip archive path' do + directory('/usr/local/foo_sub-2/bin').must_exist + file('/usr/local/foo_sub-2/foo1.txt').must_exist + end +end diff --git a/cookbooks/ark/libraries/default.rb b/cookbooks/ark/libraries/default.rb new file mode 100644 index 0000000..4ee688c --- /dev/null +++ b/cookbooks/ark/libraries/default.rb @@ -0,0 +1,234 @@ +# libs + +module Opscode + module Ark + module ProviderHelpers + private + + def unpack_type + case parse_file_extension + when /tar.gz|tgz/ then "tar_xzf" + when /tar.bz2|tbz/ then "tar_xjf" + when /tar.xz|txz/ then "tar_xJf" + when /zip|war|jar/ then "unzip" + else fail "Don't know how to expand #{new_resource.url}" + end + end + + def parse_file_extension + if new_resource.extension.nil? + # purge any trailing redirect + url = new_resource.url.clone + url =~ %r{^https?:\/\/.*(.bin|bz2|gz|jar|tbz|tgz|txz|war|xz|zip)(\/.*\/)} + url.gsub!(Regexp.last_match(2), '') unless Regexp.last_match(2).nil? + # remove tailing query string + release_basename = ::File.basename(url.gsub(/\?.*\z/, '')).gsub(/-bin\b/, '') + # (\?.*)? accounts for a trailing querystring + Chef::Log.debug("DEBUG: release_basename is #{release_basename}") + release_basename =~ /^(.+?)\.(jar|tar\.bz2|tar\.gz|tar\.xz|tbz|tgz|txz|war|zip)(\?.*)?/ + Chef::Log.debug("DEBUG: file_extension is #{Regexp.last_match(2)}") + new_resource.extension = Regexp.last_match(2) + end + new_resource.extension + end + + def unpack_command + if node['platform_family'] === 'windows' + cmd = sevenzip_command + else + case unpack_type + when "tar_xzf" + cmd = tar_command("xzf") + when "tar_xjf" + cmd = tar_command("xjf") + when "tar_xJf" + cmd = tar_command("xJf") + when "unzip" + cmd = unzip_command + end + end + Chef::Log.debug("DEBUG: cmd: #{cmd}") + cmd + end + + def tar_command(tar_args) + cmd = node['ark']['tar'] + cmd += " #{tar_args} " + cmd += new_resource.release_file + cmd += tar_strip_args + cmd + end + + def unzip_command + if new_resource.strip_components > 0 + require 'tmpdir' + tmpdir = Dir.mktmpdir + strip_dir = '*/' * new_resource.strip_components + cmd = "unzip -q -u -o #{new_resource.release_file} -d #{tmpdir}" + cmd += " && rsync -a #{tmpdir}/#{strip_dir} #{new_resource.path}" + cmd += " && rm -rf #{tmpdir}" + cmd + else + "unzip -q -u -o #{new_resource.release_file} -d #{new_resource.path}" + end + end + + def sevenzip_command + if new_resource.strip_components > 0 + require 'tmpdir' + tmpdir = Dir.mktmpdir + cmd = sevenzip_command_builder(tmpdir,'e') + cmd += " && " + currdir = tmpdir + var = 0 + while var < new_resource.strip_components do + var += 1 + cmd += "for /f %#{var} in ('dir /ad /b \"#{currdir.gsub! '/', '\\'}\"') do " + currdir += "\\%#{var}" + end + cmd += "xcopy \"#{currdir}\" \"#{new_resource.home_dir}\" /s /e" + else + cmd = sevenzip_command_builder(new_resource.path,'x') + end + cmd + end + + def sevenzip_command_builder(dir, command) + cmd = "#{node['ark']['tar']} #{command} \""; + cmd += new_resource.release_file + cmd += "\" " + case parse_file_extension + when /tar.gz|tgz|tar.bz2|tbz|tar.xz|txz/ + cmd += " -so | #{node['ark']['tar']} x -aoa -si -ttar" + end + cmd += " -o\"#{dir}\" -uy" + cmd + end + + def dump_command + if node['platform_family'] === 'windows' + cmd = sevenzip_command_builder(new_resource.path,'e') + else + case unpack_type + when "tar_xzf", "tar_xjf", "tar_xJf" + cmd = "tar -mxf \"#{new_resource.release_file}\" -C \"#{new_resource.path}\"" + when "unzip" + cmd = "unzip -j -q -u -o \"#{new_resource.release_file}\" -d \"#{new_resource.path}\"" + end + end + Chef::Log.debug("DEBUG: cmd: #{cmd}") + cmd + end + + def cherry_pick_command + if node['platform_family'] === 'windows' + cmd = sevenzip_command_builder(new_resource.path,'e') + cmd += " -r #{new_resource.creates}" + else + case unpack_type + when "tar_xzf" + cmd = cherry_pick_tar_command("xzf") + when "tar_xjf" + cmd = cherry_pick_tar_command("xjf") + when "tar_xJf" + cmd = cherry_pick_tar_command("xJf") + when "unzip" + cmd = "unzip -t #{new_resource.release_file} \"*/#{new_resource.creates}\" ; stat=$? ;" + cmd += "if [ $stat -eq 11 ] ; then " + cmd += "unzip -j -o #{new_resource.release_file} \"#{new_resource.creates}\" -d #{new_resource.path} ;" + cmd += "elif [ $stat -ne 0 ] ; then false ;" + cmd += "else " + cmd += "unzip -j -o #{new_resource.release_file} \"*/#{new_resource.creates}\" -d #{new_resource.path} ;" + cmd += "fi" + end + end + Chef::Log.debug("DEBUG: cmd: #{cmd}") + cmd + end + + def cherry_pick_tar_command(tar_args) + cmd = node['ark']['tar'] + cmd += " #{tar_args}" + cmd += " #{new_resource.release_file}" + cmd += " -C" + cmd += " #{new_resource.path}" + cmd += " #{new_resource.creates}" + cmd += tar_strip_args + cmd + end + + def set_paths + release_ext = parse_file_extension + prefix_bin = new_resource.prefix_bin.nil? ? new_resource.run_context.node['ark']['prefix_bin'] : new_resource.prefix_bin + prefix_root = new_resource.prefix_root.nil? ? new_resource.run_context.node['ark']['prefix_root'] : new_resource.prefix_root + if new_resource.prefix_home.nil? + default_home_dir = ::File.join(new_resource.run_context.node['ark']['prefix_home'], new_resource.name) + else + default_home_dir = ::File.join(new_resource.prefix_home, new_resource.name) + end + # set effective paths + new_resource.prefix_bin = prefix_bin + new_resource.version ||= "1" # initialize to one if nil + new_resource.home_dir ||= default_home_dir + if node['platform_family'] === 'windows' + new_resource.path = new_resource.win_install_dir + else + new_resource.path = ::File.join(prefix_root, "#{new_resource.name}-#{new_resource.version}") + end + Chef::Log.debug("path is #{new_resource.path}") + new_resource.release_file = ::File.join(Chef::Config[:file_cache_path], "#{new_resource.name}-#{new_resource.version}.#{release_ext}") + end + + def set_put_paths + release_ext = parse_file_extension + path = new_resource.path.nil? ? new_resource.run_context.node['ark']['prefix_root'] : new_resource.path + new_resource.path = ::File.join(path, new_resource.name) + Chef::Log.debug("DEBUG: path is #{new_resource.path}") + new_resource.release_file = ::File.join(Chef::Config[:file_cache_path], "#{new_resource.name}.#{release_ext}") + end + + def set_dump_paths + release_ext = parse_file_extension + new_resource.release_file = ::File.join(Chef::Config[:file_cache_path], "#{new_resource.name}.#{release_ext}") + end + + def tar_strip_args + new_resource.strip_components > 0 ? " --strip-components=#{new_resource.strip_components}" : "" + end + + def show_deprecations + if [true, false].include?(new_resource.strip_leading_dir) + Chef::Log.warn("DEPRECATED: strip_leading_dir attribute was deprecated. Use strip_components instead.") + end + end + + def owner_command + if node['platform_family'] === 'windows' + cmd = "icacls #{new_resource.path}\\* /setowner #{new_resource.owner}" + else + cmd = "chown -R #{new_resource.owner}:#{new_resource.group} #{new_resource.path}" + end + cmd + end + + # def unpacked?(path) + # if new_resource.creates + # full_path = ::File.join(new_resource.path, new_resource.creates) + # else + # full_path = path + # end + # if ::File.directory? full_path + # if ::File.stat(full_path).nlink == 2 + # false + # else + # true + # end + # elsif ::File.exists? full_path + # true + # else + # false + # end + # end + end + end +end diff --git a/cookbooks/ark/metadata.json b/cookbooks/ark/metadata.json new file mode 100644 index 0000000..4247a31 --- /dev/null +++ b/cookbooks/ark/metadata.json @@ -0,0 +1,38 @@ +{ + "name": "ark", + "version": "0.9.0", + "description": "Installs/Configures ark", + "long_description": "# chef-ark [![Build Status](https://secure.travis-ci.org/opscode-cookbooks/ark.png?branch=master)](https://travis-ci.org/opscode-cookbooks/ark)\n\nOverview\n========\n\nThis cookbook provides `ark`, a resource for managing software\narchives. It manages the fetch-unpack-configure-build-install process\ncommon to installing software from source, or from binary\ndistributions that are not fully fledged OS packages.\n\nThis is a modified verion of Infochimp's awesome\n[install_from cookbook](http://github.com/infochimps-cookbooks/install_from).\nIt has been heavily refactored and extended to meet different use\ncases.\n\nGiven a simple project archive available at a url:\n\n ark 'pig' do\n url 'http://apache.org/pig/pig-0.8.0.tar.gz'\n end\n\nThe provider will:\n\n* fetch it to to `/var/cache/chef/`\n* unpack it to the default path (`/usr/local/pig-0.8.0`)\n* create a symlink for `:home_dir` (`/usr/local/pig`) pointing to path\n* add specified binary commands to the enviroment `PATH` variable\n\nBy default, the ark will not run again if the `:path` is not empty.\nArk provides many actions to accommodate different use cases, such as\n`:dump`, `:cherry_pick`, `:put`, and `:install_with_make`.\n\nAt this time ark only handles files available from URLs. It does not\nhandle local files.\n\nRequirements\n============\n\nThis cookbook requires Chef 11 for the provider, as it uses the\n`use_inline_resources` method.\n\nMore about\n[use_inline_resources](http://docs.opscode.com/lwrp_common_inline_compile.html)\nin the Chef documentation.\n\nShould work on common Unix/Linux systems with typical userland\nutilities like tar, gzip, etc. May require the installation of build\ntools for compiling from source, but that installation is outside the\nscope of this cookbook.\n\nAttributes\n==========\n\nCustomize the attributes to suit site specific conventions and\ndefaults.\n\n* `node['ark']['apache_mirror']` - if the URL is an apache mirror,\n use the attribute as the default.\n* `node['ark']['prefix_root']` - default base location if the\n `prefix_root` is not passed into the resource.\n* `node['ark']['prefix_bin']` - default binary location if the\n `prefix_bin` is not passed into the resource.\n* `node['ark']['prefix_home']` - default home location if the\n `prefix_home` is not passed into the resource.\n* `node['ark']['win_install_dir']` - directory where the files will\n be put on windows\n* `node['ark']['package_dependencies']` - prerequisite system\n packages that need to be installed to support ark.\n\nResources/Providers\n===================\n\n* `ark` - does the extract/build/configure dance\n\nActions\n-------\n\n- `:install`: extracts the file and creates a 'friendly' symbolic link\n to the extracted directory path\n- `:configure`: configure ahead of the install action\n- `:install_with_make`: extracts the archive to a path, runs `make`,\n and `make install`. It does _not_ run the configure step at this\n time\n- `:dump`: strips all directories from the archive and dumps the\n contained files into a specified path\n- `:cherry_pick`: extract a specified file from an archive and places\n in specified path\n- `:put`: extract the archive to a specified path, does not create any\n symbolic links\n- `:remove`: removes the extracted directory and related symlink #TODO\n- `:setup_py_build`: runs the command \"python setup.py build\" in the\n extracted directory\n- `:setup_py_install`: runs the comand \"python setup.py install\" in\n the extracted directory\n\n## :cherry_pick\n\nExtract a specified file from an archive and places in specified path.\n\n### Relevant Attribute Parameters for :cherry_pick\n\n- `path`: directory to place file in.\n- `creates`: specific file to cherry-pick.\n\n## :dump\n\nStrips all directories from the archive and dumps the contained files\ninto a specified path.\n\nNOTE: This currently only works for zip archives\n\n### Attribute Parameters for :dump\n\n- `path`: path to dump files to.\n- `mode`: file mode for `app_home`, as an integer.\n - Example: `0775`\n- `creates`: if you are appending files to a given directory, ark\n needs a condition to test whether the file has already been\n extracted. You can specify with creates, a file whose existence\n indicates the ark has previously been extracted and does not need to\n be extracted again.\n\n## :put\n\nExtract the archive to a specified path, does not create any symbolic\nlinks.\n\n### Attribute Parameters for :put\n\n- `path`: path to extract to.\n - Default: `/usr/local`\n- `has_binaries`: array of binary commands to symlink into\n `/usr/local/bin/`, you must specify the relative path.\n - Example: `[ 'bin/java', 'bin/javaws' ]`\n- `append_env_path`: boolean, if true, append the `./bin` directory of\n the extracted directory to the global `PATH` variable for all users.\n\nAttribute Parameters\n--------------------\n\n- `name`: name of the package, defaults to the resource name.\n- `url`: url for tarball, `.tar.gz`, `.bin` (oracle-specific), `.war`,\n and `.zip` currently supported. Also supports special syntax\n `:name:version:apache_mirror:` that will auto-magically construct\n download url from the apache mirrors site.\n- `version`: software version, defaults to `1`.\n- `checksum`: sha256 checksum, used for security .\n- `mode`: file mode for `app_home`, is an integer.\n- `prefix_root`: default `prefix_root`, for use with `:install*`\n actions.\n- `prefix_home`: default directory prefix for a friendly symlink to\n the path.\n - Example: `/usr/local/maven` -> `/usr/local/maven-2.2.1`\n- `prefix_bin`: default directory to place a symlink to a binary\n command.\n - Example: `/opt/bin/mvn` -> `/opt/maven-2.2.1/bin/mvn`, where the\n `prefix_bin` is `/opt/bin`\n- `path`: path to extract the ark to. The `:install*` actions\n overwrite any user-provided values for `:path`.\n - Default: `/usr/local/-` for the `:install`,\n `:install_with_make` actions\n- `home_dir`: symbolic link to the path `:prefix_root/:name-:version`,\n does not apply to `:dump`, `:put`, or `:cherry_pick` actions.\n - Default: `:prefix_root/:name`\n- `has_binaries`: array of binary commands to symlink into\n `/usr/local/bin/`, you must specify the relative path.\n - Example: `[ 'bin/java', 'bin/javaws' ]`\n- `append_env_path`: boolean, similar to `has_binaries` but less\n granular. If true, append the `./bin` directory of the extracted\n directory to. the `PATH` environment variable for all users, by\n placing a file in `/etc/profile.d/`. The commands are symbolically\n linked into `/usr/bin/*`. This option provides more granularity than\n the boolean option.\n - Example: `mvn`, `java`, `javac`, etc.\n- `environment`: hash of environment variables to pass to invoked\n shell commands like `tar`, `unzip`, `configure`, and `make`.\n- `strip_components`: number of components in path to strip when extracting archive.\n With default value of `1`, ark strips the leading directory from an archive, \n which is the default for both `unzip` and `tar` commands.\n- `autoconf_opts`: an array of command line options for use with the\n GNU `autoconf` script.\n - Example: `[ '--include=/opt/local/include', '--force' ]`\n- `make_opts`: an array of command line options for use with `make`.\n - Example: `[ '--warn-undefined-variables', '--load-average=2' ]`\n- `owner`: owner of extracted directory.\n - Default: `root`\n\n### Examples\n\nThis example copies `ivy.tar.gz` to\n`/var/cache/chef/ivy-2.2.0.tar.gz`, unpacks its contents to\n`/usr/local/ivy-2.2.0/` -- stripping the leading directory, and\nsymlinks `/usr/local/ivy` to `/usr/local/ivy-2.2.0`\n\n # install Apache Ivy dependency resolution tool\n ark \"ivy\" do\n url 'http://someurl.example.com/ivy.tar.gz'\n version '2.2.0'\n checksum '89ba5fde0c596db388c3bbd265b63007a9cc3df3a8e6d79a46780c1a39408cb5'\n action :install\n end\n\nThis example copies `jdk-7u2-linux-x64.tar.gz` to\n`/var/cache/chef/jdk-7.2.tar.gz`, unpacks its contents to\n`/usr/local/jvm/jdk-7.2/` -- stripping the leading directory, symlinks\n`/usr/local/jvm/default` to `/usr/local/jvm/jdk-7.2`, and adds\n`/usr/local/jvm/jdk-7.2/bin/` to the global `PATH` for all users. The\nuser 'foobar' is the owner of the `/usr/local/jvm/jdk-7.2` directory:\n\n ark 'jdk' do\n url 'http://download.example.com/jdk-7u2-linux-x64.tar.gz'\n version '7.2'\n path \"/usr/local/jvm/\"\n home_dir \"/usr/local/jvm/default\"\n checksum '89ba5fde0c596db388c3bbd265b63007a9cc3df3a8e6d79a46780c1a39408cb5'\n append_env_path true\n owner 'foobar'\n end\n\nInstall Apache Ivy dependency resolution tool in /resource_name in this case\n`/usr/local/ivy`, do not symlink, and strip any leading directory if one\nexists in the tarball:\n\n ark \"ivy\" do\n url 'http://someurl.example.com/ivy.tar.gz'\n checksum '89ba5fde0c596db388c3bbd265b63007a9cc3df3a8e6d79a46780c1a39408cb5'\n action :put\n end\n\nInstall Apache Ivy dependency resolution tool in /home/foobar/ivy, strip any\nleading directory if one exists:\n\n ark \"ivy\" do\n path \"/home/foobar\n url 'http://someurl.example.com/ivy.tar.gz'\n checksum '89ba5fde0c596db388c3bbd265b63007a9cc3df3a8e6d79a46780c1a39408cb5'\n action :put\n end\n\n Strip all directories and dump files into path specified by the path attribute.\n You must specify the `creates` attribute in order to keep the extraction from\n running every time. The directory path will be created if it doesn't already exist:\n\n ark \"my_jars\" do\n url \"http://example.com/bunch_of_jars.zip\"\n path \"/usr/local/tomcat/lib\"\n creates \"mysql.jar\"\n owner \"tomcat\"\n action :dump\n end\n\nExtract specific files from a tarball (currently only handles one named file):\n\n ark 'mysql-connector-java' do\n url 'http://oracle.com/mysql-connector.zip'\n creates 'mysql-connector-java-5.0.8-bin.jar'\n path '/usr/local/tomcat/lib'\n action :cherry_pick\n end\n\nBuild and install haproxy and use alternave values for `prefix_root`, `prefix_home`, and `prefix_bin`:\n\n ark \"haproxy\" do\n url \"http://haproxy.1wt.eu/download/1.5/src/snapshot/haproxy-ss-20120403.tar.gz\"\n version \"1.5\"\n checksum 'ba0424bf7d23b3a607ee24bbb855bb0ea347d7ffde0bec0cb12a89623cbaf911'\n make_opts [ 'TARGET=linux26' ]\n prefix_root '/opt'\n prefix_home '/opt'\n prefix_bin '/opt/bin'\n action :install_with_make\n end\n\nYou can also pass multiple actions to ark and supply the file extension in case\nthe file extension can not be determined by the URL:\n\n ark \"test_autogen\" do\n url 'https://github.com/zeromq/libzmq/tarball/master'\n extension \"tar.gz\"\n action [ :configure, :install_with_make ]\n end\n\nLicense and Author\n==================\n\n- Author: Philip (flip) Kromer - Infochimps, Inc()\n- Author: Bryan W. Berry ()\n- Author: Denis Barishev ()\n- Author: Sean OMeara ()\n- Copyright: 2011, Philip (flip) Kromer - Infochimps, Inc\n- Copyright: 2012, Bryan W. Berry\n- Copyright: 2012, Denis Barishev\n- Copyright: 2013, Opscode, Inc\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n", + "maintainer": "Bryan W. Berry", + "maintainer_email": "bryan.berry@gmail.com", + "license": "Apache 2.0", + "platforms": { + "debian": ">= 0.0.0", + "ubuntu": ">= 0.0.0", + "centos": ">= 0.0.0", + "redhat": ">= 0.0.0", + "fedora": ">= 0.0.0", + "windows": ">= 0.0.0" + }, + "dependencies": { + "windows": ">= 0.0.0", + "7-zip": ">= 0.0.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + "ark::default": "Installs and configures ark" + } +} \ No newline at end of file diff --git a/cookbooks/ark/metadata.rb b/cookbooks/ark/metadata.rb new file mode 100644 index 0000000..6514d1c --- /dev/null +++ b/cookbooks/ark/metadata.rb @@ -0,0 +1,16 @@ +name 'ark' +maintainer 'Bryan W. Berry' +maintainer_email 'bryan.berry@gmail.com' +license 'Apache 2.0' +description 'Installs/Configures ark' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '0.9.0' + +%w( debian ubuntu centos redhat fedora windows ).each do |os| + supports os +end + +recipe 'ark::default', 'Installs and configures ark' + +depends 'windows' +depends '7-zip' \ No newline at end of file diff --git a/cookbooks/ark/providers/default.rb b/cookbooks/ark/providers/default.rb new file mode 100644 index 0000000..623ec64 --- /dev/null +++ b/cookbooks/ark/providers/default.rb @@ -0,0 +1,396 @@ +# +# Cookbook Name:: ark +# Provider:: Ark +# +# Author:: Bryan W. Berry +# Author:: Sean OMeara "#{new_resource.path}/bin") + only_if { new_resource.append_env_path } + end + end + + # Add to path for the current chef-client converge. + bin_path = ::File.join(new_resource.path, 'bin') + ruby_block "adding '#{bin_path}' to chef-client ENV['PATH']" do + block do + ENV['PATH'] = bin_path + ':' + ENV['PATH'] + end + only_if { new_resource.append_env_path && ENV['PATH'].scan(bin_path).empty? } + end +end + +############## +# action :put +############## +action :put do + show_deprecations + set_put_paths + + directory new_resource.path do + recursive true + action :create + notifies :run, "execute[unpack #{new_resource.release_file}]" + end + + # download + remote_file new_resource.release_file do + source new_resource.url + checksum new_resource.checksum if new_resource.checksum + action :create + notifies :run, "execute[unpack #{new_resource.release_file}]" + end + + # unpack based on file extension + _unpack_command = unpack_command + execute "unpack #{new_resource.release_file}" do + command _unpack_command + cwd new_resource.path + environment new_resource.environment + notifies :run, "execute[set owner on #{new_resource.path}]" + action :nothing + end + + # set_owner + _owner_command = owner_command + execute "set owner on #{new_resource.path}" do + command _owner_command + action :nothing + end +end + +########################### +# action :dump +########################### +action :dump do + show_deprecations + set_dump_paths + + directory new_resource.path do + recursive true + action :create + notifies :run, "execute[unpack #{new_resource.release_file}]" + end + + # download + remote_file new_resource.release_file do + Chef::Log.debug("DEBUG: new_resource.release_file #{new_resource.release_file}") + source new_resource.url + checksum new_resource.checksum if new_resource.checksum + action :create + notifies :run, "execute[unpack #{new_resource.release_file}]" + end + + # unpack based on file extension + _dump_command = dump_command + execute "unpack #{new_resource.release_file}" do + command _dump_command + cwd new_resource.path + environment new_resource.environment + notifies :run, "execute[set owner on #{new_resource.path}]" + action :nothing + end + + # set_owner + _owner_command = owner_command + execute "set owner on #{new_resource.path}" do + command _owner_command + action :nothing + end +end + +########################### +# action :unzip +########################### +action :unzip do + show_deprecations + set_dump_paths + + directory new_resource.path do + recursive true + action :create + notifies :run, "execute[unpack #{new_resource.release_file}]" + end + + # download + remote_file new_resource.release_file do + Chef::Log.debug("DEBUG: new_resource.release_file #{new_resource.release_file}") + source new_resource.url + checksum new_resource.checksum if new_resource.checksum + action :create + notifies :run, "execute[unpack #{new_resource.release_file}]" + end + + # unpack based on file extension + _unzip_command = unzip_command + execute "unpack #{new_resource.release_file}" do + command _unzip_command + cwd new_resource.path + environment new_resource.environment + notifies :run, "execute[set owner on #{new_resource.path}]" + action :nothing + end + + # set_owner + _owner_command = owner_command + execute "set owner on #{new_resource.path}" do + command _owner_command + action :nothing + end +end + +##################### +# action :cherry_pick +##################### +action :cherry_pick do + show_deprecations + set_dump_paths + Chef::Log.debug("DEBUG: new_resource.creates #{new_resource.creates}") + + directory new_resource.path do + recursive true + action :create + notifies :run, "execute[cherry_pick #{new_resource.creates} from #{new_resource.release_file}]" + end + + # download + remote_file new_resource.release_file do + source new_resource.url + checksum new_resource.checksum if new_resource.checksum + action :create + notifies :run, "execute[cherry_pick #{new_resource.creates} from #{new_resource.release_file}]" + end + + _unpack_type = unpack_type + _cherry_pick_command = cherry_pick_command + execute "cherry_pick #{new_resource.creates} from #{new_resource.release_file}" do + Chef::Log.debug("DEBUG: unpack_type: #{_unpack_type}") + command _cherry_pick_command + creates "#{new_resource.path}/#{new_resource.creates}" + notifies :run, "execute[set owner on #{new_resource.path}]" + action :nothing + end + + # set_owner + _owner_command = owner_command + execute "set owner on #{new_resource.path}" do + command _owner_command + action :nothing + end +end + +########################### +# action :install_with_make +########################### +action :install_with_make do + show_deprecations + set_paths + + directory new_resource.path do + recursive true + action :create + notifies :run, "execute[unpack #{new_resource.release_file}]" + end + + remote_file new_resource.release_file do + Chef::Log.debug('DEBUG: new_resource.release_file') + source new_resource.url + checksum new_resource.checksum if new_resource.checksum + action :create + notifies :run, "execute[unpack #{new_resource.release_file}]" + end + + # unpack based on file extension + _unpack_command = unpack_command + execute "unpack #{new_resource.release_file}" do + command _unpack_command + cwd new_resource.path + environment new_resource.environment + notifies :run, "execute[set owner on #{new_resource.path}]" + notifies :run, "execute[autogen #{new_resource.path}]" + notifies :run, "execute[configure #{new_resource.path}]" + notifies :run, "execute[make #{new_resource.path}]" + notifies :run, "execute[make install #{new_resource.path}]" + action :nothing + end + + # set_owner + _owner_command = owner_command + execute "set owner on #{new_resource.path}" do + command _owner_command + action :nothing + end + + execute "autogen #{new_resource.path}" do + command './autogen.sh' + only_if { ::File.exist? "#{new_resource.path}/autogen.sh" } + cwd new_resource.path + environment new_resource.environment + action :nothing + ignore_failure true + end + + execute "configure #{new_resource.path}" do + command "./configure #{new_resource.autoconf_opts.join(' ')}" + only_if { ::File.exist? "#{new_resource.path}/configure" } + cwd new_resource.path + environment new_resource.environment + action :nothing + end + + execute "make #{new_resource.path}" do + command "make #{new_resource.make_opts.join(' ')}" + cwd new_resource.path + environment new_resource.environment + action :nothing + end + + execute "make install #{new_resource.path}" do + command "make install #{new_resource.make_opts.join(' ')}" + cwd new_resource.path + environment new_resource.environment + action :nothing + end + + # unless new_resource.creates and ::File.exists? new_resource.creates + # end +end + +action :configure do + show_deprecations + set_paths + + directory new_resource.path do + recursive true + action :create + notifies :run, "execute[unpack #{new_resource.release_file}]" + end + + remote_file new_resource.release_file do + Chef::Log.debug('DEBUG: new_resource.release_file') + source new_resource.url + checksum new_resource.checksum if new_resource.checksum + action :create + notifies :run, "execute[unpack #{new_resource.release_file}]" + end + + # unpack based on file extension + _unpack_command = unpack_command + execute "unpack #{new_resource.release_file}" do + command _unpack_command + cwd new_resource.path + environment new_resource.environment + notifies :run, "execute[set owner on #{new_resource.path}]" + notifies :run, "execute[autogen #{new_resource.path}]" + notifies :run, "execute[configure #{new_resource.path}]" + action :nothing + end + + # set_owner + _owner_command = owner_command + execute "set owner on #{new_resource.path}" do + command _owner_command + action :nothing + end + + execute "autogen #{new_resource.path}" do + command './autogen.sh' + only_if { ::File.exist? "#{new_resource.path}/autogen.sh" } + cwd new_resource.path + environment new_resource.environment + action :nothing + ignore_failure true + end + + execute "configure #{new_resource.path}" do + command "./configure #{new_resource.autoconf_opts.join(' ')}" + only_if { ::File.exist? "#{new_resource.path}/configure" } + cwd new_resource.path + environment new_resource.environment + action :nothing + end +end diff --git a/cookbooks/ark/recipes/default.rb b/cookbooks/ark/recipes/default.rb new file mode 100644 index 0000000..2d8fc6a --- /dev/null +++ b/cookbooks/ark/recipes/default.rb @@ -0,0 +1,27 @@ +# +# Cookbook Name:: ark +# Recipe:: default +# +# Author:: Bryan W. Berry +# Copyright 2012, Bryan W. Berry +# +# 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. +# + +Array(node['ark']['package_dependencies']).each do |pkg| + package pkg +end + +if node['platform_family'] === 'windows' + include_recipe "7-zip" +end \ No newline at end of file diff --git a/cookbooks/ark/recipes/test.rb b/cookbooks/ark/recipes/test.rb new file mode 100644 index 0000000..a23f272 --- /dev/null +++ b/cookbooks/ark/recipes/test.rb @@ -0,0 +1,152 @@ +# require 'fileutils' + +# remove file so we can test sending notification on its creation + +FileUtils.rm_f '/tmp/foobarbaz/foo1.txt' if ::File.exist? '/tmp/foobarbaz/foo1.txt' + +ruby_block 'test_notification' do + block do + FileUtils.touch '/tmp/foobarbaz/notification_successful.txt' if ::File.exist? '/tmp/foobarbaz/foo1.txt' + end + action :nothing +end + +user 'foobarbaz' + +directory '/opt/bin' do + recursive true +end + +ark 'foo' do + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo.tar.gz' + checksum '5996e676f17457c823d86f1605eaa44ca8a81e70d6a0e5f8e45b51e62e0c52e8' + version '2' + prefix_root '/usr/local' + owner 'foobarbaz' + group 'foobarbaz' + has_binaries ['bin/do_foo', 'bin/do_more_foo'] + action :install +end + +ark 'test_put' do + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo.tar.gz' + checksum '5996e676f17457c823d86f1605eaa44ca8a81e70d6a0e5f8e45b51e62e0c52e8' + owner 'foobarbaz' + group 'foobarbaz' + action :put +end + +ark 'test_dump' do + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo.zip' + checksum 'deea3a324115c9ca0f3078362f807250080bf1b27516f7eca9d34aad863a11e0' + path '/usr/local/foo_dump' + creates 'foo1.txt' + owner 'foobarbaz' + group 'foobarbaz' + action :dump +end + +ark 'cherry_pick_test' do + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo.tar.gz' + checksum '5996e676f17457c823d86f1605eaa44ca8a81e70d6a0e5f8e45b51e62e0c52e8' + path '/usr/local/foo_cherry_pick' + owner 'foobarbaz' + group 'foobarbaz' + creates 'foo_sub/foo1.txt' + action :cherry_pick +end + +ark 'cherry_pick_with_zip' do + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo.zip' + checksum 'deea3a324115c9ca0f3078362f807250080bf1b27516f7eca9d34aad863a11e0' + path '/usr/local/foo_cherry_pick_from_zip' + creates 'foo_sub/foo1.txt' + action :cherry_pick +end + +ark 'foo_append_env' do + version '7.0.26' + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo.tar.gz' + checksum '5996e676f17457c823d86f1605eaa44ca8a81e70d6a0e5f8e45b51e62e0c52e8' + append_env_path true + action :install +end + +ark 'foo_dont_strip' do + version '2' + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo.tar.gz' + checksum '5996e676f17457c823d86f1605eaa44ca8a81e70d6a0e5f8e45b51e62e0c52e8' + strip_components 0 + action :install +end + +ark 'foo_zip_strip' do + version '2' + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo.zip' + checksum 'deea3a324115c9ca0f3078362f807250080bf1b27516f7eca9d34aad863a11e0' + action :install +end + +ark 'haproxy' do + url 'http://haproxy.1wt.eu/download/1.5/src/snapshot/haproxy-ss-20120403.tar.gz' + version '1.5' + checksum 'ba0424bf7d23b3a607ee24bbb855bb0ea347d7ffde0bec0cb12a89623cbaf911' + make_opts ['TARGET=linux26'] + action :install_with_make +end unless platform?('freebsd') + +ark 'foo_alt_bin' do + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo.tar.gz' + checksum '5996e676f17457c823d86f1605eaa44ca8a81e70d6a0e5f8e45b51e62e0c52e8' + version '3' + prefix_root '/opt' + prefix_home '/opt' + prefix_bin '/opt/bin' + owner 'foobarbaz' + group 'foobarbaz' + has_binaries ['bin/do_foo'] + action :install +end + +ark 'foo_tbz' do + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo.tbz' + version '3' +end + +ark 'foo_tgz' do + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo.tgz' + version '3' +end + +ark 'foo_txz' do + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo.txz' + version '3' +end + +ark 'test notification' do + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo.zip' + path '/tmp/foobarbaz' + creates 'foo1.txt' + action :dump + notifies :create, 'ruby_block[test_notification]', :immediately +end + +ark 'test_autogen' do + url 'https://github.com/zeromq/libzmq/tarball/master' + extension 'tar.gz' + action :configure + # autoconf in RHEL < 6 is too old + not_if { platform_family?('rhel') && node['platform_version'].to_f < 6.0 } +end + +ark 'foo_sub' do + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo_sub.tar.gz' + version '1' + strip_components 2 +end + +ark 'foo_sub' do + url 'https://github.com/opscode-cookbooks/ark/raw/master/files/default/foo_sub.zip' + version '2' + strip_components 2 +end diff --git a/cookbooks/ark/resources/default.rb b/cookbooks/ark/resources/default.rb new file mode 100644 index 0000000..bdf2601 --- /dev/null +++ b/cookbooks/ark/resources/default.rb @@ -0,0 +1,55 @@ +# +# Cookbook Name:: ark +# Resource:: Ark +# +# Author:: Bryan W. Berry +# Copyright 2012, Bryan W. Berry +# +# 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. +# + +def initialize(name, run_context = nil) + super + @resource_name = :ark + @allowed_actions.push(:install, :dump, :cherry_pick, :put, :install_with_make, :configure, :setup_py_build, :setup_py_install, :setup_py, :unzip) + @action = :install + @provider = Chef::Provider::Ark +end + +attr_accessor :path, :release_file, :prefix_bin, :prefix_root, :home_dir, :extension, :version + +attribute :owner, :kind_of => String, :default => 'root' +attribute :group, :kind_of => [String, Fixnum], :default => 0 +attribute :url, :kind_of => String, :required => true +attribute :path, :kind_of => String, :default => nil +attribute :full_path, :kind_of => String, :default => nil +attribute :append_env_path, :kind_of => [TrueClass, FalseClass], :default => false +attribute :checksum, :regex => /^[a-zA-Z0-9]{64}$/, :default => nil +attribute :has_binaries, :kind_of => Array, :default => [] +attribute :creates, :kind_of => String, :default => nil +attribute :release_file, :kind_of => String, :default => '' +attribute :strip_leading_dir, :kind_of => [TrueClass, FalseClass, NilClass] +attribute :strip_components, :kind_of => Integer, :default => 1 +attribute :mode, :kind_of => Fixnum, :default => 0755 +attribute :prefix_root, :kind_of => String, :default => nil +attribute :prefix_home, :kind_of => String, :default => nil +attribute :prefix_bin, :kind_of => String, :default => nil +attribute :version, :kind_of => String, :default => nil +attribute :home_dir, :kind_of => String, :default => nil +attribute :win_install_dir, :kind_of => String, :default => nil +attribute :environment, :kind_of => Hash, :default => {} +attribute :autoconf_opts, :kind_of => Array, :default => [] +attribute :make_opts, :kind_of => Array, :default => [] +attribute :home_dir, :kind_of => String, :default => nil +attribute :autoconf_opts, :kind_of => Array, :default => [] +attribute :extension, :kind_of => String diff --git a/cookbooks/ark/templates/default/add_to_path.sh.erb b/cookbooks/ark/templates/default/add_to_path.sh.erb new file mode 100644 index 0000000..1ab1bfa --- /dev/null +++ b/cookbooks/ark/templates/default/add_to_path.sh.erb @@ -0,0 +1 @@ +export PATH=<%= @directory -%>:$PATH \ No newline at end of file diff --git a/cookbooks/bluepill/CHANGELOG.md b/cookbooks/bluepill/CHANGELOG.md new file mode 100644 index 0000000..f8c93ca --- /dev/null +++ b/cookbooks/bluepill/CHANGELOG.md @@ -0,0 +1,70 @@ +bluepill Cookbook CHANGELOG +=========================== +This file is used to list changes made in each version of the bluepill cookbook. + + +v2.3.1 +------ +### New Feature +- **[COOK-3705](https://tickets.opscode.com/browse/COOK-3705)** - Add init.d script with LSB style + + +v2.3.0 +------ +### Improvement +- **[COOK-3503](https://tickets.opscode.com/browse/COOK-3503)** - Add why-run support + +v2.2.2 +------ +- [COOK-2507] - stringify language attributes + +v2.2.0 +------ +- [COOK-547] - Add `load` action to provider to reload services when template changes. + +v2.1.0 +------ +- [COOK-1295] - The bluepill cookbook does not create the default log file +- [COOK-1840] - Enable bluepill to log to rsyslog + +v2.0.0 +------ +This version uses platform_family attribute (in the provider), making the cookbook incompatible with older versions of Chef/Ohai, hence the major version bump. + +- [COOK-1644] - Bluepill cookbook fails on Redhat due to missing default or redhat template directory. +- [COOK-1920] - init script should have a template file named after platform_family instead of using file specificity + +v1.1.2 +------ +- [COOK-1730] - Add ability to specify which version of bluepill to install + +v1.1.0 +------ +- [COOK-1592] - use mixlib-shellout instead of execute, add test-kitchen + +v1.0.6 +------ +- [COOK-1304] - support amazon linux +- [COOK-1427] - resolve foodcritic warnings + +v1.0.4 +------ +- [COOK-1106] - fix chkconfig loader for CentOS 5 +- [COOK-1107] - use integer for GID instead of string + +v1.0.2 +------ +- [COOK-1043] - Bluepill cookbook fails on OS X because it tries to use root group + +v1.0.0 +------ +- [COOK-943] - add init script for freebsd + +v0.3.0 +------ +- [COOK-867] - enable bluepill service on RHEL family +- [COOK-550] - add freebsd support + +v0.2.2 +------ +- Fixes COOK-524, COOK-632 diff --git a/cookbooks/bluepill/README.md b/cookbooks/bluepill/README.md new file mode 100644 index 0000000..3bb173a --- /dev/null +++ b/cookbooks/bluepill/README.md @@ -0,0 +1,87 @@ +bluepill Cookbook +================= +Installs bluepill RubyGem and configures it to manage services. Also includes a LWRP. + + +Requirements +------------ +Bluepill is a pure Ruby service management tool/library, so this cookbook should work on any system. The attributes do set up paths based on FHS locations, see below. + + +Attributes +---------- +Default locations for bluepill are in "FHS compliant" locations. + +* `node["bluepill"]["bin"]` - Path to bluepill program, default is 'bluepill' in the RubyGems binary directory. +* `node["bluepill"]["logfile"]` - Location of the bluepill log file, default "/var/log/bluepill.log". +* `node["bluepill"]["conf_dir"]` - Location of service config files (pills), default "/etc/bluepill". +* `node["bluepill"]["pid_dir"]` - Location of pidfiles, default "/var/run/bluepill" +* `node["bluepill"]["state_dir"]` - Location of state directory, default "/var/lib/bluepill" +* `node["bluepill"]["init_dir"]` - Location of init script directory, default selected by platform. +* `node["bluepill"]["version"]` - Version of bluepill to install, default is latest. +* `node["bluepill"]["use_rsyslog"]` - Enable configuration and use of rsyslog for bluepill. + + +Resources/Providers +------------------- +This cookbook contains an LWRP, `bluepill_service`. This can be used with the normal Chef service resource, by using the `provider` parameter, or by specifying the `bluepill_service` shortcut. These two resources are equivalent. + +```ruby +service 'my_app' do + provider bluepill_service + action [:enable, :load, :start] +end + +bluepill_service 'my_app' do + action [:enable, :load, :start] +end +``` + +The load action should probably always be specified, to ensure that if bluepill isn't running already it gets started. The + +The recipe using the service must contain a template resource for the pill and it must be named `my_app.pill.erb`, where `my_app` is the service name passed to the bluepill service resource. + + +Usage +----- +Be sure to include the bluepill recipe in the run list to ensure that the gem and bluepill-related directories are created. This will also make the cookbook available on the system and other cookbooks won't need to explicitly depend on it in the metadata. + +If the default directory locations in the attributes/default.rb aren't what you want, change them by setting them either in the attributes file itself, or create attributes in a role applied to any systems that will use bluepill. + +Example pill template resource and .erb file: + +```ruby +template '/etc/bluepill/my_app.pill' do + source 'my_app.pill.erb' +end + +Bluepill.application('my_app') do |app| + app.process('my_app') do |process| + process.pid_file = '/var/run/my_app.pid' + process.start_command = '/usr/bin/my_app' + end +end +``` + +See bluepill's documentation for more information on creating pill templates. + + +License & Authors +----------------- +- Author:: Joshua Timberman () + +```text +Copyright 2010-2013, Opscode, Inc. + +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/cookbooks/bluepill/attributes/default.rb b/cookbooks/bluepill/attributes/default.rb new file mode 100644 index 0000000..b857d19 --- /dev/null +++ b/cookbooks/bluepill/attributes/default.rb @@ -0,0 +1,35 @@ +# Cookbook Name:: bluepill +# Attributes:: default +# +# Copyright 2010, Opscode, Inc. +# +# 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. + +default["bluepill"]["bin"] = "#{node['languages']['ruby']['bin_dir']}/bluepill" +default["bluepill"]["logfile"] = "/var/log/bluepill.log" +default["bluepill"]["pid_dir"] = "/var/run/bluepill" +default["bluepill"]["state_dir"] = "/var/lib/bluepill" +default["bluepill"]["group"] = 0 +default["bluepill"]["use_rsyslog"] = false + +case platform +when "arch" + default["bluepill"]["init_dir"] = "/etc/rc.d" + default["bluepill"]["conf_dir"] = "/etc/bluepill" +when "freebsd" + default["bluepill"]["init_dir"] = "/usr/local/etc/rc.d" + default["bluepill"]["conf_dir"] = "/usr/local/etc/bluepill" +else + default["bluepill"]["init_dir"] = "/etc/init.d" + default["bluepill"]["conf_dir"] = "/etc/bluepill" +end diff --git a/cookbooks/bluepill/metadata.json b/cookbooks/bluepill/metadata.json new file mode 100644 index 0000000..61515e9 --- /dev/null +++ b/cookbooks/bluepill/metadata.json @@ -0,0 +1,31 @@ +{ + "name": "bluepill", + "version": "2.3.1", + "description": "Installs bluepill gem and configures to manage services, includes bluepill_service LWRP", + "long_description": "bluepill Cookbook\n=================\nInstalls bluepill RubyGem and configures it to manage services. Also includes a LWRP.\n\n\nRequirements\n------------\nBluepill is a pure Ruby service management tool/library, so this cookbook should work on any system. The attributes do set up paths based on FHS locations, see below.\n\n\nAttributes\n----------\nDefault locations for bluepill are in \"FHS compliant\" locations.\n\n* `node[\"bluepill\"][\"bin\"]` - Path to bluepill program, default is 'bluepill' in the RubyGems binary directory.\n* `node[\"bluepill\"][\"logfile\"]` - Location of the bluepill log file, default \"/var/log/bluepill.log\".\n* `node[\"bluepill\"][\"conf_dir\"]` - Location of service config files (pills), default \"/etc/bluepill\".\n* `node[\"bluepill\"][\"pid_dir\"]` - Location of pidfiles, default \"/var/run/bluepill\"\n* `node[\"bluepill\"][\"state_dir\"]` - Location of state directory, default \"/var/lib/bluepill\"\n* `node[\"bluepill\"][\"init_dir\"]` - Location of init script directory, default selected by platform.\n* `node[\"bluepill\"][\"version\"]` - Version of bluepill to install, default is latest.\n* `node[\"bluepill\"][\"use_rsyslog\"]` - Enable configuration and use of rsyslog for bluepill.\n\n\nResources/Providers\n-------------------\nThis cookbook contains an LWRP, `bluepill_service`. This can be used with the normal Chef service resource, by using the `provider` parameter, or by specifying the `bluepill_service` shortcut. These two resources are equivalent.\n\n```ruby\nservice 'my_app' do\n provider bluepill_service\n action [:enable, :load, :start]\nend\n\nbluepill_service 'my_app' do\n action [:enable, :load, :start]\nend\n```\n\nThe load action should probably always be specified, to ensure that if bluepill isn't running already it gets started. The\n\nThe recipe using the service must contain a template resource for the pill and it must be named `my_app.pill.erb`, where `my_app` is the service name passed to the bluepill service resource.\n\n\nUsage\n-----\nBe sure to include the bluepill recipe in the run list to ensure that the gem and bluepill-related directories are created. This will also make the cookbook available on the system and other cookbooks won't need to explicitly depend on it in the metadata.\n\nIf the default directory locations in the attributes/default.rb aren't what you want, change them by setting them either in the attributes file itself, or create attributes in a role applied to any systems that will use bluepill.\n\nExample pill template resource and .erb file:\n\n```ruby\ntemplate '/etc/bluepill/my_app.pill' do\n source 'my_app.pill.erb'\nend\n\nBluepill.application('my_app') do |app|\n app.process('my_app') do |process|\n process.pid_file = '/var/run/my_app.pid'\n process.start_command = '/usr/bin/my_app'\n end\nend\n```\n\nSee bluepill's documentation for more information on creating pill templates.\n\n\nLicense & Authors\n-----------------\n- Author:: Joshua Timberman ()\n\n```text\nCopyright 2010-2013, Opscode, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "maintainer": "Opscode, Inc.", + "maintainer_email": "cookbooks@opscode.com", + "license": "Apache 2.0", + "platforms": { + }, + "dependencies": { + "rsyslog": ">= 0.0.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + "bluepill::default": "Installs bluepill rubygem and set up management directories" + } +} \ No newline at end of file diff --git a/cookbooks/bluepill/metadata.rb b/cookbooks/bluepill/metadata.rb new file mode 100644 index 0000000..d0975d8 --- /dev/null +++ b/cookbooks/bluepill/metadata.rb @@ -0,0 +1,10 @@ +name "bluepill" +maintainer "Opscode, Inc." +maintainer_email "cookbooks@opscode.com" +license "Apache 2.0" +description "Installs bluepill gem and configures to manage services, includes bluepill_service LWRP" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version "2.3.1" +recipe "bluepill::default", "Installs bluepill rubygem and set up management directories" + +depends "rsyslog" diff --git a/cookbooks/bluepill/providers/service.rb b/cookbooks/bluepill/providers/service.rb new file mode 100644 index 0000000..f1e9c74 --- /dev/null +++ b/cookbooks/bluepill/providers/service.rb @@ -0,0 +1,175 @@ +# +# Cookbook Name:: bluepill +# Provider:: service +# +# Copyright 2010, Opscode, Inc. +# Copyright 2012, Heavy Water Operations, LLC +# +# 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. +# + +require 'chef/mixin/shell_out' +require 'chef/mixin/language' + +include Chef::Mixin::ShellOut + +def whyrun_supported? + true +end + +action :enable do + config_file = ::File.join(node['bluepill']['conf_dir'], + "#{new_resource.service_name}.pill") + unless @current_resource.enabled + converge_by("enable #{ @new_resource }") do + link "#{node['bluepill']['init_dir']}/#{new_resource.service_name}" do + to node['bluepill']['bin'] + only_if { ::File.exists?(config_file) } + end + template_suffix = case node['platform_family'] + when "rhel", "fedora", "freebsd" then node['platform_family'] + when 'debian' then 'lsb' + else nil + end + + template "#{node['bluepill']['init_dir']}/bluepill-#{new_resource.service_name}" do + source "bluepill_init.#{template_suffix}.erb" + cookbook "bluepill" + owner "root" + group node['bluepill']['group'] + mode "0755" + variables( + :service_name => new_resource.service_name, + :config_file => config_file + ) + end if template_suffix + + service "bluepill-#{new_resource.service_name}" do + action [ :enable ] + end + end + end +end + +action :load do + unless @current_resource.running + converge_by("load #{ @new_resource }") do + shell_out!(load_command) + end + end +end + +action :reload do + converge_by("reload #{ @new_resource }") do + shell_out!(stop_command) if @current_resource.running + shell_out!(load_command) + end +end + +action :start do + unless @current_resource.running + converge_by("start #{ @new_resource }") do + shell_out!(start_command) + end + end +end + +action :disable do + if @current_resource.enabled + converge_by("disable #{ @new_resource }") do + file "#{node['bluepill']['conf_dir']}/#{new_resource.service_name}.pill" do + action :delete + end + link "#{node['bluepill']['init_dir']}/#{new_resource.service_name}" do + action :delete + end + end + end +end + +action :stop do + if @current_resource.running + converge_by("stop #{ @new_resource }") do + shell_out!(stop_command) + end + end +end + +action :restart do + if @current_resource.running + converge_by("restart #{ @new_resource }") do + Chef::Log.debug "Restarting #{new_resource.service_name}" + shell_out!(restart_command) + Chef::Log.debug "Restarted #{new_resource.service_name}" + end + end +end + +def load_current_resource + @current_resource = Chef::Resource::BluepillService.new(new_resource.name) + @current_resource.service_name(new_resource.service_name) + + Chef::Log.debug("Checking status of service #{new_resource.service_name}") + + determine_current_status! + + @current_resource +end + +protected + +def status_command + "#{node['bluepill']['bin']} #{new_resource.service_name} status" +end + +def load_command + "#{node['bluepill']['bin']} load #{node['bluepill']['conf_dir']}/#{new_resource.service_name}.pill" +end + +def start_command + "#{node['bluepill']['bin']} #{new_resource.service_name} start" +end + +def stop_command + "#{node['bluepill']['bin']} #{new_resource.service_name} stop" +end + +def restart_command + "#{node['bluepill']['bin']} #{new_resource.service_name} restart" +end + +def determine_current_status! + service_running? + service_enabled? +end + +def service_running? + begin + if shell_out(status_command).exitstatus == 0 + @current_resource.running true + Chef::Log.debug("#{new_resource} is running") + end + rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError + @current_resource.running false + nil + end +end + +def service_enabled? + if ::File.exists?("#{node['bluepill']['conf_dir']}/#{new_resource.service_name}.pill") && + ::File.symlink?("#{node['bluepill']['init_dir']}/#{new_resource.service_name}") + @current_resource.enabled true + else + @current_resource.enabled false + end +end diff --git a/cookbooks/bluepill/recipes/default.rb b/cookbooks/bluepill/recipes/default.rb new file mode 100644 index 0000000..4b11c3b --- /dev/null +++ b/cookbooks/bluepill/recipes/default.rb @@ -0,0 +1,48 @@ +# +# Cookbook Name:: bluepill +# Recipe:: default +# +# Copyright 2010, Opscode, Inc. +# +# 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. +# + +gem_package "i18n" do + action :install +end + +gem_package "bluepill" do + version node["bluepill"]["version"] if node["bluepill"]["version"] + action :install +end + +[ + node["bluepill"]["conf_dir"], + node["bluepill"]["pid_dir"], + node["bluepill"]["state_dir"] +].each do |dir| + directory dir do + recursive true + owner "root" + group node["bluepill"]["group"] + end +end + +file node["bluepill"]["logfile"] do + owner "root" + group node["bluepill"]["group"] + mode "0755" + action :create_if_missing +end + +include_recipe "bluepill::rsyslog" if node['bluepill']['use_rsyslog'] diff --git a/cookbooks/bluepill/recipes/rsyslog.rb b/cookbooks/bluepill/recipes/rsyslog.rb new file mode 100644 index 0000000..a19113b --- /dev/null +++ b/cookbooks/bluepill/recipes/rsyslog.rb @@ -0,0 +1,28 @@ +# +# Cookbook Name:: bluepill +# Recipe:: rsyslog +# +# Copyright 2010, Opscode, Inc. +# +# 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. +# + +include_recipe "rsyslog" + +template "/etc/rsyslog.d/bluepill.conf" do + owner "root" + group "root" + mode 0644 + source "bluepill_rsyslog.conf.erb" + notifies :restart, "service[rsyslog]" +end diff --git a/cookbooks/bluepill/resources/service.rb b/cookbooks/bluepill/resources/service.rb new file mode 100644 index 0000000..e810fe1 --- /dev/null +++ b/cookbooks/bluepill/resources/service.rb @@ -0,0 +1,27 @@ +# +# Cookbook Name:: bluepill +# Resource:: service +# +# Copyright 2010, Opscode, Inc. +# +# 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. +# + +actions :start, :stop, :enable, :disable, :load, :restart, :reload +default_action :start + +attribute :service_name, :name_attribute => true +attribute :enabled, :default => false +attribute :running, :default => false +attribute :variables, :kind_of => Hash +attribute :supports, :default => { :restart => true, :status => true } diff --git a/cookbooks/bluepill/templates/default/bluepill_init.fedora.erb b/cookbooks/bluepill/templates/default/bluepill_init.fedora.erb new file mode 100644 index 0000000..0044bc4 --- /dev/null +++ b/cookbooks/bluepill/templates/default/bluepill_init.fedora.erb @@ -0,0 +1,32 @@ +#!/bin/sh +# +# Author: Jamie Winsor () +# +# chkconfig: 345 99 1 +# Description: Bluepill loader for <%= @service_name %> +# Provides: <%= @service_name %> +# Default-Start: 3 4 5 +# Default-Stop: 0 1 2 6 + +BLUEPILL_BIN=<%= node['bluepill']['bin'] %> +BLUEPILL_CONFIG=<%= @config_file %> +SERVICE_NAME=<%= @service_name %> + +case "$1" in + start) + echo "Loading bluepill configuration for $SERVICE_NAME " + $BLUEPILL_BIN load $BLUEPILL_CONFIG + ;; + stop) + $BLUEPILL_BIN $SERVICE_NAME stop + $BLUEPILL_BIN $SERVICE_NAME quit + ;; + restart) + $0 stop + $0 start + ;; + *) + echo "Usage: $0 {start|stop|restart}" + exit 1 + ;; +esac diff --git a/cookbooks/bluepill/templates/default/bluepill_init.freebsd.erb b/cookbooks/bluepill/templates/default/bluepill_init.freebsd.erb new file mode 100644 index 0000000..a85613f --- /dev/null +++ b/cookbooks/bluepill/templates/default/bluepill_init.freebsd.erb @@ -0,0 +1,31 @@ +#!/bin/sh +## +# PROVIDE: named +# REQUIRE: SERVERS cleanvar +# KEYWORD: shutdown +# + +. /etc/rc.subr + +name="<%= @service_name %>" +rcvar=`set_rcvar` + +# Set some defaults +<%= @service_name %>_enable=${<%= @service_name %>_enable:-"NO"} + +pidfile="/var/run/<%= @service_name %>.pid" +command="/usr/local/bin/bluepill" + +start_precmd="${command} load <%= node['bluepill']['conf_dir'] %>/<%= @service_name %>.pill" +start_cmd="${command} ${name} start" + +status_cmd="${command} ${name} status" + +stop_cmd="${command} ${name} stop" +stop_postcmd="${command} ${name} quit" + +load_rc_config ${name} + +PATH="${PATH}:/usr/local/bin" + +run_rc_command "$1" diff --git a/cookbooks/bluepill/templates/default/bluepill_init.lsb.erb b/cookbooks/bluepill/templates/default/bluepill_init.lsb.erb new file mode 100644 index 0000000..a49edc4 --- /dev/null +++ b/cookbooks/bluepill/templates/default/bluepill_init.lsb.erb @@ -0,0 +1,34 @@ +#!/bin/sh +# +### BEGIN INIT INFO +# Provides: <%= @service_name %> +# Required-Start: bar +# Defalt-Start: 2 3 4 5 +# Default-Stop: 0 1 2 6 +# Description: Bluepill loader for <%= @service_name %> +### END INIT INFO + +BLUEPILL_BIN=<%= node['bluepill']['bin'] %> +BLUEPILL_CONFIG=<%= @config_file %> +SERVICE_NAME=<%= @service_name %> + +case "$1" in + start) + echo "Loading bluepill configuration for $SERVICE_NAME " + $BLUEPILL_BIN load $BLUEPILL_CONFIG + ;; + stop) + $BLUEPILL_BIN $SERVICE_NAME stop + $BLUEPILL_BIN $SERVICE_NAME quit + ;; + restart) + $BLUEPILL_BIN $SERVICE_NAME restart + ;; + status) + $BLUEPILL_BIN $SERVICE_NAME status + ;; + *) + echo "Usage: $0 {start|stop|restart}" + exit 1 + ;; +esac diff --git a/cookbooks/bluepill/templates/default/bluepill_init.rhel.erb b/cookbooks/bluepill/templates/default/bluepill_init.rhel.erb new file mode 100644 index 0000000..0044bc4 --- /dev/null +++ b/cookbooks/bluepill/templates/default/bluepill_init.rhel.erb @@ -0,0 +1,32 @@ +#!/bin/sh +# +# Author: Jamie Winsor () +# +# chkconfig: 345 99 1 +# Description: Bluepill loader for <%= @service_name %> +# Provides: <%= @service_name %> +# Default-Start: 3 4 5 +# Default-Stop: 0 1 2 6 + +BLUEPILL_BIN=<%= node['bluepill']['bin'] %> +BLUEPILL_CONFIG=<%= @config_file %> +SERVICE_NAME=<%= @service_name %> + +case "$1" in + start) + echo "Loading bluepill configuration for $SERVICE_NAME " + $BLUEPILL_BIN load $BLUEPILL_CONFIG + ;; + stop) + $BLUEPILL_BIN $SERVICE_NAME stop + $BLUEPILL_BIN $SERVICE_NAME quit + ;; + restart) + $0 stop + $0 start + ;; + *) + echo "Usage: $0 {start|stop|restart}" + exit 1 + ;; +esac diff --git a/cookbooks/bluepill/templates/default/bluepill_rsyslog.conf.erb b/cookbooks/bluepill/templates/default/bluepill_rsyslog.conf.erb new file mode 100644 index 0000000..6a13f39 --- /dev/null +++ b/cookbooks/bluepill/templates/default/bluepill_rsyslog.conf.erb @@ -0,0 +1 @@ +local6.* <%= node["bluepill"]["logfile"] %> diff --git a/cookbooks/build-essential/.envrc b/cookbooks/build-essential/.envrc new file mode 100644 index 0000000..8edeaaf --- /dev/null +++ b/cookbooks/build-essential/.envrc @@ -0,0 +1,3 @@ + +# Force ChefDK's environment +source_env ~/.envrc_chefdk diff --git a/cookbooks/build-essential/.gitignore b/cookbooks/build-essential/.gitignore new file mode 100644 index 0000000..4e95ff6 --- /dev/null +++ b/cookbooks/build-essential/.gitignore @@ -0,0 +1,16 @@ +.vagrant +Berksfile.lock +Gemfile.lock +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ +.bundle +.cache +.kitchen +.rspec +bin +.kitchen.local.yml +.coverage diff --git a/cookbooks/build-essential/.kitchen.cloud.yml b/cookbooks/build-essential/.kitchen.cloud.yml new file mode 100644 index 0000000..e9b4dc1 --- /dev/null +++ b/cookbooks/build-essential/.kitchen.cloud.yml @@ -0,0 +1,165 @@ +#<% require 'kitchen-sync' %> +--- +driver_config: + digitalocean_client_id: <%= ENV['DIGITAL_OCEAN_CLIENT_ID'] %> + google_client_email: <%= ENV['GOOGLE_CLIENT_EMAIL'] %> + google_key_location: <%= ENV['GOOGLE_KEY_LOCATION'] %> + google_project: <%= ENV['GOOGLE_PROJECT'] %> + joyent_username: <%= ENV['SDC_CLI_ACCOUNT'] %> + joyent_keyfile: <%= ENV['SDC_CLI_IDENTITY'] %> + joyent_keyname: <%= ENV['SDC_CLI_KEY_ID'] %> + joyent_url: <%= ENV['SDC_CLI_URL'] %> + aws_access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %> + aws_secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> + aws_ssh_key_id: <%= ENV['AWS_KEYPAIR_NAME'] %> + flavor_id: <%= ENV['EC2_FLAVOR_ID'] %> + availability_zone: <%= ENV['AWS_AVAILABILITY_ZONE'] %> + +provisioner: + name: chef_zero + # require_chef_omnibus: 11.16.4 + require_chef_omnibus: 12.0.3 + +platforms: +- name: centos-5.8 + driver_plugin: digital_ocean + driver_config: + size: 2gb + image: centos-5-8-x64 + region: <%= ENV['DIGITAL_OCEAN_REGION'] %> + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + +- name: centos-6.5 + driver_plugin: digital_ocean + driver_config: + size: 2gb + image: centos-6-5-x64 + region: <%= ENV['DIGITAL_OCEAN_REGION'] %> + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + +- name: centos-7.0 + driver_plugin: digital_ocean + driver_config: + size: 2gb + image: centos-7-0-x64 + region: <%= ENV['DIGITAL_OCEAN_REGION'] %> + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + +- name: amazon-2014.09 + driver_plugin: ec2 + driver_config: + image_id: ami-9a6ed3f2 + username: ec2-user + ssh_key: <%= ENV['EC2_SSH_KEY_PATH'] %> + +# - name: redhat-6.5 +# driver_plugin: ec2 +# driver_config: +# image_id: ami-8d756fe4 +# username: ec2-user +# ssh_key: <%= ENV['EC2_SSH_KEY_PATH'] %> +# run_list: +# - recipe[selinux::disabled] + +- name: redhat-7.0 + driver_plugin: ec2 + driver_config: + image_id: ami-a8d369c0 + username: ec2-user + ssh_key: <%= ENV['EC2_SSH_KEY_PATH'] %> + run_list: + - recipe[selinux::disabled] + +- name: fedora-20 + driver_plugin: digital_ocean + driver_config: + size: 2gb + image: fedora-20-x64 + region: <%= ENV['DIGITAL_OCEAN_REGION'] %> + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + +- name: suse-11.3 + driver_plugin: ec2 + driver_config: + image_id: ami-e8084981 + username: root + ssh_key: <%= ENV['EC2_SSH_KEY_PATH'] %> + +- name: debian-7.0 + driver_plugin: gce + driver_config: + image_name: debian-7-wheezy-v20131120 + zone: <%= ENV['GCE_ZONE'] %> + area: <%= ENV['GCE_AREA'] %> + network: <%= ENV['GCE_NETWORK'] %> + username: <%= ENV['GCE_USERNAME'] %> + public_key_path: <%= ENV['GCE_PUBLIC_KEY_PATH'] %> + ssh_key: <%= ENV['GCE_SSH_KEY_PATH'] %> + run_list: + - recipe[apt] + +- name: ubuntu-10.04 + driver_plugin: digital_ocean + driver_config: + size: 2gb + image: ubuntu-10-04-x64 + region: <%= ENV['DIGITAL_OCEAN_REGION'] %> + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + run_list: + - recipe[apt] + +- name: ubuntu-12.04 + driver_plugin: digital_ocean + driver_config: + size: 2gb + image: ubuntu-12-04-x64 + region: <%= ENV['DIGITAL_OCEAN_REGION'] %> + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + run_list: + - recipe[apt] + +- name: ubuntu-14.04 + driver_plugin: digital_ocean + driver_config: + size: 2gb + image: ubuntu-14-04-x64 + region: <%= ENV['DIGITAL_OCEAN_REGION'] %> + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + run_list: + - recipe[apt] + +# - name: omnios-151006 +# driver_plugin: ec2 +# driver_config: +# image_id: ami-35eb835c +# flavor_id: m3.large +# username: root +# ssh_key: <%= ENV['EC2_SSH_KEY_PATH'] %> +# run_list: +# - recipe[ips-omniti] + +# - name: smartos-14.3.0 +# driver_plugin: joyent +# driver_config: +# joyent_image_id: 62f148f8-6e84-11e4-82c5-efca60348b9f +# joyent_flavor_id: g3-standard-4-smartos +# username: root +# ssh_key: <%= ENV['SDC_CLI_IDENTITY'] %> +# busser: +# ruby_bindir: '/opt/local/bin/' +# provisioner: +# sudo: false +# chef_omnibus_url: https://raw.github.com/test-kitchen/kitchen-joyent/master/scripts/install-smartos.sh + +suites: +- name: default + run_list: + - recipe[build-essential::default] + attributes: {} diff --git a/cookbooks/build-essential/.kitchen.yml b/cookbooks/build-essential/.kitchen.yml new file mode 100644 index 0000000..4bae9ac --- /dev/null +++ b/cookbooks/build-essential/.kitchen.yml @@ -0,0 +1,24 @@ +driver: + name: vagrant + +platforms: + - name: centos-5.10 + - name: centos-6.5 + - name: fedora-19 + - name: freebsd-9.2 + run_list: freebsd::portsnap + - name: freebsd-10.0 + run_list: freebsd::portsnap + - name: macosx-10.9 + - name: debian-7.4 + run_list: apt::default + - name: ubuntu-10.04 + run_list: apt::default + - name: ubuntu-12.04 + run_list: apt::default + - name: ubuntu-13.10 + run_list: apt::default + +suites: + - name: default + run_list: build-essential::default diff --git a/cookbooks/build-essential/.rubocop.yml b/cookbooks/build-essential/.rubocop.yml new file mode 100644 index 0000000..d89e28e --- /dev/null +++ b/cookbooks/build-essential/.rubocop.yml @@ -0,0 +1,30 @@ +AllCops: + Exclude: + - bin/**/* + - script/**/* + - vendor/**/* + +AbcSize: + Max: 50 +AlignParameters: + Enabled: false +ClassAndModuleChildren: + Enabled: false +Documentation: + Enabled: false +DoubleNegation: + Enabled: false +Encoding: + Enabled: false +GuardClause: + Enabled: false +LineLength: + Max: 120 +MethodLength: + Max: 20 +PercentLiteralDelimiters: + Enabled: false +SignalException: + Enabled: false +SingleSpaceBeforeFirstArg: + Enabled: false diff --git a/cookbooks/build-essential/.travis.yml b/cookbooks/build-essential/.travis.yml new file mode 100644 index 0000000..67f451d --- /dev/null +++ b/cookbooks/build-essential/.travis.yml @@ -0,0 +1,40 @@ +# Use Travis's cointainer based infrastructure +sudo: false +addons: + apt: + sources: + - chef-stable-precise + packages: + - chefdk + +# Don't `bundle install` +install: echo "skip bundle install" + +branches: + only: + - master + +# Ensure we make ChefDK's Ruby the default +before_script: + - eval "$(/opt/chefdk/bin/chef shell-init bash)" + # We have to install chef-sugar for ChefSpec + - /opt/chefdk/embedded/bin/chef gem install chef-sugar +script: + - /opt/chefdk/embedded/bin/chef --version + - /opt/chefdk/embedded/bin/rubocop --version + - /opt/chefdk/embedded/bin/rubocop + - /opt/chefdk/embedded/bin/foodcritic --version + - /opt/chefdk/embedded/bin/foodcritic . --exclude spec + - /opt/chefdk/embedded/bin/rspec spec + +notifications: + hipchat: + on_change: true + on_failure: true + on_success: false + on_pull_requests: false + rooms: + # Build Statuses + - secure: fk4NTplcjE097Oan2HgZc+Lxx8X9k2QDolwBKZMDNrreFImBgw6HJBwHUv6Hfay2xh7Y720iNFeTTN3Ep0/M4YgmBrwhry3jSMN8TX7SAOGPCC8zIB0ELGGAyQKxDjwwsA18KTbuDkV5yboiUzvY86G5bWT8vfWEd1ljTEnIazQ= + # Release Engineering + - secure: X35KY6kImjVyYiT9gOlRJd26MKh5KQFwxcQm3fF9Y+pnB1v7uB3w6+jzoxhvPN5O2US3xGQsaJOSAB1uhZh+FZOKfZ/ewyXVUcTXrUTC9Mjofd3n33xD68qoI22mntEQilugvC+OPhq9uWyX0OlRhnnT+J56Vq7feSI4ez9e9Og= diff --git a/cookbooks/build-essential/Berksfile b/cookbooks/build-essential/Berksfile new file mode 100644 index 0000000..50e57a3 --- /dev/null +++ b/cookbooks/build-essential/Berksfile @@ -0,0 +1,8 @@ +source 'https://supermarket.chef.io' + +metadata + +group :integration do + cookbook 'apt' + cookbook 'freebsd' +end diff --git a/cookbooks/build-essential/CHANGELOG.md b/cookbooks/build-essential/CHANGELOG.md new file mode 100644 index 0000000..c2a5a10 --- /dev/null +++ b/cookbooks/build-essential/CHANGELOG.md @@ -0,0 +1,125 @@ +build-essential Cookbook CHANGELOG +================================== +This file is used to list changes made in each version of the build-essential cookbook. + +v2.2.3 (2015-04-15) +------------------- +* Don’t install omnibus-build-essential on Solaris 10 - We decided it’s easier to use the old GCC that ships with Solaris 10. +* Use ChefDK for all Travis testing. + +v2.2.2 (2015-03-27) +------------------- +* Update Solar 10’s omnibus-build-essential to 0.0.5 + +v2.2.1 (2015-03-23) +------------------- +* Install GNU Patch on Solaris 11 + +v2.2.0 (2015-03-18) +------------------- +* [solaris] Differentiate between Solaris 10 and 11 +* [solaris] Add ucb compat package +* [solaris] Solaris 10 build essential setup +* Fix metadata to use a string instead of a bool (see #56, #57) + +v2.1.3 (2014-11-18) +------------------- +* Update metadata for supported versions of OS X (10.7+) as noted from + v2.0.0 previously (#38) +* Clarify requirement to have apt package cache updated in README. (#41) +* Fix Xcode CLI installation on OS X (#50) + +v2.1.2 (2014-10-14) +------------------- +* Mac OS X 10.10 Yosemite support + +v2.1.0 (2014-10-14) +------------------- +* Use fully-qualified names when installing FreeBSD package + +v2.0.6 (2014-08-11) +------------------- +* Use the resource form of `remote_file` to prevent context issues + +v2.0.4 (2014-06-06) +------------------- +* [COOK-4661] added patch package to _rhel recipe + + +v2.0.2 (2014-05-02) +------------------- +- Updated documentation about older Chef versions +- Added new SVG badges to the README +- Fix a bug where `potentially_at_compile_time` fails on non-resources + +v2.0.0 (2014-03-13) +------------------- +- Updated tested harnesses to use latest ecosystem tools +- Added support for FreeBSD +- Added support for installing XCode Command Line Tools on OSX (10.7, 10.8, 10.9) +- Created a DSL method for wrapping compile_time vs runtime execution +- Install additional developement tools on some platforms +- Add nicer log and warning messages with helpful information + +**Potentially Breaking Changes** + +- Dropped support for OSX 10.6 +- OSX no longer downloads OSX GCC and uses XCode CLI tools instead +- `build_essential` -> `build-essential` in node attributes +- `compiletime` -> `compile_time` in node attributes +- Cookbook version 2.x no longer supports Chef 10.x + +v1.4.4 (2014-02-27) +------------------- +- [COOK-4245] Wrong package name used for developer tools on OS X 10.9 + +v1.4.2 +------ +### Bug +- **[COOK-3318](https://tickets.chef.io/browse/COOK-3318)** - Use Mixlib::ShellOut instead of Chef::ShellOut + +### New Feature +- **[COOK-3093](https://tickets.chef.io/browse/COOK-3093)** - Add OmniOS support + +### Improvement +- **[COOK-3024](https://tickets.chef.io/browse/COOK-3024)** - Use newer package on SmartOS + +v1.4.0 +------ +This version splits up the default recipe into recipes included based on the node's platform_family. + +- [COOK-2505] - backport omnibus builder improvements + +v1.3.4 +------ +- [COOK-2272] - Complete `platform_family` conversion in build-essential + +v1.3.2 +------ +- [COOK-2069] - build-essential will install osx-gcc-installer when XCode is present + +v1.3.0 +------ +- [COOK-1895] - support smartos + +v1.2.0 +------ +- Add test-kitchen support (source repo only) +- [COOK-1677] - build-essential cookbook support for OpenSuse and SLES +- [COOK-1718] - build-essential cookbook metadata should include scientific +- [COOK-1768] - The apt-get update in build-essentials needs to be renamed + +v1.1.2 +------ +- [COOK-1620] - support OS X 10.8 + +v1.1.0 +------ +- [COOK-1098] - support amazon linux +- [COOK-1149] - support Mac OS X +- [COOK-1296] - allow for compile-time installation of packages through an attribute (see README) + +v1.0.2 +------ +- [COOK-1098] - Add Amazon Linux platform support +- [COOK-1149] - Add OS X platform support diff --git a/cookbooks/build-essential/CONTRIBUTING b/cookbooks/build-essential/CONTRIBUTING new file mode 100644 index 0000000..e781c97 --- /dev/null +++ b/cookbooks/build-essential/CONTRIBUTING @@ -0,0 +1,29 @@ +If you would like to contribute, please open a ticket in JIRA: + +* http://tickets.chef.io + +Create the ticket in the COOK project and use the cookbook name as the +component. + +For all code contributions, we ask that contributors sign a +contributor license agreement (CLA). Instructions may be found here: + +* http://wiki.chef.io/display/chef/How+to+Contribute + +When contributing changes to individual cookbooks, please do not +modify the version number in the metadata.rb. Also please do not +update the CHANGELOG.md for a new version. Not all changes to a +cookbook may be merged and released in the same versions. Chef Software will +handle the version updates during the release process. You are welcome +to correct typos or otherwise make updates to documentation in the +README. + +If a contribution adds new platforms or platform versions, indicate +such in the body of the commit message(s), and update the relevant +COOK ticket. When writing commit messages, it is helpful for others if +you indicate the COOK ticket. For example: + + git commit -m '[COOK-1041] Updated pool resource to correctly delete.' + +In the ticket itself, it is also helpful if you include log output of +a successful Chef run, but this is not absolutely required. diff --git a/cookbooks/build-essential/Gemfile b/cookbooks/build-essential/Gemfile new file mode 100644 index 0000000..c1d9ad3 --- /dev/null +++ b/cookbooks/build-essential/Gemfile @@ -0,0 +1,38 @@ +source 'https://rubygems.org' + +group :lint do + gem 'foodcritic', '~> 3.0' + gem 'rubocop', '= 0.26.1' +end + +group :unit do + gem 'berkshelf', '~> 3.1' + gem 'chefspec', '~> 4.0' +end + +group :kitchen_common do + gem 'test-kitchen', '~> 1.2' +end + +group :kitchen_vagrant do + gem 'kitchen-vagrant', '~> 0.15' +end + +group :kitchen_cloud do + gem 'kitchen-digitalocean', '~> 0.8' + gem 'kitchen-ec2', '~> 0.8' + gem 'kitchen-joyent', '~> 0.1' + gem 'kitchen-gce', '~> 0.2' +end + +group :development do + gem 'ruby_gntp' + gem 'growl' + gem 'rb-fsevent' + gem 'guard', '~> 2.4' + gem 'guard-kitchen' + gem 'guard-foodcritic' + gem 'guard-rspec' + gem 'guard-rubocop' + gem 'rake' +end diff --git a/cookbooks/build-essential/Guardfile b/cookbooks/build-essential/Guardfile new file mode 100644 index 0000000..b878300 --- /dev/null +++ b/cookbooks/build-essential/Guardfile @@ -0,0 +1,35 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +# guard 'kitchen' do +# watch(%r{test/.+}) +# watch(%r{^recipes/(.+)\.rb$}) +# watch(%r{^attributes/(.+)\.rb$}) +# watch(%r{^files/(.+)}) +# watch(%r{^templates/(.+)}) +# watch(%r{^providers/(.+)\.rb}) +# watch(%r{^resources/(.+)\.rb}) +# end + +guard 'foodcritic', cookbook_paths: '.', all_on_start: false do + watch(/attributes\/.+\.rb$/) + watch(/providers\/.+\.rb$/) + watch(/recipes\/.+\.rb$/) + watch(/resources\/.+\.rb$/) + watch('metadata.rb') +end + +guard 'rubocop', all_on_start: false do + watch(/attributes\/.+\.rb$/) + watch(/providers\/.+\.rb$/) + watch(/recipes\/.+\.rb$/) + watch(/resources\/.+\.rb$/) + watch('metadata.rb') +end + +guard :rspec, cmd: 'bundle exec rspec', all_on_start: false, notification: false do + watch(/^libraries\/(.+)\.rb$/) + watch(/^spec\/(.+)_spec\.rb$/) + watch(/^(recipes)\/(.+)\.rb$/) { |m| "spec/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { 'spec' } +end diff --git a/cookbooks/build-essential/LICENSE b/cookbooks/build-essential/LICENSE new file mode 100644 index 0000000..11069ed --- /dev/null +++ b/cookbooks/build-essential/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/cookbooks/build-essential/README.md b/cookbooks/build-essential/README.md new file mode 100644 index 0000000..bd94ff9 --- /dev/null +++ b/cookbooks/build-essential/README.md @@ -0,0 +1,108 @@ +Description +=========== +[![Cookbook Version](http://img.shields.io/cookbook/v/build-essential.svg)][cookbook] +[![Build Status](http://img.shields.io/travis/chef-cookbooks/build-essential.svg)][travis] + +[cookbook]: https://community.chef.io/cookbooks/build-essential +[travis]: http://travis-ci.org/chef-cookbooks/build-essential + +Installs packages required for compiling C software from source. Use this +cookbook if you wish to compile C programs, or install RubyGems with native +extensions. + +Requirements +------------ +Chef 11+ and Ohai 6.14+ are required. For the latest list of supported +platforms, please see the `metadata.rb`. + +**Note for Debian platform family:** On Debian platform-family systems, it is recommended that `apt-get update` be run, to ensure that the package cache is updated. It's not in the scope of this cookbook to do that, as it can [create a duplicate resource](https://tickets.chef.io/browse/CHEF-3694). We recommend using the [apt](https://supermarket.chef.io/cookbooks/apt) cookbook to do this. + +**Note for OmniOS**: Currently, OmniOS's Ruby package is built with +GCC 4.6.3, and the path is hardcoded, as the gcc binaries are not +installed in the default $PATH. This means that in order to install +RubyGems into the "system" Ruby, one must install `developer/gcc46`. +[An issue](https://github.com/omniti-labs/omnios-build/issues/19) is +open upstream w/ OmniOS to rebuild the Ruby package with GCC 4.7.2. + +Attributes +---------- +| Attribute | Default | Description | +|----------------|:-------:|-----------------------------------| +| `compile_time` | `false` | Execute resources at compile time | + + +Usage +----- +Include the build-essential recipe in your run list: + +```sh +knife node run_list add NODE "recipe[build-essential::default]" +``` + +or add the build-essential recipe as a dependency and include it from inside +another cookbook: + +```ruby +include_recipe 'build-essential::default' +``` + +### Gems with C extensions +For RubyGems that include native C extensions you wish to use with Chef, you +should do the following. + +1. Set the `compile_time` attribute to true in your wrapper cookbook or role: + + ```ruby + # Wrapper attribute + default['build-essential']['compile_time'] = true + ``` + + ```ruby + # Role + default_attributes( + 'build-essential' => { + 'compile_time' => true + } + ) + ``` + +1. Ensure that the C libraries, which include files and other assorted "dev" +type packages, are installed in the compile phase after the build-essential +recipe is executed. For example: + + ```ruby + include_recipe 'build-essential::default' + + package('mypackage-devel') { action :nothing }.run_action(:install) + ``` + +1. Use the `chef_gem` resource in your recipe to install the gem with the native +extension: + + ```ruby + chef_gem 'gem-with-native-extension' + ``` + + +License & Authors +----------------- +- Author: Seth Vargo () +- Author: Joshua Timberman () +- Author: Seth Chisamore () + +```text +Copyright 2009-2014, Chef Software, Inc. () + +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/cookbooks/build-essential/Rakefile b/cookbooks/build-essential/Rakefile new file mode 100644 index 0000000..0efd590 --- /dev/null +++ b/cookbooks/build-essential/Rakefile @@ -0,0 +1,65 @@ +require 'bundler/setup' + +# Style tests. Rubocop and Foodcritic +namespace :style do + require 'rubocop/rake_task' + desc 'Run Ruby style checks' + RuboCop::RakeTask.new(:ruby) + + require 'foodcritic' + desc 'Run Chef style checks' + FoodCritic::Rake::LintTask.new(:chef) do |t| + t.options = { + fail_tags: ['any'], + tags: ['~FC005'] + } + end +end + +desc 'Run all style checks' +task style: ['style:chef', 'style:ruby'] + +# Rspec and ChefSpec +require 'rspec/core/rake_task' +desc 'Run ChefSpec examples' +RSpec::Core::RakeTask.new(:spec) + +# Integration tests. Kitchen.ci +require 'kitchen' +namespace :integration do + desc 'Run Test Kitchen with Vagrant' + task :vagrant do + Kitchen.logger = Kitchen.default_file_logger + Kitchen::Config.new.instances.each do |instance| + instance.test(:always) + end + end + + desc 'Run Test Kitchen with cloud plugins' + task :cloud do + run_kitchen = true + if ENV['TRAVIS'] == 'true' && ENV['TRAVIS_PULL_REQUEST'] != 'false' + run_kitchen = false + end + + if run_kitchen + Kitchen.logger = Kitchen.default_file_logger + @loader = Kitchen::Loader::YAML.new(project_config: './.kitchen.cloud.yml') + config = Kitchen::Config.new(loader: @loader) + config.instances.each do |instance| + instance.test(:always) + end + end + end +end + +namespace :travis do + desc 'Run tests on Travis' + task ci: %w(style spec) +end + +# The default rake task should just run it all +task default: %w(travis:ci integration) + +# The default rake task should just run it all +task default: ['style', 'spec', 'integration:vagrant'] diff --git a/cookbooks/build-essential/attributes/default.rb b/cookbooks/build-essential/attributes/default.rb new file mode 100644 index 0000000..ca383ee --- /dev/null +++ b/cookbooks/build-essential/attributes/default.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: build-essential +# Attributes:: default +# +# Copyright 2008-2012, Chef Software, Inc. +# +# 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. +# + +default['build-essential']['compile_time'] = false diff --git a/cookbooks/build-essential/libraries/matchers.rb b/cookbooks/build-essential/libraries/matchers.rb new file mode 100644 index 0000000..fcc5305 --- /dev/null +++ b/cookbooks/build-essential/libraries/matchers.rb @@ -0,0 +1,5 @@ +if defined?(ChefSpec) + def install_xcode_command_line_tools(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:xcode_command_line_tools, :install, resource_name) + end +end diff --git a/cookbooks/build-essential/libraries/timing.rb b/cookbooks/build-essential/libraries/timing.rb new file mode 100644 index 0000000..e75cdbb --- /dev/null +++ b/cookbooks/build-essential/libraries/timing.rb @@ -0,0 +1,124 @@ +# +# Cookbook Name:: build-essential +# Library:: timing +# +# Copyright 2014, Chef Software, Inc. +# +# 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. +# + +# +# This module is used to clean up the recipe DSL and "potentially" execute +# resources at compile time (depending on the value of an attribute). +# +# This library is only for use within the build-essential cookbook. Resources +# inside the potentially_at_compile_time block will not fire notifications in +# some situations. This is fixable, but since none of the resources in this +# cookbook actually use notifications, it is not worth the added technical debt. +# +# TL;DR Don't use this DSL method outside of this cookbook. +# +module BuildEssential + module Timing + # + # Potentially evaluate the given block at compile time, depending on the + # value of the +node['build-essential']['compile_time']+ attribute. + # + # @example + # potentially_at_compile_time do + # package 'apache2' + # end + # + # @param [Proc] block + # the thing to eval + # + def potentially_at_compile_time(&block) + if compile_time? + CompileTime.new(self).evaluate(&block) + else + instance_eval(&block) + end + end + + private + + # + # Checks if the DSL should be evaluated at compile time. + # + # @return [true, false] + # + def compile_time? + check_for_old_attributes! + !!node['build-essential']['compile_time'] + end + + # + # Checks for the presence of the "old" attributes. + # + # @todo Remove in 2.0.0 + # + # @return [void] + # + def check_for_old_attributes! + unless node['build_essential'].nil? + Chef::Log.warn <<-EOH +node['build_essential'] has been changed to node['build-essential'] to match the +cookbook name and community standards. I have gracefully converted the attribute +for you, but this warning and conversion will be removed in the next major +release of the build-essential cookbook. +EOH + node.default['build-essential'] = node['build_essential'] + end + + unless node['build-essential']['compiletime'].nil? + Chef::Log.warn <<-EOH +node['build-essential']['compiletime'] has been deprecated. Please use +node['build-essential']['compile_time'] instead. I have gracefully converted the +attribute for you, but this warning and converstion will be removed in the next +major release of the build-essential cookbook. +EOH + node.default['build-essential']['compile_time'] = node['build-essential']['compiletime'] + end + end + + # + # A class graciously borrowed from Chef Sugar for evaluating a resource at + # compile time in a block. + # + class CompileTime + def initialize(recipe) + @recipe = recipe + end + + def evaluate(&block) + instance_eval(&block) + end + + def method_missing(m, *args, &block) + resource = @recipe.send(m, *args, &block) + if resource.is_a?(Chef::Resource) + actions = Array(resource.action) + resource.action(:nothing) + + actions.each do |action| + resource.run_action(action) + end + end + resource + end + end + end +end + +# Include the timing module into the main recipe DSL +Chef::Recipe.send(:include, BuildEssential::Timing) diff --git a/cookbooks/build-essential/libraries/xcode_command_line_tools.rb b/cookbooks/build-essential/libraries/xcode_command_line_tools.rb new file mode 100644 index 0000000..1f4f049 --- /dev/null +++ b/cookbooks/build-essential/libraries/xcode_command_line_tools.rb @@ -0,0 +1,211 @@ +# +# Cookbook Name:: build-essential +# Library:: xcode_command_line_tools +# +# Copyright 2014, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef + class Resource::XcodeCommandLineTools < Resource::LWRPBase + def self.resource_name + :xcode_command_line_tools + end + + actions :install + default_action :install + + def initialize(name, run_context = nil) + super + + @provider = case node['platform_version'].to_f + when 10.7, 10.8 + Provider::XcodeCommandLineToolsFromDmg + when 10.9, 10.10 + Provider::XcodeCommandLineToolsFromSoftwareUpdate + else + Chef::Log.warn <<-EOH +OSX #{node['platform_version']} is not an officially supported platform for the +build-essential cookbook. I am going to try and install the command line tools +from Software Update, but there is a high probability that it will fail... + +If you have tested and verified OSX #{node['platform_version']} and you are sick +of seeing this warning in your Chef Client runs, please submit a Pull Request to +https://github.com/chef-cookbooks/build-essential and add this version of OSX +to provider list. +EOH + Provider::XcodeCommandLineToolsFromSoftwareUpdate + end + end + end +end + +# +# This is a legacy provider for installing OSX from DMGs. It only supports OSX +# versions 10.7 and 10.8 and will (hopefully) be deprecated in the future. It +# downloads a remote .dmg file, mounts it, installs it, and unmounts it +# automatically. In later versions of OSX, the operating system handles this for +# the end user. +# +class Chef + class Provider::XcodeCommandLineToolsFromDmg < Provider::LWRPBase + action(:install) do + if installed? + Chef::Log.debug("#{new_resource} already installed - skipping") + else + converge_by("Install #{new_resource}") do + download + attach + install + detach + end + end + end + + private + + # + # Determine if the XCode Command Line Tools are installed + # + # @return [true, false] + # + def installed? + cmd = Mixlib::ShellOut.new('pkgutil --pkgs=com.apple.pkg.DeveloperToolsCLI') + cmd.run_command + cmd.error! + true + rescue Mixlib::ShellOut::ShellCommandFailed + false + end + + # + # The path where the dmg should be cached on disk. + # + # @return [String] + # + def dmg_cache_path + ::File.join(Chef::Config[:file_cache_path], 'osx-command-line-tools.dmg') + end + + # + # The path where the dmg should be downloaded from. This is intentionally + # not a configurable object by the end user. If you do not like where we + # are downloading XCode from - too bad. + # + # @return [String] + # + def dmg_remote_source + case node['platform_version'].to_f + when 10.7 + 'http://devimages.apple.com/downloads/xcode/command_line_tools_for_xcode_os_x_lion_april_2013.dmg' + when 10.8 + 'http://devimages.apple.com/downloads/xcode/command_line_tools_for_xcode_os_x_mountain_lion_march_2014.dmg' + else + raise "Unknown DMG download URL for OSX #{node['platform_version']}" + end + end + + # + # The path where the volume should be mounted. + # + # @return [String] + # + def mount_path + ::File.join(Chef::Config[:file_cache_path], 'osx-command-line-tools') + end + + # + # Action: download the remote dmg. + # + # @return [void] + # + def download + remote_file = Resource::RemoteFile.new(dmg_cache_path, run_context) + remote_file.source(dmg_remote_source) + remote_file.backup(false) + remote_file.run_action(:create) + end + + # + # Action: attach the dmg (basically, double-click on it) + # + # @return [void] + # + def attach + execute %|hdiutil attach "#{dmg_cache_path}" -mountpoint "#{mount_path}"| + end + + # + # Action: install the package inside the dmg + # + # @return [void] + # + def install + execute %|installer -package "$(find '#{mount_path}' -name *.mpkg)" -target "/"| + end + + # + # Action: detach the dmg (basically, drag it to eject on the dock) + # + # @return [void] + # + def detach + execute %|hdiutil detach "#{mount_path}"| + end + end +end + +class Chef + class Provider::XcodeCommandLineToolsFromSoftwareUpdate < Provider::LWRPBase + action(:install) do + if installed? + Chef::Log.debug("#{new_resource} already installed - skipping") + else + converge_by("Install #{new_resource}") do + # This script was graciously borrowed and modified from Tim Sutton's + # osx-vm-templates at https://github.com/timsutton/osx-vm-templates/blob/b001475df54a9808d3d56d06e71b8fa3001fff42/scripts/xcode-cli-tools.sh + execute 'install XCode Command Line tools' do + # rubocop:disable Metrics/LineLength + command <<-EOH.gsub(/^ {14}/, '') + # create the placeholder file that's checked by CLI updates' .dist code + # in Apple's SUS catalog + touch /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress + # find the CLI Tools update + PROD=$(softwareupdate -l | grep "\*.*Command Line" | head -n 1 | awk -F"*" '{print $2}' | sed -e 's/^ *//' | tr -d '\n') + # install it + softwareupdate -i "$PROD" -v + EOH + # rubocop:enable Metrics/LineLength + end + end + end + end + + private + + # + # Determine if the XCode Command Line Tools are installed + # + # @return [true, false] + # + def installed? + cmd = Mixlib::ShellOut.new('pkgutil --pkgs=com.apple.pkg.CLTools_Executables') + cmd.run_command + cmd.error! + true + rescue Mixlib::ShellOut::ShellCommandFailed + false + end + end +end diff --git a/cookbooks/build-essential/matrix b/cookbooks/build-essential/matrix new file mode 100644 index 0000000..c7ea413 --- /dev/null +++ b/cookbooks/build-essential/matrix @@ -0,0 +1,10 @@ + matrix: + - KITCHEN_INSTANCE='default-centos-58 + - KITCHEN_INSTANCE='default-centos-64 + - KITCHEN_INSTANCE='default-amazon-201309 + - KITCHEN_INSTANCE='default-fedora-19 + - KITCHEN_INSTANCE='default-debian-70 + - KITCHEN_INSTANCE='default-ubuntu-1004 + - KITCHEN_INSTANCE='default-ubuntu-1204 + - KITCHEN_INSTANCE='default-ubuntu-1310 + - KITCHEN_INSTANCE='default-smartos-1330 diff --git a/cookbooks/build-essential/metadata.json b/cookbooks/build-essential/metadata.json new file mode 100644 index 0000000..bed677f --- /dev/null +++ b/cookbooks/build-essential/metadata.json @@ -0,0 +1,64 @@ +{ + "name": "build-essential", + "description": "Installs C compiler / build tools", + "long_description": "", + "maintainer": "Chef Software, Inc.", + "maintainer_email": "cookbooks@chef.io", + "license": "Apache 2.0", + "platforms": { + "amazon": ">= 0.0.0", + "centos": ">= 0.0.0", + "debian": ">= 0.0.0", + "fedora": ">= 0.0.0", + "freebsd": ">= 0.0.0", + "mac_os_x": ">= 10.7.0", + "mac_os_x_server": ">= 10.7.0", + "oracle": ">= 0.0.0", + "redhat": ">= 0.0.0", + "scientific": ">= 0.0.0", + "smartos": ">= 0.0.0", + "suse": ">= 0.0.0", + "ubuntu": ">= 0.0.0" + }, + "dependencies": { + + }, + "recommendations": { + + }, + "suggestions": { + "pkgutil": ">= 0.0.0" + }, + "conflicting": { + + }, + "providing": { + + }, + "replacing": { + + }, + "attributes": { + "build-essential/compile_time": { + "display_name": "Build Essential Compile Time Execution", + "description": "Execute resources at compile time.", + "default": "false", + "recipes": [ + "build-essential::default" + ], + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional" + } + }, + "groupings": { + + }, + "recipes": { + "build-essential": "Installs packages required for compiling C software from source." + }, + "version": "2.2.3" +} diff --git a/cookbooks/build-essential/metadata.rb b/cookbooks/build-essential/metadata.rb new file mode 100644 index 0000000..d432a50 --- /dev/null +++ b/cookbooks/build-essential/metadata.rb @@ -0,0 +1,29 @@ +name 'build-essential' +maintainer 'Chef Software, Inc.' +maintainer_email 'cookbooks@chef.io' +license 'Apache 2.0' +description 'Installs C compiler / build tools' +version '2.2.3' +recipe 'build-essential', 'Installs packages required for compiling C software from source.' + +supports 'amazon' +supports 'centos' +supports 'debian' +supports 'fedora' +supports 'freebsd' +supports 'mac_os_x', '>= 10.7.0' +supports 'mac_os_x_server', '>= 10.7.0' +supports 'oracle' +supports 'redhat' +supports 'scientific' +supports 'smartos' +supports 'suse' +supports 'ubuntu' + +suggests 'pkgutil' # Solaris 2 + +attribute 'build-essential/compile_time', + display_name: 'Build Essential Compile Time Execution', + description: 'Execute resources at compile time.', + default: 'false', + recipes: ['build-essential::default'] diff --git a/cookbooks/build-essential/recipes/_debian.rb b/cookbooks/build-essential/recipes/_debian.rb new file mode 100644 index 0000000..217032b --- /dev/null +++ b/cookbooks/build-essential/recipes/_debian.rb @@ -0,0 +1,28 @@ +# +# Cookbook Name:: build-essential +# Recipe:: debian +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +potentially_at_compile_time do + package 'autoconf' + package 'binutils-doc' + package 'bison' + package 'build-essential' + package 'flex' + package 'gettext' + package 'ncurses-dev' +end diff --git a/cookbooks/build-essential/recipes/_fedora.rb b/cookbooks/build-essential/recipes/_fedora.rb new file mode 100644 index 0000000..72715b7 --- /dev/null +++ b/cookbooks/build-essential/recipes/_fedora.rb @@ -0,0 +1,31 @@ +# +# Cookbook Name:: build-essential +# Recipe:: fedora +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +potentially_at_compile_time do + package 'autoconf' + package 'bison' + package 'flex' + package 'gcc' + package 'gcc-c++' + package 'gettext' + package 'kernel-devel' + package 'make' + package 'm4' + package 'ncurses-devel' +end diff --git a/cookbooks/build-essential/recipes/_freebsd.rb b/cookbooks/build-essential/recipes/_freebsd.rb new file mode 100644 index 0000000..48c12c9 --- /dev/null +++ b/cookbooks/build-essential/recipes/_freebsd.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: build-essential +# Recipe:: freebsd +# +# Copyright 2014, Chef Software, Inc. +# +# 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. +# + +potentially_at_compile_time do + package 'devel/gmake' + package 'devel/autoconf' + package 'devel/m4' +end diff --git a/cookbooks/build-essential/recipes/_mac_os_x.rb b/cookbooks/build-essential/recipes/_mac_os_x.rb new file mode 100644 index 0000000..f577613 --- /dev/null +++ b/cookbooks/build-essential/recipes/_mac_os_x.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: build-essential +# Recipe:: mac_os_x +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +potentially_at_compile_time do + xcode_command_line_tools 'install' +end diff --git a/cookbooks/build-essential/recipes/_omnios.rb b/cookbooks/build-essential/recipes/_omnios.rb new file mode 100644 index 0000000..cba2bd7 --- /dev/null +++ b/cookbooks/build-essential/recipes/_omnios.rb @@ -0,0 +1,33 @@ +# +# Cookbook Name:: build-essential +# Recipe:: omnios +# +# Copyright 2013, Chef Software, Inc. +# +# 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. +# + +potentially_at_compile_time do + package 'developer/gcc47' + package 'developer/object-file' + package 'developer/linker' + package 'developer/library/lint' + package 'developer/build/gnu-make' + package 'system/header' + package 'system/library/math/header-math' +end + +# Per OmniOS documentation, the gcc bin dir isn't in the default +# $PATH, so add it to the running process environment +# http://omnios.omniti.com/wiki.php/DevEnv +ENV['PATH'] = "#{ENV['PATH']}:/opt/gcc-4.7.2/bin" diff --git a/cookbooks/build-essential/recipes/_rhel.rb b/cookbooks/build-essential/recipes/_rhel.rb new file mode 100644 index 0000000..e2b08a6 --- /dev/null +++ b/cookbooks/build-essential/recipes/_rhel.rb @@ -0,0 +1,36 @@ +# +# Cookbook Name:: build-essential +# Recipe:: rhel +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +potentially_at_compile_time do + package 'autoconf' + package 'bison' + package 'flex' + package 'gcc' + package 'gcc-c++' + package 'kernel-devel' + package 'make' + package 'm4' + package 'patch' + + # Ensure GCC 4 is available on older pre-6 EL + if node['platform_version'].to_i < 6 + package 'gcc44' + package 'gcc44-c++' + end +end diff --git a/cookbooks/build-essential/recipes/_smartos.rb b/cookbooks/build-essential/recipes/_smartos.rb new file mode 100644 index 0000000..f969bb8 --- /dev/null +++ b/cookbooks/build-essential/recipes/_smartos.rb @@ -0,0 +1,27 @@ +# +# Cookbook Name:: build-essential +# Recipe:: smartos +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +potentially_at_compile_time do + package 'autoconf' + package 'binutils' + package 'build-essential' + package 'gcc47' + package 'gmake' + package 'pkg-config' +end diff --git a/cookbooks/build-essential/recipes/_solaris2.rb b/cookbooks/build-essential/recipes/_solaris2.rb new file mode 100644 index 0000000..32ed8a7 --- /dev/null +++ b/cookbooks/build-essential/recipes/_solaris2.rb @@ -0,0 +1,48 @@ +# +# Cookbook Name:: build-essential +# Recipe:: solaris2 +# +# Copyright 2013, Chef Software, Inc. +# +# 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. +# + +case node['platform_version'].to_f +when 5.10 + # You should install the following packages from the Solaris 10 DVD: + # + # SUNWbison + # SUNWgcc + # SUNWggrp + # SUNWgmake + # SUNWgtar + # +when 5.11 + potentially_at_compile_time do + package 'autoconf' + package 'automake' + package 'bison' + package 'gnu-coreutils' + package 'flex' + package 'gcc' + package 'gcc-3' + package 'gnu-grep' + package 'gnu-make' + package 'gnu-patch' + package 'gnu-tar' + package 'pkg-config' + package 'ucb' + end +else + raise "Sorry, we don't support Solaris version #{node['platform_version']} at this juncture." +end diff --git a/cookbooks/build-essential/recipes/_suse.rb b/cookbooks/build-essential/recipes/_suse.rb new file mode 100644 index 0000000..6618c0e --- /dev/null +++ b/cookbooks/build-essential/recipes/_suse.rb @@ -0,0 +1,29 @@ +# +# Cookbook Name:: build-essential +# Recipe:: suse +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +potentially_at_compile_time do + package 'autoconf' + package 'bison' + package 'flex' + package 'gcc' + package 'gcc-c++' + package 'kernel-default-devel' + package 'make' + package 'm4' +end diff --git a/cookbooks/build-essential/recipes/default.rb b/cookbooks/build-essential/recipes/default.rb new file mode 100644 index 0000000..46bcad7 --- /dev/null +++ b/cookbooks/build-essential/recipes/default.rb @@ -0,0 +1,29 @@ +# +# Cookbook Name:: build-essential +# Recipe:: default +# +# Copyright 2008-2009, Chef Software, Inc. +# +# 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 + include_recipe "build-essential::_#{node['platform_family']}" +rescue Chef::Exceptions::RecipeNotFound + Chef::Log.warn <<-EOH +A build-essential recipe does not exist for '#{node['platform_family']}'. This +means the build-essential cookbook does not have support for the +#{node['platform_family']} family. If you are not compiling gems with native +extensions or building packages from source, this will likely not affect you. +EOH +end diff --git a/cookbooks/chef-solo-search/.gitignore b/cookbooks/chef-solo-search/.gitignore new file mode 100644 index 0000000..0a0905a --- /dev/null +++ b/cookbooks/chef-solo-search/.gitignore @@ -0,0 +1,2 @@ +/Gemfile.lock +/tests/gemfiles/*.lock diff --git a/cookbooks/chef-solo-search/.travis.yml b/cookbooks/chef-solo-search/.travis.yml new file mode 100644 index 0000000..4e5cf34 --- /dev/null +++ b/cookbooks/chef-solo-search/.travis.yml @@ -0,0 +1,9 @@ +before_install: gem update --system 1.8.25 +before_script: chef-solo -v +rvm: + - 1.9.3 + - 1.9.2 + - 1.8.7 +gemfile: + - tests/gemfiles/Gemfile.11 + - tests/gemfiles/Gemfile.10 diff --git a/cookbooks/chef-solo-search/CHANGELOG b/cookbooks/chef-solo-search/CHANGELOG new file mode 100644 index 0000000..a33d492 --- /dev/null +++ b/cookbooks/chef-solo-search/CHANGELOG @@ -0,0 +1,31 @@ +========================== +chef-solo-search Changelog +========================== + +Version 0.5.1, September 19, 2013 +--------------------------------- +* Added missing metadata.json file + + +Version 0.5.0, September 19, 2013 +--------------------------------- +* Allow node data bag path to be configured +* updated install instructions with focus on Chef 11 +* Fixed Treetop dependency such that the omnibus installer is working +* Added proper ruby Gemfile +* use rake as runner for the tests +* Fixed Travis CI builds + + +Version 0.4.0, March 8, 2013 +---------------------------- +* Added support for 'chef_environment:_default' to queries. +* Special case for lucene "anything range" [* TO *] +* Added support for ruby bundler +* Added support for Berkshelf + +Version 0.3.0, July 27, 2011 +---------------------------- +First release of chef-solo-search as a self-contained project. +Search functionality has been added to the original concept and the search +extension as well as the data bags extension are shipped in libraries/ diff --git a/cookbooks/chef-solo-search/Gemfile b/cookbooks/chef-solo-search/Gemfile new file mode 100644 index 0000000..b68f200 --- /dev/null +++ b/cookbooks/chef-solo-search/Gemfile @@ -0,0 +1,8 @@ +source 'https://rubygems.org' + +gem 'chef', '>= 10.4' +gem 'treetop' +gem 'rake' +gem 'ruby-wmi' +gem 'win32-service', :platforms => [:mswin, :mingw] + diff --git a/cookbooks/chef-solo-search/LICENSE b/cookbooks/chef-solo-search/LICENSE new file mode 100644 index 0000000..c04563e --- /dev/null +++ b/cookbooks/chef-solo-search/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/cookbooks/chef-solo-search/NOTICE b/cookbooks/chef-solo-search/NOTICE new file mode 100644 index 0000000..26adc44 --- /dev/null +++ b/cookbooks/chef-solo-search/NOTICE @@ -0,0 +1,23 @@ +======================== +chef-solo-search Notices +======================== + +Developed at edelight GmbH (http://www.edelight-group.com/). + +Contributors: + + * Arjun Singh + * Brian p o'rourke + * Chris Roberts + * Greg Karékinian + * Jeff Wallace + * Michael Glass + * Miquel Torres + * Markus Korn + * Matt Gleeson + * Patrick Debois + * Patrick Wyatt + * Paweł Pacana + * Seth Chisamore + * Teemu Matilainen + * Tyler Rick diff --git a/cookbooks/chef-solo-search/README.md b/cookbooks/chef-solo-search/README.md new file mode 100644 index 0000000..e9c97b1 --- /dev/null +++ b/cookbooks/chef-solo-search/README.md @@ -0,0 +1,148 @@ +# chef-solo-search + +[![Build Status](https://travis-ci.org/edelight/chef-solo-search.png?branch=master)](https://travis-ci.org/edelight/chef-solo-search) + +Chef-solo-search is a cookbook library that adds data bag search powers +to Chef Solo. Data bag support was added to Chef Solo by Chef 0.10.4. +Please see *Supported queries* for a list of query types which are supported. + +## Requirements + + * ruby >= 1.8 + * ruby-chef >= 0.10.4 + +## Installation + +Install this cookbook into your Chef repository using your favorite cookbook +management tool +([Librarian](https://github.com/applicationsonline/librarian-chef), +[Berkshelf](https://github.com/RiotGames/berkshelf), knife...). + +In Chef 11, you must either add this to the run list of the nodes where it's used or include it as a dependency in the recipes that use it. [See changes in Chef 11.](http://docs.opscode.com/breaking_changes_chef_11.html#non-recipe-file-evaluation-includes-dependencies) + +Now you have to make sure chef-solo knows about data bags, therefore add + + data_bag_path "/data_bags" + +to the config file of chef-solo (defaults to /etc/chef/solo.rb). + +The same for your roles, add + + role_path "/roles" + +## Supported queries + +The search methods supports a basic sub-set of the lucene query language. +Sample supported queries are: + +### General queries: + + search(:users, "*:*") + search(:users) + search(:users, nil) + getting all items in ':users' + search(:users, "username:*") + search(:users, "username:[* TO *]") + getting all items from ':users' which have a 'username' attribute + search(:users, "(NOT username:*)") + search(:users, "(NOT username:[* TO *])") + getting all items from ':users' which don't have a 'username' attribute + +### Queries on attributes with string values: + + search(:users, "username:speedy") + getting all items from ':users' with username equals 'speedy' + search(:users, "NOT username:speedy") + getting all items from ':users' with username is unequal to 'speedy' + search(:users, "username:spe*") + getting all items which 'username'-value begins with 'spe' + +### Queries on attributes with array values: + + search(:users, "children:tom") + getting all items which 'children' attribute contains 'tom' + search(:users, "children:t*") + getting all items which have at least one element in 'children' + which starts with 't' + +### Queries on attributes with boolean values: + + search(:users, "married:true") + +### Queries in attributes with integer values: + + search(:users, "age:35") + +### OR conditions in queries: + + search(:users, "age:42 OR age:22") + +### AND conditions in queries: + + search(:users, "married:true AND age:35") + +### NOT condition in queries: + + search(:users, "children:tom NOT gender:female") + +### More complex queries: + + search(:users, "children:tom NOT gender:female AND age:42") + + +## Supported Objects +The search methods have support for 'roles', 'nodes' and 'databags'. + +### Roles +You can use the standard role objects in json form and put them into your role path + + { + "name": "monitoring", + "default_attributes": { }, + "override_attributes": { }, + "json_class": "Chef::Role", + "description": "This is just a monitoring role, no big deal.", + "run_list": [ + ], + "chef_type": "role" + + +### Nodes +Nodes are injected through a databag called 'node'. Create a databag called 'node' and put your json files there +You can use the standard node objects in json form. + + { + "id": "vagrant", + "name": "vagrant-vm", + "chef_environment": "_default", + "json_class": "Chef::Node", + "automatic": { + "hostname": "vagrant.vm", + "os": "centos" + }, + "normal": { + }, + "chef_type": "node", + "default": { + }, + "override": { + }, + "run_list": [ + "role[monitoring]" + ] + } + +### Databags +You can use the standard databag objects in json form + + { + "id": "my-ssh", + "hostgroup_name": "all", + "command_line": "$USER1$/check_ssh $HOSTADDRESS$" + } + +## Running tests + +Running tests is as simple as: + + % rake test diff --git a/cookbooks/chef-solo-search/Rakefile b/cookbooks/chef-solo-search/Rakefile new file mode 100644 index 0000000..8bb4eab --- /dev/null +++ b/cookbooks/chef-solo-search/Rakefile @@ -0,0 +1,10 @@ +#!/usr/bin/env rake +require "rake/testtask" + +Rake::TestTask.new do |t| + t.pattern = "tests/test_*.rb" + t.libs = %w(libraries) +end + +desc "Run tests" +task :default => :test diff --git a/cookbooks/chef-solo-search/libraries/search.rb b/cookbooks/chef-solo-search/libraries/search.rb new file mode 100644 index 0000000..5c1d155 --- /dev/null +++ b/cookbooks/chef-solo-search/libraries/search.rb @@ -0,0 +1,74 @@ +# +# Copyright 2011, edelight GmbH +# +# Authors: +# Markus Korn +# Seth Chisamore +# +# 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. +# + +if Chef::Config[:solo] + + # add currrent dir to load path + $: << File.dirname(__FILE__) + + # All chef/solr_query/* classes were removed in Chef 11; Load vendored copy + # that ships with this cookbook + $: << File.expand_path("vendor", File.dirname(__FILE__)) if Chef::VERSION.to_i >= 11 + + # Ensure the treetop gem is installed and available + begin + require 'treetop' + rescue LoadError + run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + chef_gem = Chef::Resource::ChefGem.new("treetop", run_context) + chef_gem.version('>= 1.4') + chef_gem.run_action(:install) + end + + require 'search/overrides' + require 'search/parser' + + module Search; class Helper; end; end + + # The search and data_bag related methods moved form `Chef::Mixin::Language` + # to `Chef::DSL::DataQuery` in Chef 11. + if Chef::VERSION.to_i >= 11 + module Chef::DSL::DataQuery + def self.included(base) + base.send(:include, Search::Overrides) + end + end + Search::Helper.send(:include, Chef::DSL::DataQuery) + else + module Chef::Mixin::Language + def self.included(base) + base.send(:include, Search::Overrides) + end + end + Search::Helper.send(:include, Chef::Mixin::Language) + end + + class Chef + class Search + class Query + def initialize(*args) + end + def search(*args, &block) + ::Search::Helper.new.search(*args, &block) + end + end + end + end +end diff --git a/cookbooks/chef-solo-search/libraries/search/overrides.rb b/cookbooks/chef-solo-search/libraries/search/overrides.rb new file mode 100644 index 0000000..6189621 --- /dev/null +++ b/cookbooks/chef-solo-search/libraries/search/overrides.rb @@ -0,0 +1,100 @@ +# +# Copyright 2011, edelight GmbH +# +# Authors: +# Markus Korn +# Seth Chisamore +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +module Search + module Overrides + # Overwrite the search method of recipes to operate locally by using + # data found in data_bags. + # Only very basic lucene syntax is supported and also sorting the result + # is not implemented, if this search method does not support a given query + # an exception is raised. + # This search() method returns a block iterator or an Array, depending + # on how this method is called. + def search(obj, query=nil, sort=nil, start=0, rows=1000, &block) + if !sort.nil? + raise "Sorting search results is not supported" + end + _query = Query.parse(query) + if _query.nil? + raise "Query #{query} is not supported" + end + _result = [] + + case obj + when :node + nodes = search_nodes(_query, start, rows, &block) + _result += nodes + when :role + roles = search_roles(_query, start, rows, &block) + _result += roles + else + bags = search_data_bag(_query, obj, start, rows, &block) + _result += bags + end + + + if block_given? + pos = 0 + while (pos >= start and pos < (start + rows) and pos < _result.size) + yield _result[pos] + pos += 1 + end + else + return _result.slice(start, rows) + end + end + + def search_nodes(_query, start, rows, &block) + _result = [] + node_path = Chef::Config[:nodes_path] || File.join(Chef::Config[:data_bag_path], "node") + Dir.glob(File.join(node_path, "*.json")).map do |f| + # parse and hashify the node + node = Chef::JSONCompat.from_json(IO.read(f)) + if _query.match(node.to_hash) + _result << node + end + end + return _result + end + + def search_roles(_query, start, rows, &block) + _result = [] + Dir.glob(File.join(Chef::Config[:role_path], "*.json")).map do |f| + # parse and hashify the role + role = Chef::JSONCompat.from_json(IO.read(f)) + if _query.match(role.to_hash) + _result << role + end + end + return _result + end + + def search_data_bag(_query, bag_name, start, rows, &block) + _result = [] + data_bag(bag_name.to_s).each do |bag_item_id| + bag_item = data_bag_item(bag_name.to_s, bag_item_id) + if _query.match(bag_item) + _result << bag_item + end + end + return _result + end + end +end diff --git a/cookbooks/chef-solo-search/libraries/search/parser.rb b/cookbooks/chef-solo-search/libraries/search/parser.rb new file mode 100644 index 0000000..0955630 --- /dev/null +++ b/cookbooks/chef-solo-search/libraries/search/parser.rb @@ -0,0 +1,222 @@ +# +# Copyright 2011, edelight GmbH +# +# Authors: +# Markus Korn +# +# 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. +# + +require 'chef/solr_query/query_transform' + +# mock QueryTransform such that we can access the location of the lucene grammar +class Chef + class SolrQuery + class QueryTransform + def self.base_path + class_variable_get(:@@base_path) + end + end + end +end + +def build_flat_hash(hsh, prefix="") + result = {} + hsh.each_pair do |key, value| + if value.kind_of?(Hash) + result.merge!(build_flat_hash(value, "#{prefix}#{key}_")) + else + result[prefix+key] = value + end + end + result +end + +module Lucene + + class Term < Treetop::Runtime::SyntaxNode + # compares a query value and a value, tailing '*'-wildcards are handled correctly. + # Value can either be a string or an array, all other objects are converted + # to a string and than checked. + def match( value ) + if value.is_a?(Array) + value.any?{ |x| self.match(x) } + else + File.fnmatch(self.text_value, value.to_s) + end + end + end + + class Field < Treetop::Runtime::SyntaxNode + # simple field -> value matches, supporting tailing '*'-wildcards in keys + # as well as in values + def match( item ) + keys = self.elements[0].match(item) + if keys.nil? + false + else + keys.any?{ |key| self.elements[1].match(item[key]) } + end + end + end + + # we don't support range matches + # range of integers would be easy to implement + # but string ranges are hard + class FiledRange < Treetop::Runtime::SyntaxNode + end + + # we handle '[* TO *]' as a special case since it is common in + # cookbooks for matching the existence of keys + class InclFieldRange + def match(item) + field = self.elements[0].text_value + range_start = self.elements[1].transform + range_end = self.elements[2].transform + if range_start == "*" and range_end == "*" + !!item[field] + else + raise "Ranges not really supported yet" + end + end + end + + class ExclFieldRange < FieldRange + end + + class RangeValue < Treetop::Runtime::SyntaxNode + end + + class FieldName < Treetop::Runtime::SyntaxNode + def match( item ) + if self.text_value.count("_") > 0 + item.merge!(build_flat_hash(item)) + end + if self.text_value.end_with?("*") + part = self.text_value.chomp("*") + item.keys.collect{ |key| key.start_with?(part)? key: nil}.compact + else + if item.has_key?(self.text_value) + [self.text_value,] + else + nil + end + end + end + end + + class Body < Treetop::Runtime::SyntaxNode + def match( item ) + self.elements[0].match( item ) + end + end + + class Group < Treetop::Runtime::SyntaxNode + def match( item ) + self.elements[0].match(item) + end + end + + class BinaryOp < Treetop::Runtime::SyntaxNode + def match( item ) + self.elements[1].match( + self.elements[0].match(item), + self.elements[2].match(item) + ) + end + end + + class OrOperator < Treetop::Runtime::SyntaxNode + def match( cond1, cond2 ) + cond1 or cond2 + end + end + + class AndOperator < Treetop::Runtime::SyntaxNode + def match( cond1, cond2 ) + cond1 and cond2 + end + end + + # we don't support fuzzy string matching + class FuzzyOp < Treetop::Runtime::SyntaxNode + end + + class BoostOp < Treetop::Runtime::SyntaxNode + end + + class FuzzyParam < Treetop::Runtime::SyntaxNode + end + + class UnaryOp < Treetop::Runtime::SyntaxNode + def match( item ) + self.elements[0].match( + self.elements[1].match(item) + ) + end + end + + class NotOperator < Treetop::Runtime::SyntaxNode + def match( cond ) + not cond + end + end + + class RequiredOperator < Treetop::Runtime::SyntaxNode + end + + class ProhibitedOperator < Treetop::Runtime::SyntaxNode + end + + class Phrase < Treetop::Runtime::SyntaxNode + # a quoted ::Term + def match( value ) + self.elements[0].match(value) + end + end +end + +class Query + # initialize the parser by using the grammar shipped with chef + @@grammar = File.join(Chef::SolrQuery::QueryTransform.base_path, "lucene.treetop") + Treetop.load(@@grammar) + @@parser = LuceneParser.new + + def self.parse(data) + # parse the query into a query tree + if data.nil? + data = "*:*" + end + tree = @@parser.parse(data) + if tree.nil? + msg = "Parse error at offset: #{@@parser.index}\n" + msg += "Reason: #{@@parser.failure_reason}" + raise "Query #{data} is not supported: #{msg}" + end + self.clean_tree(tree) + tree + end + + private + + def self.clean_tree(root_node) + # remove all SyntaxNode elements from the tree, we don't need them as + # the related ruby class already knowns what to do. + return if root_node.elements.nil? + root_node.elements.delete_if do |node| + node.class.name == "Treetop::Runtime::SyntaxNode" + end + root_node.elements.each { |node| self.clean_tree(node) } + end +end + diff --git a/cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/lucene.treetop b/cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/lucene.treetop new file mode 100644 index 0000000..df2d180 --- /dev/null +++ b/cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/lucene.treetop @@ -0,0 +1,150 @@ +# +# Author:: Seth Falcon () +# Copyright:: Copyright (c) 2010-2011 Opscode, Inc. +# 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. +# + +grammar Lucene + + rule body + (expression / space)* + end + + rule expression + operation / group / field / field_range / term / string + end + + rule term + keyword valid_letter+ / !keyword !"?" valid_letter + end + + rule field + field_name ":" (term/string/group) + end + + rule field_range + field_name ":" "[" range_value " TO " range_value "]" + / + field_name ":" "{" range_value " TO " range_value "}" + end + + rule field_name + !keyword valid_letter+ + end + + rule range_value + valid_letter+ / "*" + end + + rule group + space? '(' body ')' space? + end + + rule operation + binary_op / unary_op / fuzzy_op / boost_op + end + + rule unary_op + not_op / required_op / prohibited_op + end + + rule binary_op + (group / field / field_range / term) space? boolean_operator space+ body + end + + rule boolean_operator + and_operator / or_operator + end + + rule and_operator + 'AND' / '&&' + end + + rule or_operator + 'OR' / '||' + end + + rule not_op + not_operator space (group / field / field_range / term / string) + / + bang_operator space? (group / field / field_range / term / string) + end + + rule not_operator + 'NOT' + end + + rule bang_operator + '!' + end + + rule required_op + !valid_letter required_operator (term/string) + / + required_operator (term/string) + end + + rule required_operator + '+' + end + + rule prohibited_op + !valid_letter prohibited_operator (field/field_range/term/string) + end + + rule prohibited_operator + '-' + end + + rule boost_op + (term/string) '^' fuzzy_param + end + + rule fuzzy_op + (term/string) '~' fuzzy_param? (space / !valid_letter) + end + + rule fuzzy_param + [0-9] '.'? [0-9] / [0-9]+ + end + + rule string + '"' term (space term)* '"' + end + + rule keyword + 'AND' / 'OR' / 'NOT' + end + + rule valid_letter + start_letter+ ([a-zA-Z0-9@*?_.-] / '\\' special_char)* + end + + rule start_letter + [a-zA-Z0-9@._*] / '\\' special_char + end + + rule end_letter + [a-zA-Z0-9*?_.] / '\\' special_char + end + + rule special_char + [-+&|!(){}\[\]^"~*?:\\] + end + + rule space + [\s]+ + end +end diff --git a/cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/lucene_nodes.rb b/cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/lucene_nodes.rb new file mode 100644 index 0000000..b50304f --- /dev/null +++ b/cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/lucene_nodes.rb @@ -0,0 +1,285 @@ +# +# Author:: Seth Falcon () +# Copyright:: Copyright (c) 2010-2011 Opscode, Inc. +# 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. +# + +require 'treetop' + +module Lucene + SEP = "__=__" + + class Term < Treetop::Runtime::SyntaxNode + def to_array + "T:#{self.text_value}" + end + + def transform + self.text_value + end + end + + class Field < Treetop::Runtime::SyntaxNode + def to_array + field = self.elements[0].text_value + term = self.elements[1].to_array + "(F:#{field} #{term})" + end + + def transform + field = self.elements[0].text_value + term = self.elements[1] + if term.is_a? Phrase + str = term.transform + # remove quotes + str = str[1 ... (str.length - 1)] + "content:\"#{field}#{SEP}#{str}\"" + else + "content:#{field}#{SEP}#{term.transform}" + end + end + end + + class FieldRange < Treetop::Runtime::SyntaxNode + + def to_array + field = self.elements[0].text_value + range_start = self.elements[1].to_array + range_end = self.elements[2].to_array + "(FR:#{field} #{left}#{range_start}#{right} #{left}#{range_end}#{right})" + end + + def transform + field = self.elements[0].text_value + range_start = self.elements[1].transform + range_end = self.elements[2].transform + # FIXME: handle special cases for missing start/end + if ("*" == range_start && "*" == range_end) + "content:#{field}#{SEP}*" + elsif "*" == range_end + "content:#{left}#{field}#{SEP}#{range_start} TO #{field}#{SEP}\\ufff0#{right}" + elsif "*" == range_start + "content:#{left}#{field}#{SEP} TO #{field}#{SEP}#{range_end}#{right}" + else + "content:#{left}#{field}#{SEP}#{range_start} TO #{field}#{SEP}#{range_end}#{right}" + end + end + + end + + class InclFieldRange < FieldRange + def left + "[" + end + def right + "]" + end + end + + class ExclFieldRange < FieldRange + def left + "{" + end + def right + "}" + end + end + + class RangeValue < Treetop::Runtime::SyntaxNode + def to_array + self.text_value + end + + def transform + to_array + end + end + + class FieldName < Treetop::Runtime::SyntaxNode + def to_array + self.text_value + end + + def transform + to_array + end + end + + + class Body < Treetop::Runtime::SyntaxNode + def to_array + self.elements.map { |x| x.to_array }.join(" ") + end + + def transform + self.elements.map { |x| x.transform }.join(" ") + end + end + + class Group < Treetop::Runtime::SyntaxNode + def to_array + "(" + self.elements[0].to_array + ")" + end + + def transform + "(" + self.elements[0].transform + ")" + end + end + + class BinaryOp < Treetop::Runtime::SyntaxNode + def to_array + op = self.elements[1].to_array + a = self.elements[0].to_array + b = self.elements[2].to_array + "(#{op} #{a} #{b})" + end + + def transform + op = self.elements[1].transform + a = self.elements[0].transform + b = self.elements[2].transform + "#{a} #{op} #{b}" + end + end + + class AndOperator < Treetop::Runtime::SyntaxNode + def to_array + "OP:AND" + end + + def transform + "AND" + end + end + + class OrOperator < Treetop::Runtime::SyntaxNode + def to_array + "OP:OR" + end + + def transform + "OR" + end + end + + class FuzzyOp < Treetop::Runtime::SyntaxNode + def to_array + a = self.elements[0].to_array + param = self.elements[1] + if param + "(OP:~ #{a} #{param.to_array})" + else + "(OP:~ #{a})" + end + end + + def transform + a = self.elements[0].transform + param = self.elements[1] + if param + "#{a}~#{param.transform}" + else + "#{a}~" + end + end + end + + class BoostOp < Treetop::Runtime::SyntaxNode + def to_array + a = self.elements[0].to_array + param = self.elements[1] + "(OP:^ #{a} #{param.to_array})" + end + + def transform + a = self.elements[0].transform + param = self.elements[1] + "#{a}^#{param.transform}" + end + end + + class FuzzyParam < Treetop::Runtime::SyntaxNode + def to_array + self.text_value + end + + def transform + self.text_value + end + end + + class UnaryOp < Treetop::Runtime::SyntaxNode + def to_array + op = self.elements[0].to_array + a = self.elements[1].to_array + "(#{op} #{a})" + end + + def transform + op = self.elements[0].transform + a = self.elements[1].transform + spc = case op + when "+", "-" + "" + else + " " + end + "#{op}#{spc}#{a}" + end + + end + + class NotOperator < Treetop::Runtime::SyntaxNode + def to_array + "OP:NOT" + end + + def transform + "NOT" + end + + end + + class RequiredOperator < Treetop::Runtime::SyntaxNode + def to_array + "OP:+" + end + + def transform + "+" + end + + end + + class ProhibitedOperator < Treetop::Runtime::SyntaxNode + def to_array + "OP:-" + end + + def transform + "-" + end + end + + class Phrase < Treetop::Runtime::SyntaxNode + def to_array + "STR:#{self.text_value}" + end + + def transform + "#{self.text_value}" + end + end +end diff --git a/cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/query_transform.rb b/cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/query_transform.rb new file mode 100644 index 0000000..84bf5a1 --- /dev/null +++ b/cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/query_transform.rb @@ -0,0 +1,65 @@ +# +# Author:: Seth Falcon () +# Copyright:: Copyright (c) 2010-2011 Opscode, Inc. +# 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. +# +require 'treetop' +require 'chef/solr_query/lucene_nodes' + +class Chef + class Exceptions + class QueryParseError < StandardError + end + end +end + +class Chef + class SolrQuery + class QueryTransform + @@base_path = File.expand_path(File.dirname(__FILE__)) + Treetop.load(File.join(@@base_path, 'lucene.treetop')) + @@parser = LuceneParser.new + + def self.parse(data) + tree = @@parser.parse(data) + msg = "Parse error at offset: #{@@parser.index}\n" + msg += "Reason: #{@@parser.failure_reason}" + raise Chef::Exceptions::QueryParseError, msg if tree.nil? + self.clean_tree(tree) + tree.to_array + end + + def self.transform(data) + return "*:*" if data == "*:*" + tree = @@parser.parse(data) + msg = "Parse error at offset: #{@@parser.index}\n" + msg += "Reason: #{@@parser.failure_reason}" + raise Chef::Exceptions::QueryParseError, msg if tree.nil? + self.clean_tree(tree) + tree.transform + end + + private + + def self.clean_tree(root_node) + return if root_node.elements.nil? + root_node.elements.delete_if do |node| + node.class.name == "Treetop::Runtime::SyntaxNode" + end + root_node.elements.each { |node| self.clean_tree(node) } + end + end + end +end diff --git a/cookbooks/chef-solo-search/metadata.json b/cookbooks/chef-solo-search/metadata.json new file mode 100644 index 0000000..6d58afb --- /dev/null +++ b/cookbooks/chef-solo-search/metadata.json @@ -0,0 +1,35 @@ +{ + "license": "Apache 2.0", + "replacing": { + }, + "suggestions": { + }, + "long_description": "# chef-solo-search\n\n[![Build Status](https://travis-ci.org/edelight/chef-solo-search.png?branch=master)](https://travis-ci.org/edelight/chef-solo-search)\n\nChef-solo-search is a cookbook library that adds data bag search powers\nto Chef Solo. Data bag support was added to Chef Solo by Chef 0.10.4.\nPlease see *Supported queries* for a list of query types which are supported.\n\n## Requirements\n\n * ruby >= 1.8\n * ruby-chef >= 0.10.4\n\n## Installation\n\nInstall this cookbook into your Chef repository using your favorite cookbook\nmanagement tool\n([Librarian](https://github.com/applicationsonline/librarian-chef),\n[Berkshelf](https://github.com/RiotGames/berkshelf), knife...).\n\nIn Chef 11, you must either add this to the run list of the nodes where it's used or include it as a dependency in the recipes that use it. [See changes in Chef 11.](http://docs.opscode.com/breaking_changes_chef_11.html#non-recipe-file-evaluation-includes-dependencies)\n\nNow you have to make sure chef-solo knows about data bags, therefore add\n\n data_bag_path \"/data_bags\"\n\nto the config file of chef-solo (defaults to /etc/chef/solo.rb).\n\nThe same for your roles, add\n\n role_path \"/roles\"\n\n## Supported queries\n\nThe search methods supports a basic sub-set of the lucene query language.\nSample supported queries are:\n\n### General queries:\n\n search(:users, \"*:*\")\n search(:users)\n search(:users, nil)\n getting all items in ':users'\n search(:users, \"username:*\")\n search(:users, \"username:[* TO *]\")\n getting all items from ':users' which have a 'username' attribute\n search(:users, \"(NOT username:*)\")\n search(:users, \"(NOT username:[* TO *])\")\n getting all items from ':users' which don't have a 'username' attribute\n\n### Queries on attributes with string values:\n\n search(:users, \"username:speedy\")\n getting all items from ':users' with username equals 'speedy'\n search(:users, \"NOT username:speedy\")\n getting all items from ':users' with username is unequal to 'speedy'\n search(:users, \"username:spe*\")\n getting all items which 'username'-value begins with 'spe'\n\n### Queries on attributes with array values:\n\n search(:users, \"children:tom\")\n getting all items which 'children' attribute contains 'tom'\n search(:users, \"children:t*\")\n getting all items which have at least one element in 'children'\n which starts with 't'\n\n### Queries on attributes with boolean values:\n\n search(:users, \"married:true\")\n\n### Queries in attributes with integer values:\n\n search(:users, \"age:35\")\n\n### OR conditions in queries:\n\n search(:users, \"age:42 OR age:22\")\n\n### AND conditions in queries:\n\n search(:users, \"married:true AND age:35\")\n\n### NOT condition in queries:\n\n search(:users, \"children:tom NOT gender:female\")\n\n### More complex queries:\n\n search(:users, \"children:tom NOT gender:female AND age:42\")\n\n\n## Supported Objects\nThe search methods have support for 'roles', 'nodes' and 'databags'.\n\n### Roles\nYou can use the standard role objects in json form and put them into your role path\n\n {\n \"name\": \"monitoring\",\n \"default_attributes\": { },\n \"override_attributes\": { },\n \"json_class\": \"Chef::Role\",\n \"description\": \"This is just a monitoring role, no big deal.\",\n \"run_list\": [\n ],\n \"chef_type\": \"role\"\n\n\n### Nodes\nNodes are injected through a databag called 'node'. Create a databag called 'node' and put your json files there\nYou can use the standard node objects in json form.\n\n {\n \"id\": \"vagrant\",\n \"name\": \"vagrant-vm\",\n \"chef_environment\": \"_default\",\n \"json_class\": \"Chef::Node\",\n \"automatic\": {\n \"hostname\": \"vagrant.vm\",\n \"os\": \"centos\"\n },\n \"normal\": {\n },\n \"chef_type\": \"node\",\n \"default\": {\n },\n \"override\": {\n },\n \"run_list\": [\n \"role[monitoring]\"\n ]\n }\n\n### Databags\nYou can use the standard databag objects in json form\n\n {\n \"id\": \"my-ssh\",\n \"hostgroup_name\": \"all\",\n \"command_line\": \"$USER1$/check_ssh $HOSTADDRESS$\"\n }\n\n## Running tests\n\nRunning tests is as simple as:\n\n % rake test\n", + "attributes": { + }, + "providing": { + }, + "maintainer_email": "markus.korn@edelight.de", + "groupings": { + }, + "conflicting": { + }, + "description": "Data bag search for Chef Solo", + "name": "chef-solo-search", + "version": "0.5.1", + "dependencies": { + }, + "platforms": { + "freebsd": ">= 0.0.0", + "fedora": ">= 0.0.0", + "debian": ">= 0.0.0", + "ubuntu": ">= 0.0.0", + "centos": ">= 0.0.0", + "redhat": ">= 0.0.0" + }, + "maintainer": "edelight GmbH", + "recipes": { + }, + "recommendations": { + } +} \ No newline at end of file diff --git a/cookbooks/chef-solo-search/metadata.rb b/cookbooks/chef-solo-search/metadata.rb new file mode 100644 index 0000000..8cdecc2 --- /dev/null +++ b/cookbooks/chef-solo-search/metadata.rb @@ -0,0 +1,11 @@ +name "chef-solo-search" +maintainer "edelight GmbH" +maintainer_email "markus.korn@edelight.de" +license "Apache 2.0" +description "Data bag search for Chef Solo" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version "0.5.1" + +%w{ ubuntu debian redhat centos fedora freebsd}.each do |os| + supports os +end diff --git a/cookbooks/chef-solo-search/recipes/default.rb b/cookbooks/chef-solo-search/recipes/default.rb new file mode 100644 index 0000000..8ffa4a1 --- /dev/null +++ b/cookbooks/chef-solo-search/recipes/default.rb @@ -0,0 +1,2 @@ +# This file is intentionally blank, which allows this +# entire repository to be used as a cookbook diff --git a/cookbooks/chef-solo-search/tests/data/data_bags/node/alpha.json b/cookbooks/chef-solo-search/tests/data/data_bags/node/alpha.json new file mode 100644 index 0000000..d06ac6d --- /dev/null +++ b/cookbooks/chef-solo-search/tests/data/data_bags/node/alpha.json @@ -0,0 +1,10 @@ +{ + "id": "alpha", + "name": "alpha.example.com", + "json_class": "Chef::Node", + "run_list": ["role[test_server]"], + "chef_environment": "default", + "automatic": { + "hostname": "alpha.example.com" + } +} diff --git a/cookbooks/chef-solo-search/tests/data/data_bags/node/beta.json b/cookbooks/chef-solo-search/tests/data/data_bags/node/beta.json new file mode 100644 index 0000000..9e4f267 --- /dev/null +++ b/cookbooks/chef-solo-search/tests/data/data_bags/node/beta.json @@ -0,0 +1,10 @@ +{ + "id": "beta", + "name": "beta.example.com", + "json_class": "Chef::Node", + "run_list": ["role[test_server]","role[beta_server]"], + "chef_environment": "default", + "automatic": { + "hostname": "beta.example.com" + } +} diff --git a/cookbooks/chef-solo-search/tests/data/data_bags/node/without_json_class.json b/cookbooks/chef-solo-search/tests/data/data_bags/node/without_json_class.json new file mode 100644 index 0000000..e64c88c --- /dev/null +++ b/cookbooks/chef-solo-search/tests/data/data_bags/node/without_json_class.json @@ -0,0 +1,7 @@ +{ + "id": "without_json_class", + "name": "wjc.example.com", + "chef_environment": "default", + "hostname": "wjc.example.com", + "run_list": ["role[test_server]"] +} diff --git a/cookbooks/chef-solo-search/tests/data/data_bags/users/jerry.json b/cookbooks/chef-solo-search/tests/data/data_bags/users/jerry.json new file mode 100644 index 0000000..bdfe6e5 --- /dev/null +++ b/cookbooks/chef-solo-search/tests/data/data_bags/users/jerry.json @@ -0,0 +1,8 @@ +{ + "id": "jerry", + "username": "speedy", + "age": 22, + "gender": "male", + "married": true, + "color": "green" +} diff --git a/cookbooks/chef-solo-search/tests/data/data_bags/users/lea.json b/cookbooks/chef-solo-search/tests/data/data_bags/users/lea.json new file mode 100644 index 0000000..4b58de4 --- /dev/null +++ b/cookbooks/chef-solo-search/tests/data/data_bags/users/lea.json @@ -0,0 +1,10 @@ +{ + "id": "lea", + "username": "lea", + "age": 35, + "gender": "female", + "married": true, + "children": ["tom"], + "tag": "tag::test", + "tags": ["tag::first", "tag::second"] +} diff --git a/cookbooks/chef-solo-search/tests/data/data_bags/users/mike.json b/cookbooks/chef-solo-search/tests/data/data_bags/users/mike.json new file mode 100644 index 0000000..215b30d --- /dev/null +++ b/cookbooks/chef-solo-search/tests/data/data_bags/users/mike.json @@ -0,0 +1,13 @@ +{ + "id": "mike", + "username": "mike the hammer", + "age": 42, + "gender": "male", + "married": true, + "children": ["tom", "jerry"], + "address": { + "street": { + "floor": 1 + } + } +} diff --git a/cookbooks/chef-solo-search/tests/data/data_bags/users/tom.json b/cookbooks/chef-solo-search/tests/data/data_bags/users/tom.json new file mode 100644 index 0000000..b1622a4 --- /dev/null +++ b/cookbooks/chef-solo-search/tests/data/data_bags/users/tom.json @@ -0,0 +1,10 @@ +{ + "id": "tom", + "username": "tom von homme", + "age": 13, + "gender": "male", + "married": false, + "address": { + "street": "wilhelmsstrasse" + } +} diff --git a/cookbooks/chef-solo-search/tests/gemfiles/Gemfile.10 b/cookbooks/chef-solo-search/tests/gemfiles/Gemfile.10 new file mode 100644 index 0000000..e4860d6 --- /dev/null +++ b/cookbooks/chef-solo-search/tests/gemfiles/Gemfile.10 @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +gem "chef", "~> 10.0" +gem "rake" diff --git a/cookbooks/chef-solo-search/tests/gemfiles/Gemfile.11 b/cookbooks/chef-solo-search/tests/gemfiles/Gemfile.11 new file mode 100644 index 0000000..6753b4d --- /dev/null +++ b/cookbooks/chef-solo-search/tests/gemfiles/Gemfile.11 @@ -0,0 +1,5 @@ +source "https://rubygems.org" + +gem "chef", "~> 11.0" +gem "rake" +gem "treetop" diff --git a/cookbooks/chef-solo-search/tests/test_data_bags.rb b/cookbooks/chef-solo-search/tests/test_data_bags.rb new file mode 100644 index 0000000..05fd9bb --- /dev/null +++ b/cookbooks/chef-solo-search/tests/test_data_bags.rb @@ -0,0 +1,45 @@ +# +# Copyright 2011, edelight GmbH +# +# Authors: +# Markus Korn +# +# 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. +# + +require "test/unit" +require "chef" + +# mocking chef such that it thinks it's running as chef-solo and knows about +# the location of the data_bag +Chef::Config[:solo] = true +Chef::Config[:data_bag_path] = "tests/data/data_bags" + +def data_bag_item(bag, item) + # wrapper around creating a new Recipe instance and calling data_bag on it + node = Chef::Node.new() + events = Chef::EventDispatch::Dispatcher.new + cookbooks = Chef::CookbookCollection.new() + run_context = Chef::RunContext.new(node, cookbooks, events) + return Chef::Recipe.new("test_cookbook", "test_recipe", run_context).data_bag_item(bag, item) +end + +class TestDataBags < Test::Unit::TestCase + + def test_data_bag + item = data_bag_item("users", "mike") + assert_equal item["age"], 42 + assert_equal item[:age], nil #upstream code for chef-solo does not use mashes + end + +end diff --git a/cookbooks/chef-solo-search/tests/test_search.rb b/cookbooks/chef-solo-search/tests/test_search.rb new file mode 100644 index 0000000..8d4c20b --- /dev/null +++ b/cookbooks/chef-solo-search/tests/test_search.rb @@ -0,0 +1,244 @@ +# +# Copyright 2011, edelight GmbH +# +# Authors: +# Markus Korn +# +# 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. +# + +require "rubygems" +require "test/unit" +require "chef" + +# mocking chef such that it thinks it's running as chef-solo and knows about +# the location of the data_bag +Chef::Config[:solo] = true +Chef::Config[:data_bag_path] = "#{File.dirname(__FILE__)}/data/data_bags" + +# load the extension +require File.expand_path('../../libraries/search', __FILE__) + +module SearchDbTests + + def test_search_all + # try to get data of all users + nodes = search(:users, "*:*") + assert_equal nodes.length, 4 + nodes = search(:users) + assert_equal nodes.length, 4 + nodes = search(:users, nil) + assert_equal nodes.length, 4 + end + + def test_search_exact_match + nodes = search(:users, "username:speedy") + assert_equal nodes.length, 1 + assert_equal nodes[0]["username"], "speedy" + end + + def test_get_all_with_field + nodes = search(:users, "username:*") + assert nodes.length > 0 + assert nodes.all?{|x| !x["username"].nil?} + end + + def test_get_all_without_field + nodes = search(:users, "(NOT username:*)") + assert nodes.length == 0 + nodes = search(:users, "(NOT color:*)") + assert nodes.length == 3 + assert nodes.all?{|x| x["color"].nil?} + end + + def test_get_all_but_speedy + nodes = search(:users, "NOT username:speedy") + assert nodes.length > 0 + assert nodes.all?{|x| x["username"] != "speedy"} + end + + def test_array_includes + nodes = search(:users, "children:tom") + assert nodes.length == 2 + assert nodes.all?{ |x| x["children"].include?("tom") } + nodes = search(:users, "children:jerry") + assert nodes.length == 1 + assert nodes.all?{ |x| x["children"].include?("jerry") } + end + + def test_boolean + nodes = search(:users, "married:true") + assert nodes.length == 3 + assert nodes.all?{ |x| x["married"] == true } + nodes = search(:users, "married:false") + assert nodes.length == 1 + assert nodes[0]["married"] == false + end + + def test_integer + nodes = search(:users, "age:35") + assert nodes.length == 1 + assert nodes[0]["age"] == 35 + end + + def test_AND_condition + nodes = search(:users, "married:true AND age:35") + assert nodes.length == 1 + assert nodes[0]["username"] == "lea" + end + + def test_OR_condition + nodes = search(:users, "age:42 OR age:22") + assert nodes.length == 2 + end + + def test_NOT_condition + nodes = search(:users, "children:tom AND (NOT gender:female)") + assert nodes.length == 1 + nodes = search(:users, "children:tom AND (NOT gender:female) AND age:42") + assert nodes.length == 1 + nodes = search(:users, "children:tom AND (NOT gender:female) AND (NOT age:42)") + assert nodes.length == 0 + end + + def test_any_value + nodes = search(:users, "children:*") + assert nodes.length == 2 + end + + def test_any_value_lucene_range + nodes = search(:users, "address:[* TO *]") + assert nodes.length == 2 + end + + def test_general_lucene_range_fails + assert_raises RuntimeError do + nodes = search(:users, "children:[aaa TO zzz]") + end + end + + def test_block_usage + # bracket syntax + result = [] + search(:users, "*:*") {|x| result << x["id"]} + assert result.length == 4 + + # do...end syntax + result = [] + search(:users) do |x| + result << x["id"] + end + assert result.length == 4 + end + + def test_check_escaped_chars + nodes = search(:users, 'tag:tag\:\:test') + assert nodes.length == 1 + nodes = search(:users, "tag:tag\\:\\:test") + assert nodes.length == 1 + nodes = search(:users, 'tags:tag\:\:first') + assert nodes.length == 1 + nodes = search(:users, "tags:tag\\:\\:first") + assert nodes.length == 1 + nodes = search(:users, 'tags:tag\:\:*') + assert nodes.length == 1 + nodes = search(:users, "tags:tag\\:\\:*") + assert nodes.length == 1 + end + + def test_wildcards + nodes = search(:users, "gender:f??ale") + assert nodes.length == 1 + nodes = search(:users, "username:spee?y") + assert nodes.length == 1 + nodes = search(:users, "username:spee*") + assert nodes.length == 1 + end + + def test_empty_field_value + assert_raise(RuntimeError) { + search(:users, "gender:#{nil} AND age:35") + } + assert_raise(RuntimeError) { + search(:users, "gender: AND age:35") + } + assert_raise(RuntimeError) { + search(:users, "gender:\"\" AND age:35") + } + end + + def test_OR_group + nodes = search(:users, "id:(mike OR tom)") + assert nodes.length == 2 + end + + def test_nested_fieldnames + nodes = search(:users, "address_street:wilhelmsstrasse") + assert nodes.length == 1 + nodes = search(:users, "address_street_floor:1") + assert nodes.length == 1 + end +end + +module SearchNodeTests + def test_list_nodes + nodes = search(:node) + assert_equal Chef::Node, nodes.find{ |n| n["hostname"] == "alpha.example.com" }.class + assert_equal Chef::Node, nodes.find{ |n| n["hostname"] == "beta.example.com" }.class + assert_equal Hash, nodes.find{ |n| n["hostname"] == "wjc.example.com" }.class + assert_equal 3, nodes.length + end + + def test_search_node_with_wide_filter + nodes = search(:node, "role:test_server AND chef_environment:default") + assert_equal 2, nodes.length + end + + def test_search_node_with_narrow_filter + nodes = search(:node, "role:beta_server") + assert_equal 1, nodes.length + end + + def test_search_node_with_attr_filter + nodes = search(:node, "hostname:beta.example.com") + assert_equal 1, nodes.length + end + + def test_search_node_without_json_class + nodes = search(:node, "chef_environment:default") + assert_equal 3, nodes.length + end +end + +class TestImplicitSearchDB < Test::Unit::TestCase + include SearchDbTests + include SearchNodeTests + + def search(*args, &block) + # wrapper around creating a new Recipe instance and calling search on it + node = Chef::Node.new() + cookbooks = Chef::CookbookCollection.new() + run_context = Chef::RunContext.new(node, cookbooks, nil) + return Chef::Recipe.new("test_cookbook", "test_recipe", run_context).search(*args, &block) + end +end + +class TestExplicitSearchDB < Test::Unit::TestCase + include SearchDbTests + include SearchNodeTests + + def search(*args, &block) + Chef::Search::Query.new.search(*args, &block) + end +end + diff --git a/cookbooks/chef-sugar/CHANGELOG.md b/cookbooks/chef-sugar/CHANGELOG.md new file mode 100644 index 0000000..d0a52d5 --- /dev/null +++ b/cookbooks/chef-sugar/CHANGELOG.md @@ -0,0 +1,159 @@ +Chef Sugar Changelog +========================= +This file is used to list changes made in each version of the chef-sugar cookbook and gem. + +v3.0.2 (2015-03-26) +------------------- +### Improvements +- Add helpers for `ppc64` and `ppc64le` architecture + +### Bug Fixes +- Adjustments to error message + +v3.0.1 (2015-03-20) +------------------- +### Breaking Changes +- Rename `compile_time` `to at_compile_time` - if your recipes are affected by + this breaking change, your Chef Client run will produce a verbose error + message with details on how to fix the error. + +v3.0.0 (2015-03-17) +------------------- +### Breaking Changes +- Drop support for Ruby 1.9 (it might still work, but it is no longer officially supported) + +### Improvements +- Remove accidentially committed gem source +- Bump development dependencies +- Add `digitalocean?` matcher +- Expose the `rhel` platform as `el` +- Add `ppc64le` platform +- Add helper for determining if architecture is SPARC +- Add helper for determining if architecture is Intel +- Add dynamic platform/version matchers for Solaris + +### Bug Fixes +- Reset namespace_options when reaching top-level resources + +v2.5.0 (2015-01-05) +------------------- +### Improvements +- Add `data_bag_item_for_environment` function +- Add `kvm?` matcher +- Add `virtualbox?` matcher + +### Bug Fixes +- Use `.key?` to check for hash key presence, raising an `AttributeDoesNotExist` + error sooner + +v2.4.1 (2014-10-12) +------------------- +- No changes from v2.4.0 - forced a new version upload to the Chef Supermarket + +v2.4.0 (2014-10-12) +------------------- +### Improvements +- Add `docker?` matcher + +v2.3.2 (2014-10-07) +------------------- +### Big Fixues +- Include `amd64` in `_64_bit?` check + +v2.3.1 (2014-10-07) +------------------- +### Improvements +- Check all 64-bit architectures that may be reported by Ohai + +### Bug Fixes +- Be more tolerant of `nil` values return from sub functions +- Check to make sure `node['domain']` is not `nil` before calling `#include?` + +v2.3.0 (2014-09-24) +------------------- +### Improvements +- Add `vmware?` matcher +- Allow the attribute DSL to access parent attributes + +### Bug Fixes +- Return `true` or `false` from all Boolean methods (instead of `nil` or truthy values) + +v2.2.0 (2014-08-20) +------------------- +### Improvements +- Add `smartos?` matcher +- Add `omnios?` matcher + +v2.1.0 (2014-06-26) +------------------- +### Improvements +- Add `solaris2?` matcher +- Add `aix?` matcher +- Add 'lxc?' matcher + +### Bug Fixes +- Fix a bug in namespace memoization during attribute initialization + +v2.0.0 (2014-06-16) +------------------- +### Breaking +- Remove `not_linux?` method +- Remove `not_windows?` method + +### Improvements +- Miscellaneous spelling fixes +- Update a failing unit test for `installed?` +- Add Mac OS X to the list of platforms (Yosemite) +- Upgrade to RSpec 3 +- Fix `which` (and `installed?` and `installed_at_version?`) when given an absolute path +- Fix `linux?` check to only return true on real linuxes + +v1.3.0 (2014-05-05) +------------------- +- Check both `$stdout` and `$stderr` in `version_for` +- Add additional platform versions +- Make `includes_recipe?` a top-level API (instead of just Node) +- Match on the highest version number instead of direct equality checking on platform versions +- Define `Object#blank?` as a core extension +- Define `String#flush` as a core extension +- Remove Stove + +v1.2.6 (2014-03-16) +------------------- +- Fix a bug in `vagrant?` returning false on newer Vagrant versions +- Remove Coveralls + +v1.2.4 (2014-03-13) +------------------- +- See (1.2.2), but I botched the release + +v1.2.2 (2014-03-13) +------------------- +- Fix a critical bug with `encrypted_data_bag_item` using the wrong key + +v1.2.0 (2014-03-09) +------------------- +- Add `namespace` functionality for specifying attributes in a DSL +- Add constraints helpers for comparing version strings +- Add `require_chef_gem` to safely require and degrade if a gem is not installed +- Add `deep_fetch` and `deep_fetch!` to fetch deeply nested keys +- Accept an optional secret key in `encrypted_data_bag_item` helper and raise a helpful error if one is not set (NOTE: this changes the airity of the method, but it's backward-compatible because Ruby is magic) +- Add Stove for releasing +- Updated copyrights for 2014 + +v1.1.0 (2013-12-10) +------------------- +- Add `cloudstack?` helper +- Add data bag helpers +- Remove foodcritic checks +- Upgrade development gem versions +- Randomize spec order + +v1.0.1 (2013-10-15) +------------------- +- Add development recipe +- Add `compile_time`, `before`, and `after` filters + +v1.0.0 (2013-10-15) +------------------- +- First public release diff --git a/cookbooks/chef-sugar/README.md b/cookbooks/chef-sugar/README.md new file mode 100644 index 0000000..8b43ec4 --- /dev/null +++ b/cookbooks/chef-sugar/README.md @@ -0,0 +1,464 @@ +Chef Sugar +========== +[![Gem Version](http://img.shields.io/gem/v/chef-sugar.svg?style=flat-square)][gem] +[![Build Status](http://img.shields.io/travis/sethvargo/chef-sugar.svg?style=flat-square)][travis] + +[gem]: https://rubygems.org/gems/chef-sugar +[travis]: http://travis-ci.org/sethvargo/chef-suguar + +Chef Sugar is a Gem & Chef Recipe that includes series of helpful sugar of the Chef core and other resources to make a cleaner, more lean recipe DSL, enforce DRY principles, and make writing Chef recipes an awesome experience! + + +Installation +------------ +If you want to develop/hack on chef-sugar, please see the Contributing.md. + +If you are using Berkshelf, add `chef-sugar` to your `Berksfile`: + +```ruby +cookbook 'chef-sugar' +``` + +Otherwise, you can use `knife` or download the tarball directly from the community site: + +```ruby +knife cookbook site install chef-sugar +``` + + +Usage +----- +In order to use Chef Sugar in your Chef Recipes, you'll first need to include it: + +```ruby +include_recipe 'chef-sugar::default' +``` + +Alternatively you can put it in a base role or recipe and it will be included subsequently. + +Requiring the Chef Sugar Gem will automatically extend the Recipe DSL, `Chef::Resource`, and `Chef::Provider` with helpful convenience methods. + +### Module Method +If you are working outside of the Recipe DSL, you can use the module methods instead of the Recipe DSL. In general, the module methods have the same name as their Recipe-DSL counterparts, but require the node object as a parameter. For example: + +In a Recipe: + +```ruby +# cookbook/recipes/default.rb +do_something if windows? +``` + +In a Library as a singleton: + +```ruby +# cookbook/libraries/default.rb +def only_on_windows(&block) + yield if Chef::Sugar::PlatformFamily.windows?(@node) +end +``` + +In a Library as a Mixin: + +```ruby +# cookbook/libraries/default.rb +include Chef::Sugar::PlatformFamily + +def only_on_windows(&block) + yield if windows?(@node) +end +``` + + +API +--- +**Note:** For the most extensive API documentation, please see the YARD documentation. + +### Architecture +**Note:** Some of the architecture commands begin with an underscore (`_`) because Ruby does not permit methods to start with a numeric. + +- `_64_bit?` +- `_32_bit?` +- `intel?` +- `sparc?` +- `ppc64?` +- `ppc64le?` + +#### Examples +```ruby +execute 'build[my binary]' do + command '...' + not_if { _64_bit? } +end +``` + +### Cloud +- `azure?` +- `cloud?` +- `digitalocean?` +- `ec2?` +- `eucalyptus?` +- `gce?` +- `linode?` +- `openstack?` +- `cloudstack?` +- `rackspace?` + +#### Examples +```ruby +template '/tmp/config' do + variables( + # See also: best_ip_for + ipaddress: cloud? ? node['local_ipv4'] : node['public_ipv4'] + ) +end +``` + +### Core Extensions +**Note:** Core extensions are **not** included by default. You must require the `chef/sugar/core_extensions` module manually to gain access to these APIs: + +```ruby +require 'chef/sugar/core_extensions' +``` + +- `String#satisfies?` +- `String#satisfied_by?` +- `Array#satisfied_by?` +- `Object#blank?` + +#### Examples +```ruby +# Checking version constraints +'1.0.0'.satisfies?('~> 1.0') #=> true +'~> 1.0'.satisfied_by?('1.0') #=> true +``` + +```ruby +# Check for an object's presence +''.blank? #=> true +['hello'].blank? #=> false +``` + +### Data Bag +- `encrypted_data_bag_item` - a handy DSL method for loading encrypted data bag items the same way you load a regular data bag item; this requires `Chef::Config[:encrypted_data_bag_secret]` is set! +- `encrypted_data_bag_item_for_environment` - find the encrypted data bag entry for the current node's Chef environment. +- `data_bag_item_for_environment` - find the data bag entry for the current node's Chef environment. + +#### Examples +```ruby +encrypted_data_bag_item('accounts', 'hipchat') +``` + +```ruby +encrypted_data_bag_item_for_environment('accounts', 'github') +``` + +```ruby +data_bag_item_for_environment('accounts', 'github') +``` + +### Docker +Chef Sugar looks for hints to see if the node being converged is a Docker container. When [Ohai supports checking other nodes](https://github.com/opscode/ohai/pull/428), Chef Sugar will automatically pick up the information. + +- `docker?` + +#### Examples +```ruby +template '/runme' do + only_if { docker?(node) } +end +``` + +### Attributes +Chef Sugar adds more Chef-like DSL to attribute definitions. Instead of using the Ruby hash syntax, you can define attributes using nested namespaces. This DSL may be more friendly to non-Ruby developers. It can safely be mixed-and-matched with the standard syntax. + +```ruby +# This is functionally the same as default['apache2']['config']['root'] = '/var/www' +namespace 'apache2' do + namespace 'config' do + root '/var/www' + end +end +``` + +```ruby +# Specify multiple keys instead of nesting namespaces +namespace 'apache2', 'config' do + root '/var/www' +end +``` + +```ruby +# Specify different nested precedence levels +namespace 'apache2', precedence: normal do + namespace 'config', precedence: override do + root '/var/www' #=> override['apache2']['config']['root'] = '/var/www' + end +end +``` + +### Constraints +- `constraints` - create a new constraint (or requirement) that can be used to test version validations. +- `chef_version` - (DSL only) a wrapper for `version(Chef::VERSION)` +- `version` - create a new version that can be used to test constraint validation. + +#### Examples +```ruby +# Check if a version is satisfied by a constraint +version('1.2.3').satisfies?('~> 1.2.0') +``` + +```ruby +# Check if a constraint is satisfied by a version +constraint('~> 1.2.0').satisfied_by?('1.2.3') +``` + +```ruby +# Support multiple constraints +version('1.2.3').satisfies?('> 1.2', '< 2.0') +constraint('> 1.2', '< 2.0').satisfied_by?('1.2.3') +``` + +```ruby +# Only perform an operation if Chef is at a certain version +package 'apache2' do + not_if { chef_version.satisfies?('~> 11.0') } # Ignore Chef 11 +end +``` + +### Kernel +- `require_chef_gem` - "safely" require a gem. Loading a gem with Chef is sometimes difficult and confusing. The errors that Chef produces are also sometimes not very intuitive. In the event you require a gem to exist on the system, you can use `require_chef_gem`, which will attempt to require the gem and then produce helpful output if the gem is not installed: + + Chef could not load the gem `#{name}'! You may need to install the gem + manually with `gem install #{name}', or include a recipe before you can + use this resource. Please consult the documentation for this cookbook + for proper usage. + +#### Examples +```ruby +# LWRP +require_chef_gem 'pry' +``` + +```ruby +class Chef + class Provider + class MyProvider > Provider + require_chef_gem 'pry' + end + end +end +``` + +### IP +- `best_ip_for` - determine the best IP address for the given "other" node, preferring local IP addresses over public ones. + +#### Examples +```ruby +redis = search('node', 'role:redis').first + +template '/tmp/config' do + variables( + ipaddress: best_ip_for(redis) + ) +end +``` + +### Node + +Additional methods for the `node` object + +- `deep_fetch` - safely fetch a nested attribute. +- `deep_fetch!` - fetch a nested attribute, raising a more semantic error if the key does not exist. +- `in?` - determine if the node is in the given Chef environment. + +#### Examples +```ruby +credentials = if node.in?('production') + Chef::EncryptedDataBag.new('...') + else + data_bag('...') + end +``` + +```ruby +node.deep_fetch('apache2', 'config', 'root') => node['apache2']['config']['root'] +``` + +### Platform +- `amazon_linux?` +- `centos?` +- `linux_mint?` +- `oracle_linux?` +- `redhat_enterprise_linux?` +- `scientific_linux?` +- `ubuntu?` +- `solaris2?` +- `aix?` +- `smartos?` +- `omnios?` + +There are also a series of dynamically defined matchers that map named operating system release versions and comparison operators in the form "#{platform}\_#{operator}\_#{name}?". For example: + +- `debian_after_squeeze?` +- `linuxmint_after_or_at_olivia?` +- `mac_os_x_lion?` +- `ubuntu_before_lucid?` +- `ubuntu_before_or_at_maverick?` +- `solaris_10?` +- `solaris_11?` + +To get a full list, run the following in IRB: + +```ruby +require 'chef/sugar' +puts Chef::Sugar::Platform.instance_methods +``` + +#### Examples +```ruby +if ubuntu? + execute 'apt-get update' +end +``` + +### Platform Family +- `arch_linux?` +- `debian?` +- `fedora?` +- `freebsd?` +- `gentoo?` +- `linux?` +- `mac_os_x?` +- `openbsd?` +- `rhel?` +- `slackware?` +- `suse?` +- `windows?` + +#### Examples +```ruby +node['attribute'] = if windows? + 'C:\Foo\BarDrive' + else + '/foo/bar_drive' + end +``` + +### Ruby +**Note:** The applies to the Ruby found at `node['languages']['ruby']`. + +- `ruby_20?` +- `ruby_19?` + +#### Examples +```ruby +log 'This has been known to fail on Ruby 2.0' if ruby_20? +``` + +### Run Context +- `includes_recipe?` - determines if the current run context includes the recipe + +```ruby +if includes_recipe?('apache2::default') + apache_module 'my_module' do + # ... + end +end +``` + +### Shell +- `which` +- `dev_null` +- `installed?` +- `installed_at_version?` +- `version_for` + +#### Examples +```ruby +log "Using `mongo` at `#{which('mongo')}`" + +if installed?('apt') + execute 'apt-get update' +end + +execute 'install[thing]' do + command "... 2>&1 #{dev_null}" + not_if { installed_at_version?('thing', node['thing']['version']) } +end + +log "Skipping git install, version is at #{version_for('mongo', '-v')}" +``` + +### Vagrant +- `vagrant?` + +#### Examples +```ruby +http_request 'http://...' do + not_if { vagrant? } +end +``` + +### Virtualization +- `kvm?` +- `lxc?` +- `virtualbox?` +- `vmware?` + +#### Examples +```ruby +service 'ntpd' do + action [:enable, :start] + not_if { lxc? } +end +``` + +### Filters +- `at_compile_time` - accepts a block of resources to run at compile time +- `before` - insert resource in the collection before the given resource +- `after` - insert resource in the collection after the given resource + +#### Examples +```ruby +at_compile_time do + package 'apache2' +end + +# This is equivalent to +package 'apache2' do + action :nothing +end.run_action(:install) +``` + +```ruby +before 'service[apache2]' do + log 'I am before the apache 2 service fires!' +end +``` + +```ruby +after 'service[apache2]' do + log 'I am after the apache 2 service fires!' +end +``` + + +License & Authors +----------------- +- Author: Seth Vargo (sethvargo@gmail.com) + +```text +Copyright 2013-2015 Seth Vargo + +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/cookbooks/chef-sugar/metadata.json b/cookbooks/chef-sugar/metadata.json new file mode 100644 index 0000000..9f93a59 --- /dev/null +++ b/cookbooks/chef-sugar/metadata.json @@ -0,0 +1,29 @@ +{ + "name": "chef-sugar", + "version": "3.1.0", + "description": "Installs chef-sugar. Please see the chef-sugar Ruby gem for more information.", + "long_description": "Chef Sugar is a Gem & Chef Recipe that includes series of helpful syntactic\nsugars on top of the Chef core and other resources to make a cleaner, more lean\nrecipe DSL, enforce DRY principles, and make writing Chef recipes an awesome and\nfun experience!\n\nFor the most up-to-date information and documentation, please visit the [Chef\nSugar project page on GitHub](https://github.com/sethvargo/chef-sugar).\n", + "maintainer": "Seth Vargo", + "maintainer_email": "sethvargo@gmail.com", + "license": "Apache 2.0", + "platforms": { + }, + "dependencies": { + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + } +} \ No newline at end of file diff --git a/cookbooks/chef-sugar/recipes/default.rb b/cookbooks/chef-sugar/recipes/default.rb new file mode 100644 index 0000000..89200b8 --- /dev/null +++ b/cookbooks/chef-sugar/recipes/default.rb @@ -0,0 +1,27 @@ +# +# Cookbook Name:: chef-sugar +# Recipe:: default +# +# Copyright 2013-2015, Seth Vargo +# +# 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. +# + +gem_version = run_context.cookbook_collection[cookbook_name].metadata.version + +chef_gem('chef-sugar') do + version gem_version + action :nothing +end.run_action(:install) + +require 'chef/sugar' diff --git a/cookbooks/chef_handler/CHANGELOG.md b/cookbooks/chef_handler/CHANGELOG.md new file mode 100644 index 0000000..2aa532f --- /dev/null +++ b/cookbooks/chef_handler/CHANGELOG.md @@ -0,0 +1,56 @@ +chef_handler cookbook CHANGELOG +=============================== + +v1.1.9 (2015-05-26) +------------------- +Bugfixes from 1.1.8 - loading without source is not allowed again. +Class unloading is performed more carefully. +Tests for resource providers. + +v1.1.8 (2015-05-14) +------------------- +Updated Contribution and Readme docs. +Fix ChefSpec matchers. +Allow handler to load classes when no source is provided. + +v1.1.6 (2014-04-09) +------------------- +[COOK-4494] - Add ChefSpec matchers + + +v1.1.5 (2014-02-25) +------------------- +- [COOK-4117] - use the correct scope when searching the children class name + + +v1.1.4 +------ +- [COOK-2146] - style updates + +v1.1.2 +--------- +- [COOK-1989] - fix scope for handler local variable to the enable block + +v1.1.0 +------ + +- [COOK-1645] - properly delete old handlers +- [COOK-1322] - support platforms that use 'wheel' as root group' + +v1.0.8 +------ +- [COOK-1177] - doesn't work on windows due to use of unix specific attributes + +v1.0.6 +------ +- [COOK-1069] - typo in chef_handler readme + +v1.0.4 +------ +- [COOK-654] dont try and access a class before it has been loaded +- fix bad boolean check (if vs unless) + +v1.0.2 +------ +- [COOK-620] ensure handler code is reloaded during daemonized chef runs + diff --git a/cookbooks/chef_handler/README.md b/cookbooks/chef_handler/README.md new file mode 100644 index 0000000..4f13d39 --- /dev/null +++ b/cookbooks/chef_handler/README.md @@ -0,0 +1,126 @@ +Description +=========== + +Creates a configured handler path for distributing [Chef report and exception handlers](http://docs.chef.io/handlers.html). Also exposes an LWRP for enabling Chef handlers from within recipe code (as opposed to hard coding in the client.rb file). This is useful for cookbook authors who may want to ship a product specific handler (see the `cloudkick` cookbook for an example) with their cookbook. + +Requirements +============ + +* Ruby >= 1.9 + +Attributes +========== + +`node["chef_handler"]["handler_path"]` - location to drop off handlers directory, default is `/var/chef/handlers`. + +Resource/Provider +================= + +`chef_handler` +-------------- + +Requires, configures and enables handlers on the node for the current Chef run. Also has the ability to pass arguments to the handlers initializer. This allows initialization data to be pulled from a node's attribute data. + +It is best to declare `chef_handler` resources early on in the compile phase so they are available to fire for any exceptions during the Chef run. If you have a base role you would want any recipes that register Chef handlers to come first in the run_list. + +### Actions + +- :enable: Enables the Chef handler for the current Chef run on the current node +- :disable: Disables the Chef handler for the current Chef run on the current node + +### Attribute Parameters + +- class_name: name attribute. The name of the handler class (can be module name-spaced). +- source: full path to the handler file. can also be a gem path if the handler ships as part of a Ruby gem. +- arguments: an array of arguments to pass the handler's class initializer +- supports: type of Chef Handler to register as, i.e. :report, :exception or both. default is `:report => true, :exception => true` + +### Example + +```ruby + # register the Chef::Handler::JsonFile handler + # that ships with the Chef gem + chef_handler "Chef::Handler::JsonFile" do + source "chef/handler/json_file" + arguments :path => '/var/chef/reports' + action :enable + end + + # do the same but during the compile phase + chef_handler "Chef::Handler::JsonFile" do + source "chef/handler/json_file" + arguments :path => '/var/chef/reports' + action :nothing + end.run_action(:enable) + + # handle exceptions only + chef_handler "Chef::Handler::JsonFile" do + source "chef/handler/json_file" + arguments :path => '/var/chef/reports' + supports :exception => true + action :enable + end + + + # enable the CloudkickHandler which was + # dropped off in the default handler path. + # passes the oauth key/secret to the handler's + # intializer. + chef_handler "CloudkickHandler" do + source "#{node['chef_handler']['handler_path']}/cloudkick_handler.rb" + arguments [node['cloudkick']['oauth_key'], node['cloudkick']['oauth_secret']] + action :enable + end +``` + + +Usage +===== + +default +------- + +Put the recipe `chef_handler` at the start of the node's run list to make sure that custom handlers are dropped off early on in the Chef run and available for later recipes. + +For information on how to write report and exception handlers for Chef, please see the Chef wiki pages: +http://wiki.chef.io/display/chef/Exception+and+Report+Handlers + +json_file +--------- + +Leverages the `chef_handler` LWRP to automatically register the `Chef::Handler::JsonFile` handler that ships as part of Chef. This handler serializes the run status data to a JSON file located at `/var/chef/reports`. + + +Unit Testing +================== + +chef_handler provides built in [chefspec](https://github.com/sethvargo/chefspec) matchers for assisting unit tests. These matchers will only be loaded if chefspec is already loaded. Following is an example of asserting against the jsonfile handler: + + +```ruby + expect(runner).to enable_chef_handler("Chef::Handler::JsonFile").with( + source: "chef/handler/json_file", + arguments: { :path => '/var/chef/reports'}, + supports: {:exception => true} + ) + end +``` + +License and Author +================== + +Author:: Seth Chisamore () + +Copyright:: 2011, Chef Software, Inc + +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/cookbooks/chef_handler/attributes/default.rb b/cookbooks/chef_handler/attributes/default.rb new file mode 100644 index 0000000..e4a7b8c --- /dev/null +++ b/cookbooks/chef_handler/attributes/default.rb @@ -0,0 +1,30 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: chef_handlers +# Attribute:: default +# +# Copyright 2011-2013, Chef Software, Inc +# +# 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. +# + +default["chef_handler"]["root_user"] = "root" + +case platform +when "openbsd", "freebsd", "mac_os_x", "mac_os_x_server" + default["chef_handler"]["root_group"] = "wheel" +else + default["chef_handler"]["root_group"] = "root" +end + +default["chef_handler"]["handler_path"] = "#{File.expand_path(File.join(Chef::Config[:file_cache_path], '..'))}/handlers" diff --git a/cookbooks/chef_handler/files/default/handlers/README b/cookbooks/chef_handler/files/default/handlers/README new file mode 100644 index 0000000..b575066 --- /dev/null +++ b/cookbooks/chef_handler/files/default/handlers/README @@ -0,0 +1 @@ +This directory contains Chef handlers to distribute to your nodes. diff --git a/cookbooks/chef_handler/libraries/helpers.rb b/cookbooks/chef_handler/libraries/helpers.rb new file mode 100644 index 0000000..f633eab --- /dev/null +++ b/cookbooks/chef_handler/libraries/helpers.rb @@ -0,0 +1,88 @@ +# +# Author:: Kartik Cating-Subramanian () +# Copyright:: Copyright (c) 2015 Chef, Inc. +# 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. + + +module ChefHandler + module Helpers + + # Registers a handler in Chef::Config. + # + # @param handler_type [Symbol] such as :report or :exception. + # @param handler [Chef::Handler] handler to register. + def register_handler(handler_type, handler) + Chef::Log.info("Enabling #{handler.class.name} as a #{handler_type} handler.") + Chef::Config.send("#{handler_type.to_s}_handlers") << handler + end + + # Removes all handlers that match the given class name in Chef::Config. + # + # @param handler_type [Symbol] such as :report or :exception. + # @param class_full_name [String] such as 'Chef::Handler::ErrorReport'. + def unregister_handler(handler_type, class_full_name) + Chef::Log.info("Disabling #{class_full_name} as a #{handler_type} handler.") + Chef::Config.send("#{handler_type.to_s}_handlers").delete_if { |v| v.class.name == class_full_name } + end + + # Walks down the namespace heirarchy to return the class object for the given class name. + # If the class is not available, NameError is thrown. + # + # @param class_full_name [String] full class name such as 'Chef::Handler::Foo' or 'MyHandler'. + # @return [Array] parent class and child class. + def get_class(class_full_name) + ancestors = class_full_name.split('::') + class_name = ancestors.pop + + # We need to search the ancestors only for the first/uppermost namespace of the class, so we + # need to enable the #const_get inherit paramenter only when we are searching in Kernel scope + # (see COOK-4117). + parent = ancestors.inject(Kernel) { |scope, const_name| scope.const_get(const_name, scope === Kernel) } + child = parent.const_get(class_name, parent === Kernel) + return parent, child + end + + # Unloads a given class and reloads it from the file provided. + # + # @param class_full_name [String] full class name such as 'Chef::Handler::Foo'. If a class + # with that name currently exists, its definition is deleted from the enclosing module. + # @param file_name [String] full path to the ruby file to be loaded. If path doesn't end with + # .rb, that extension is appended. + # @return [Class] definition for the freshly loaded class. + def reload_class(class_full_name, file_name) + begin + parent, child = get_class(class_full_name) + rescue + Chef::Log.debug("#{class_full_name} was not previously loaded.") + end + + if child then + class_name = class_full_name.split('::').last + child = nil + parent = Object if parent === Kernel + parent.send(:remove_const, class_name) + GC.start + end + + # Use load instead of require because we need to explicitly avoid any caching that 'require' + # performs. If the file has changed, we want to get the changes. + file_name << '.rb' unless file_name =~ /.*\.rb$/ + load file_name + + parent, child = get_class(class_full_name) + return child + end + end +end diff --git a/cookbooks/chef_handler/libraries/matchers.rb b/cookbooks/chef_handler/libraries/matchers.rb new file mode 100644 index 0000000..f82bf8a --- /dev/null +++ b/cookbooks/chef_handler/libraries/matchers.rb @@ -0,0 +1,38 @@ +# +# Author:: Douglas Thrift () +# Cookbook Name:: chef_handler +# Library:: matchers +# +# Copyright 2014, Chef Software, Inc. +# +# 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. +# + +if defined?(ChefSpec) + chefspec_version = Gem.loaded_specs["chefspec"].version + if chefspec_version < Gem::Version.new('4.1.0') + define_method = ChefSpec::Runner.method(:define_runner_method) + else + define_method = ChefSpec.method(:define_matcher) + end + + define_method.call :chef_handler + + def enable_chef_handler(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:chef_handler, :enable, resource_name) + end + + def disable_chef_handler(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:chef_handler, :disable, resource_name) + end +end diff --git a/cookbooks/chef_handler/metadata.json b/cookbooks/chef_handler/metadata.json new file mode 100644 index 0000000..493c45d --- /dev/null +++ b/cookbooks/chef_handler/metadata.json @@ -0,0 +1 @@ +{"name":"chef_handler","version":"1.1.9","description":"Distribute and enable Chef Exception and Report handlers","long_description":"Description\n===========\n\nCreates a configured handler path for distributing [Chef report and exception handlers](http://docs.chef.io/handlers.html). Also exposes an LWRP for enabling Chef handlers from within recipe code (as opposed to hard coding in the client.rb file). This is useful for cookbook authors who may want to ship a product specific handler (see the `cloudkick` cookbook for an example) with their cookbook.\n\nRequirements\n============\n\n* Ruby >= 1.9\n\nAttributes\n==========\n\n`node[\"chef_handler\"][\"handler_path\"]` - location to drop off handlers directory, default is `/var/chef/handlers`.\n\nResource/Provider\n=================\n\n`chef_handler`\n--------------\n\nRequires, configures and enables handlers on the node for the current Chef run. Also has the ability to pass arguments to the handlers initializer. This allows initialization data to be pulled from a node's attribute data.\n\nIt is best to declare `chef_handler` resources early on in the compile phase so they are available to fire for any exceptions during the Chef run. If you have a base role you would want any recipes that register Chef handlers to come first in the run_list.\n\n### Actions\n\n- :enable: Enables the Chef handler for the current Chef run on the current node\n- :disable: Disables the Chef handler for the current Chef run on the current node\n\n### Attribute Parameters\n\n- class_name: name attribute. The name of the handler class (can be module name-spaced).\n- source: full path to the handler file. can also be a gem path if the handler ships as part of a Ruby gem.\n- arguments: an array of arguments to pass the handler's class initializer\n- supports: type of Chef Handler to register as, i.e. :report, :exception or both. default is `:report => true, :exception => true`\n\n### Example\n\n```ruby\n # register the Chef::Handler::JsonFile handler\n # that ships with the Chef gem\n chef_handler \"Chef::Handler::JsonFile\" do\n source \"chef/handler/json_file\"\n arguments :path => '/var/chef/reports'\n action :enable\n end\n\n # do the same but during the compile phase\n chef_handler \"Chef::Handler::JsonFile\" do\n source \"chef/handler/json_file\"\n arguments :path => '/var/chef/reports'\n action :nothing\n end.run_action(:enable)\n\n # handle exceptions only\n chef_handler \"Chef::Handler::JsonFile\" do\n source \"chef/handler/json_file\"\n arguments :path => '/var/chef/reports'\n supports :exception => true\n action :enable\n end\n\n\n # enable the CloudkickHandler which was\n # dropped off in the default handler path.\n # passes the oauth key/secret to the handler's\n # intializer.\n chef_handler \"CloudkickHandler\" do\n source \"#{node['chef_handler']['handler_path']}/cloudkick_handler.rb\"\n arguments [node['cloudkick']['oauth_key'], node['cloudkick']['oauth_secret']]\n action :enable\n end\n```\n\n\nUsage\n=====\n\ndefault\n-------\n\nPut the recipe `chef_handler` at the start of the node's run list to make sure that custom handlers are dropped off early on in the Chef run and available for later recipes.\n\nFor information on how to write report and exception handlers for Chef, please see the Chef wiki pages:\nhttp://wiki.chef.io/display/chef/Exception+and+Report+Handlers\n\njson_file\n---------\n\nLeverages the `chef_handler` LWRP to automatically register the `Chef::Handler::JsonFile` handler that ships as part of Chef. This handler serializes the run status data to a JSON file located at `/var/chef/reports`.\n\n\nUnit Testing\n==================\n\nchef_handler provides built in [chefspec](https://github.com/sethvargo/chefspec) matchers for assisting unit tests. These matchers will only be loaded if chefspec is already loaded. Following is an example of asserting against the jsonfile handler:\n\n\n```ruby\n expect(runner).to enable_chef_handler(\"Chef::Handler::JsonFile\").with(\n source: \"chef/handler/json_file\",\n arguments: { :path => '/var/chef/reports'},\n supports: {:exception => true}\n )\n end\n```\n\nLicense and Author\n==================\n\nAuthor:: Seth Chisamore ()\n\nCopyright:: 2011, Chef Software, Inc\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","maintainer":"Chef Software, Inc.","maintainer_email":"cookbooks@chef.io","license":"Apache 2.0","platforms":{},"dependencies":{},"recommendations":{},"suggestions":{},"conflicting":{},"providing":{},"replacing":{},"attributes":{},"groupings":{},"recipes":{}} \ No newline at end of file diff --git a/cookbooks/chef_handler/providers/default.rb b/cookbooks/chef_handler/providers/default.rb new file mode 100644 index 0000000..cd4e3ef --- /dev/null +++ b/cookbooks/chef_handler/providers/default.rb @@ -0,0 +1,86 @@ +# +# Author:: Seth Chisamore +# Cookbook Name:: chef_handler +# Provider:: default +# +# Copyright:: 2011-2013, Chef Software, Inc +# +# 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. +# + +include ::ChefHandler::Helpers + +def whyrun_supported? + true +end + +# This action needs to find an rb file that presumably contains the indicated class in it and the +# load that file. It needs to do this keeping in mind that the same handler class can get enabled +# and disabled multiple times and there may be multiple instances of them running around. The +# handler code may also have changed between actions. To handle all this, we parse the full class +# name and attempt to find its class object, in case it has already been loaded. If such a class +# is found, we then attempt to unload that class before we load the file requested. We use "load" +# instead of "require" because we want to reload the handler class in case it has changed and +# don't want the caching behavior of "require". +# +# Note that during this process, we also need to keep track of the current handler configuration. +# Any of the above steps might fail - in which case we would not want to be in a situation where +# we have a registered handler that has been unloaded or mangled. +action :enable do + class_name = new_resource.class_name + new_resource.supports.each do |type, enable| + if enable + converge_by("disable #{class_name} as a #{type} handler") do + unregister_handler(type, class_name) + end + end + end + handler = nil + converge_by("load #{class_name} from #{new_resource.source}") do + klass = reload_class(class_name, new_resource.source) + handler = klass.send(:new, *collect_args(new_resource.arguments)) + end + new_resource.supports.each do |type, enable| + if enable + converge_by("enable #{new_resource} as a #{type} handler") do + register_handler(type, handler) + end + end + end +end + +action :disable do + new_resource.supports.each_key do |type| + converge_by("disable #{new_resource} as a #{type} handler") do + unregister_handler(type, new_resource.class_name) + end + end +end + +def load_current_resource + @current_resource = Chef::Resource::ChefHandler.new(new_resource.name) + @current_resource.class_name(new_resource.class_name) + @current_resource.source(new_resource.source) + @current_resource +end + +private + +def collect_args(resource_args = []) + if resource_args.is_a? Array + resource_args + else + [resource_args] + end +end + diff --git a/cookbooks/chef_handler/recipes/default.rb b/cookbooks/chef_handler/recipes/default.rb new file mode 100644 index 0000000..b8935c5 --- /dev/null +++ b/cookbooks/chef_handler/recipes/default.rb @@ -0,0 +1,33 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: chef_handlers +# Recipe:: default +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +Chef::Log.info("Chef Handlers will be at: #{node['chef_handler']['handler_path']}") + +remote_directory node['chef_handler']['handler_path'] do + source 'handlers' + # Just inherit permissions on Windows, don't try to set POSIX perms + if node["platform"] != "windows" + owner node['chef_handler']['root_user'] + group node['chef_handler']['root_group'] + mode "0755" + recursive true + end + action :nothing +end.run_action(:create) diff --git a/cookbooks/chef_handler/recipes/json_file.rb b/cookbooks/chef_handler/recipes/json_file.rb new file mode 100644 index 0000000..cd831bd --- /dev/null +++ b/cookbooks/chef_handler/recipes/json_file.rb @@ -0,0 +1,28 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: chef_handlers +# Recipe:: json_file +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +# force resource actions in compile phase so exception handler +# fires for compile phase exceptions + +chef_handler "Chef::Handler::JsonFile" do + source "chef/handler/json_file" + arguments :path => '/var/chef/reports' + action :nothing +end.run_action(:enable) diff --git a/cookbooks/chef_handler/resources/default.rb b/cookbooks/chef_handler/resources/default.rb new file mode 100644 index 0000000..7b2ebd2 --- /dev/null +++ b/cookbooks/chef_handler/resources/default.rb @@ -0,0 +1,34 @@ +# +# Author:: Seth Chisamore +# Cookbook Name:: chef_handler +# Resource:: default +# +# Copyright:: 2011-2013, Chef Software, Inc +# +# 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. +# + +actions :enable, :disable + +attribute :class_name, :kind_of => String, :name_attribute => true +attribute :source, :default => nil, :kind_of => String +attribute :arguments, :default => [] +attribute :supports, :kind_of => Hash, :default => { :report => true, :exception => true } + +# we have to set default for the supports attribute +# in initializer since it is a 'reserved' attribute name +def initialize(*args) + super + @action = :enable + @supports = { :report => true, :exception => true } +end diff --git a/cookbooks/database/CHANGELOG.md b/cookbooks/database/CHANGELOG.md new file mode 100644 index 0000000..40a0234 --- /dev/null +++ b/cookbooks/database/CHANGELOG.md @@ -0,0 +1,207 @@ +Database cookbook README +======================== + +v4.0.6 (2015-04-29) +------------------- +- #126 - Use sql_query property instead of sql in the mysql provider for :query action + +v4.0.5 (2015-04-08) +------------------- +- #137/#138 - Removing log message containing password information + +v4.0.4 (2015-04-07) +------------------- +- Using unescaped db name in field value + +v4.0.3 (2015-02-22) +------------------- +- Unbreak postgresql_database_resource on older versions of PostgreSQL + +v4.0.2 (2015-02-09) +------------------- +- Removing leftover mysql recipe that installs the mysql2_chef_gem. + +v4.0.1 (2015-02-05) +------------------- +- Fixing merge conflicts with master on 4.0.0 attempted release + +v4.0.0 (2015-02-05) +------------------- +- Decoupled mysql2_chef_gem cookbook. + Users must now install it themselves before utilizing mysql_database + or mysql_database_user resources. +- Fixing various MilClass errors in mysql providers +- Restoring missing :query action for mysql +- Restoring grant_option support for mysql +- Adding revoke action for mysql + +v3.1.0 (2015-01-30) +------------------- +- Add support for postgresql_database_user privileges +- Add postgresql_database_test cookbook to test/fixtures + +v3.0.3 (2015-01-20) +------------------- +- Bugfix: bugfix: lack of node['mysql']['version'] causing NilClass error + +v3.0.2 (2015-01-16) +------------------- +- Fix bug to allow grants on databases with special characters + +v3.0.1 (2015-01-16) +------------------- +- Enabling ssl for provider_mysql_database_user + +v3.0.0 (2015-01-15) +------------------- +- Removing out of scope recipes +- porting to mysql2_chef_gem +- adding test-kitchen suites for mysql + +v2.3.1 (2014-12-13) +------------------- +- Locking mysql and mysql-chef_gem dependencies down in metadata.rb + +v2.3.0 (2014-08-13) +------------------- +- [#62] Allow requiring SSL + + +v2.2.0 (2014-05-07) +------------------- +- [COOK-4626] Add windows users for SQL Server +- [COOK-4627] Assigning sys_roles in SQL Server + + +v2.1.10 (2014-05-07) +-------------------- +- [COOK-4614] - Update README to reflect gem installation via mysql-chef_gem + + +v2.1.8 (2014-04-23) +------------------- +- [COOK-4583] - Add ChefSpec matchers + + +v2.1.6 (2014-04-10) +------------------- +- [COOK-4538] Bump supported Chef version + + +v2.1.4 (2014-04-09) +------------------- +[COOK-4529] Query action ignores MySQL errors + + +v2.1.2 (2014-04-01) +------------------- +- Depending on mysql-chef_gem cookbook + + +v2.1.0 (2014-03-31) +------------------- +- Updating mysql cookbook dependency +- Enforcing rubocops + + +v2.0.0 (2014-02-25) +------------------- +[COOK-3441] database_user password argument should not be required + + +v1.6.0 +------ +### New Feature +- **[COOK-4009](https://tickets.chef.io/browse/COOK-4009)** - Add PostgreSQL SCHEMA management capability + +### Improvement +- **[COOK-3862](https://tickets.chef.io/browse/COOK-3862)** - Improve database cookbook documentation + + +v1.5.2 +------ +### Improvement +- **[COOK-3716](https://tickets.chef.io/browse/COOK-3716)** - Add ALTER SQL Server user roles + + +v1.5.0 +------ +### Improvement +- **[COOK-3546](https://tickets.chef.io/browse/COOK-3546)** - Add connection parameters `:socket` +- **[COOK-1709](https://tickets.chef.io/browse/COOK-1709)** - Add 'grant_option' parameter + +v1.4.0 +------- +### Bug +- [COOK-2074]: Regex in exists? check in `sql_server_database` resource should match for start and end of line +- [COOK-2561]: `mysql_database_user` can't set global grants + +### Improvement + +- [COOK-2075]: Support the collation attribute in the `database_sql_server` provider + +v1.3.12 +------- +- [COOK-850] - `postgresql_database_user` doesn't have example + +v1.3.10 +------- +- [COOK-2117] - undefined variable `grant_statement` in mysql user provider + +v1.3.8 +------ +- [COOK-1896] - Escape command +- [COOK-2047] - Chef::Provider::Database::MysqlUser action :grant improperly quotes `username`@`host` string +- [COOK-2060] - Mysql::Error: Table '*.*' doesn't exist when privileges include SELECT and database/table attributes are nil +- [COOK-2062] - Remove backticks from database name when using wildcard + +v1.3.6 +------ +- [COOK-1688] - fix typo in readme and add amazon linux to supported platforms + +v1.3.4 +------ +- [COOK-1561] - depend on mysql 1.3.0+ explicitly +- depend on postgresql 1.0.0 explicitly + +v1.3.2 +------ +- Update the version for release (oops) + +v1.3.0 +------ +- [COOK-932] - Add mysql recipe to conveniently include mysql::ruby +- [COOK-1228] - database resource should be able to execute scripts on disk +- [COOK-1291] - make the snapshot retention policy less confusing +- [COOK-1401] - Allow to specify the collation of new databases +- [COOK-1534] - Add postgresql recipe to conveniently include postgresql::ruby + +v1.2.0 +------ +- [COOK-970] - workaround for disk [re]naming on ubuntu 11.04+ +- [COOK-1085] - check RUBY_VERSION and act accordingly for role +- [COOK-749] - localhost should be a string in snapshot recipe + +v1.1.4 +------ +- [COOK-1062] - Databases: Postgres exists should close connection + +v1.1.2 +------ +- [COOK-975] - Change arg='DEFAULT' to arg=nil, :default => 'DEFAULT' +- [COOK-964] - Add parentheses around connection hash in example + +v1.1.0 +------ +- [COOK-716] - providers for PostgreSQL + +v1.0.0 +------ +- [COOK-683] - added `database` and `database_user` resources +- [COOK-684] - MySQL providers +- [COOK-685] - SQL Server providers +- refactored - `database::master` and `database::snapshot` recipes to leverage new resources + +v0.99.1 +------- +- Use Chef 0.10's `node.chef_environment` instead of `node['app_environment']`. diff --git a/cookbooks/database/README.md b/cookbooks/database/README.md new file mode 100644 index 0000000..a88f221 --- /dev/null +++ b/cookbooks/database/README.md @@ -0,0 +1,647 @@ +Database Cookbook +================= +The main highlight of this cookbook is the `database` and +`database_user` resources for managing databases and database users in +a RDBMS. Providers for MySQL, PostgreSQL and SQL Server are also +provided, see usage documentation below. + +Requirements +------------ +Chef version 0.11+ + +### Platforms +- Debian, Ubuntu +- Red Hat, CentOS, Scientific, Fedora, Amazon + +### Cookbooks +The following Chef Software cookbooks are dependencies: + +* postgresql + +Resources/Providers +------------------- +These resources aim to expose an abstraction layer for interacting +with different RDBMS in a general way. Currently the cookbook ships +with providers for MySQL, PostgreSQL and SQL Server. Please see +specific usage in the __Example__ sections below. The providers use +specific Ruby gems installed under Chef's Ruby environment to execute +commands and carry out actions. These gems will need to be installed +before the providers can operate correctly. Specific notes for each +RDBS flavor: + +- MySQL: leverages the `mysql2` gem, which can be installed with the + `mysql2_chef_gem` resource prior to use (available on the + Supermarket). You must depend on the `mysql2_chef_gem` cookbook, + then use a `mysql2_chef_gem` resource to install it. The resource + allows the user to select MySQL client library versions, as well as + optionally select MariaDB libraries. + +- PostgreSQL: leverages the `pg` gem which is installed as part of the + `postgresql::ruby` recipe. You must declare `include_recipe + "database::postgresql"` to include this. + +- SQL Server: leverages the `tiny_tds` gem which is installed as part + of the `sql_server::client` recipe. + +### database +Manage databases in a RDBMS. Use the proper shortcut resource +depending on your RDBMS: `mysql_database`, `postgresql_database` or +`sql_server_database`. + +#### Actions +- :create: create a named database +- :drop: drop a named database +- :query: execute an arbitrary query against a named database + +#### Attribute Parameters +- database_name: name attribute. Name of the database to interact with +- connection: hash of connection info. valid keys include `:host`, + `:port`, `:username`, and `:password` (only for MySQL DB*) + +- sql: string of sql or a block that executes to a string of sql, + which will be executed against the database. used by `:query` action + only + +\* The database cookbook uses the `mysql2` gem. + +> "The value of host may be either a host name or an IP address. If + host is NULL or the string "127.0.0.1", a connection to the local + host is assumed. For Windows, the client connects using a + shared-memory connection, if the server has shared-memory + connections enabled. Otherwise, TCP/IP is used. For a host value of + "." on Windows, the client connects using a named pipe, if the + server has named-pipe connections enabled. If named-pipe connections + are not enabled, an error occurs." + +If you specify a `:socket` key and are using the mysql_service +resource to set up the MySQL service, you'll need to specify the path +in the form `/var/run/mysql-/mysqld.sock`. + +#### Providers +- `Chef::Provider::Database::Mysql`: shortcut resource `mysql_database` +- `Chef::Provider::Database::Postgresql`: shortcut resource `postgresql_database` +- `Chef::Provider::Database::SqlServer`: shortcut resource `sql_server_database` + +#### Examples +```ruby +# Create a mysql database +mysql_database 'wordpress-cust01' do + connection( + :host => '127.0.0.1', + :username => 'root', + :password => node['wordpress-cust01']['mysql']['initial_root_password'] + ) + action :create +end +``` +```ruby +# Create a mysql database on a named mysql instance +mysql_database 'oracle_rools' do + connection( + :host => '127.0.0.1', + :username => 'root', + :socket => "/var/run/mysql-#{instance-name}/mysqld.sock" + :password => node['mysql']['server_root_password'] + ) + action :create +end +``` +```ruby +# Create a sql server database +sql_server_database 'mr_softie' do + connection( + :host => '127.0.0.1', + :port => node['sql_server']['port'], + :username => 'sa', + :password => node['sql_server']['server_sa_password'] + ) + action :create +end +``` + +```ruby +# create a postgresql database +postgresql_database 'mr_softie' do + connection( + :host => '127.0.0.1', + :port => 5432, + :username => 'postgres', + :password => node['postgresql']['password']['postgres'] + ) + action :create +end +``` + +```ruby +# create a postgresql database with additional parameters +postgresql_database 'mr_softie' do + connection( + :host => '127.0.0.1', + :port => 5432, + :username => 'postgres', + :password => node['postgresql']['password']['postgres'] + ) + template 'DEFAULT' + encoding 'DEFAULT' + tablespace 'DEFAULT' + connection_limit '-1' + owner 'postgres' + action :create +end +``` + +```ruby +# Externalize conection info in a ruby hash +mysql_connection_info = { + :host => '127.0.0.1', + :username => 'root', + :password => node['mysql']['server_root_password'] +} + +sql_server_connection_info = { + :host => '127.0.0.1', + :port => node['sql_server']['port'], + :username => 'sa', + :password => node['sql_server']['server_sa_password'] +} + +postgresql_connection_info = { + :host => '127.0.0.1', + :port => node['postgresql']['config']['port'], + :username => 'postgres', + :password => node['postgresql']['password']['postgres'] +} + +# Same create commands, connection info as an external hash +mysql_database 'foo' do + connection mysql_connection_info + action :create +end + +sql_server_database 'foo' do + connection sql_server_connection_info + action :create +end + +postgresql_database 'foo' do + connection postgresql_connection_info + action :create +end + +# Create database, set provider in resource parameter +database 'bar' do + connection mysql_connection_info + provider Chef::Provider::Database::Mysql + action :create +end + +database 'bar' do + connection sql_server_connection_info + provider Chef::Provider::Database::SqlServer + action :create +end + +database 'bar' do + connection postgresql_connection_info + provider Chef::Provider::Database::Postgresql + action :create +end + + + +# Drop a database +mysql_database 'baz' do + connection mysql_connection_info + action :drop +end + + + +# Query a database +mysql_database 'flush the privileges' do + connection mysql_connection_info + sql 'flush privileges' + action :query +end + + +# Query a database from a sql script on disk +mysql_database 'run script' do + connection mysql_connection_info + sql { ::File.open('/path/to/sql_script.sql').read } + action :query +end + + + +# Vacuum a postgres database +postgresql_database 'vacuum databases' do + connection postgresql_connection_info + database_name 'template1' + sql 'VACUUM FULL VERBOSE ANALYZE' + action :query +end +``` + +### database_user +Manage users and user privileges in a RDBMS. Use the proper shortcut resource depending on your RDBMS: `mysql_database_user`, `postgresql_database_user`, or `sql_server_database_user`. + +#### Actions +- :create: create a user +- :drop: drop a user +- :grant: manipulate user privileges on database objects + +#### Attribute Parameters +- username: name attribute. Name of the database user +- password: password for the user account +- database_name: Name of the database to interact with +- connection: hash of connection info. valid keys include :host, + :port, :username, :password +- privileges: array of database privileges to grant user. used by the + :grant action. default is :all +- host: host where user connections are allowed from. used by MySQL + provider only. default is '127.0.0.1' +- table: table to grant privileges on. used by :grant action and MySQL + provider only. default is '*' (all tables) +- require_ssl: true or false to force SSL connections to be used for user + +### Providers + +- **Chef::Provider::Database::MysqlUser**: shortcut resource + `mysql_database_user` +- **Chef::Provider::Database::PostgresqlUser**: shortcut + resource `postgresql_database_user` +- **Chef::Provider::Database::SqlServerUser**: shortcut resource + `sql_server_database_user` + +### Examples + + # create connection info as an external ruby hash + mysql_connection_info = {:host => "127.0.0.1", + :username => 'root', + :password => node['mysql']['server_root_password']} + postgresql_connection_info = {:host => "127.0.0.1", + :port => node['postgresql']['config']['port'], + :username => 'postgres', + :password => node['postgresql']['password']['postgres']} + sql_server_connection_info = {:host => "127.0.0.1", + :port => node['sql_server']['port'], + :username => 'sa', + :password => node['sql_server']['server_sa_password']} + + # create a mysql user but grant no privileges + mysql_database_user 'disenfranchised' do + connection mysql_connection_info + password 'super_secret' + action :create + end + + # do the same but pass the provider to the database resource + database_user 'disenfranchised' do + connection mysql_connection_info + password 'super_secret' + provider Chef::Provider::Database::MysqlUser + action :create + end + + # create a postgresql user but grant no privileges + postgresql_database_user 'disenfranchised' do + connection postgresql_connection_info + password 'super_secret' + action :create + end + + # do the same but pass the provider to the database resource + database_user 'disenfranchised' do + connection postgresql_connection_info + password 'super_secret' + provider Chef::Provider::Database::PostgresqlUser + action :create + end + + # create a sql server user but grant no privileges + sql_server_database_user 'disenfranchised' do + connection sql_server_connection_info + password 'super_secret' + action :create + end + + # drop a mysql user + mysql_database_user "foo_user" do + connection mysql_connection_info + action :drop + end + + # bulk drop sql server users + %w{ disenfranchised foo_user }.each do |user| + sql_server_database_user user do + connection sql_server_connection_info + action :drop + end + end + + # grant select,update,insert privileges to all tables in foo db from all hosts, requiring connections over SSL + mysql_database_user 'foo_user' do + connection mysql_connection_info + password 'super_secret' + database_name 'foo' + host '%' + privileges [:select,:update,:insert] + require_ssl true + action :grant + end + + # grant all privileges on all databases/tables from 127.0.0.1 + mysql_database_user 'super_user' do + connection mysql_connection_info + password 'super_secret' + action :grant + end + + # grant all privileges on all tables in foo db + postgresql_database_user 'foo_user' do + connection postgresql_connection_info + database_name 'foo' + privileges [:all] + action :grant + end + + # grant select,update,insert privileges to all tables in foo db + sql_server_database_user 'foo_user' do + connection sql_server_connection_info + password 'super_secret' + database_name 'foo' + privileges [:select,:update,:insert] + action :grant + end + +#### Providers +- `Chef::Provider::Database::MysqlUser`: shortcut resource `mysql_database_user` +- `Chef::Provider::Database::PostgresqlUser`: shortcut resource `postgresql_database_user` +- `Chef::Provider::Database::SqlServerUser`: shortcut resource`sql_server_database_user` + +#### Examples + +```ruby +# create connection info as an external ruby hash +mysql_connection_info = { + :host => '127.0.0.1', + :username => 'root', + :password => node['mysql']['server_root_password'] +} + +postgresql_connection_info = { + :host => '127.0.0.1', + :port => node['postgresql']['config']['port'], + :username => 'postgres', + :password => node['postgresql']['password']['postgres'] +} + +sql_server_connection_info = { + :host => '127.0.0.1', + :port => node['sql_server']['port'], + :username => 'sa', + :password => node['sql_server']['server_sa_password'] +} + + + +# Create a mysql user but grant no privileges +mysql_database_user 'disenfranchised' do + connection mysql_connection_info + password 'super_secret' + action :create +end + + + +# Do the same but pass the provider to the database resource +database_user 'disenfranchised' do + connection mysql_connection_info + password 'super_secret' + provider Chef::Provider::Database::MysqlUser + action :create +end + + + +# Create a postgresql user but grant no privileges +postgresql_database_user 'disenfranchised' do + connection postgresql_connection_info + password 'super_secret' + action :create +end + + + +# Do the same but pass the provider to the database resource +database_user 'disenfranchised' do + connection postgresql_connection_info + password 'super_secret' + provider Chef::Provider::Database::PostgresqlUser + action :create +end + + + +# Create a sql server user but grant no privileges +sql_server_database_user 'disenfranchised' do + connection sql_server_connection_info + password 'super_secret' + action :create +end + + + +# Drop a mysql user +mysql_database_user 'foo_user' do + connection mysql_connection_info + action :drop +end + + + +# Bulk drop sql server users +%w(disenfranchised foo_user).each do |user| + sql_server_database_user user do + connection sql_server_connection_info + action :drop + end +end + + + +# Grant SELECT, UPDATE, and INSERT privileges to all tables in foo db from all hosts +mysql_database_user 'foo_user' do + connection mysql_connection_info + password 'super_secret' + database_name 'foo' + host '%' + privileges [:select,:update,:insert] + action :grant +end + + + +# Grant all privileges on all databases/tables from 127.0.0.1 +mysql_database_user 'super_user' do + connection mysql_connection_info + password 'super_secret' + action :grant +end + + + +# Grant all privileges on all tables in foo db +postgresql_database_user 'foo_user' do + connection postgresql_connection_info + database_name 'foo' + privileges [:all] + action :grant +end + +# grant select,update,insert privileges to all tables in foo db +sql_server_database_user 'foo_user' do + connection sql_server_connection_info + password 'super_secret' + database_name 'foo' + privileges [:select,:update,:insert] + action :grant +end +``` + + +Recipes +------- +### ebs_volume +*Note*: This recipe does not currently work on RHEL platforms due to the xfs cookbook not supporting RHEL yet. + +Loads the aws information from the data bag. Searches the applications data bag for the database master or slave role and checks that role is applied to the node. Loads the EBS information and the master information from data bags. Uses the aws cookbook LWRP, `aws_ebs_volume` to manage the volume. + +On a master node: +- if we have an ebs volume already as stored in a data bag, attach it +- if we don't have the ebs information then create a new one and attach it +- store the volume information in a data bag via a ruby block + +On a slave node: +- use the master volume information to generate a snapshot +- create the new volume from the snapshot and attach it + +Also on a master node, generate some configuration for running a snapshot via `chef-solo` from cron. + +On a new filesystem volume, create as XFS, then mount it in `/mnt`, and also bind-mount it to the mysql data directory (default `/var/lib/mysql`). + +### master +This recipe no longer loads AWS specific information, and the database position for replication is no longer stored in a databag because the client might not have permission to write to the databag item. This may be handled in a different way at a future date. + +Searches the apps databag for applications, and for each one it will check that the specified database master role is set in both the databag and applied to the node's run list. Then, retrieves the passwords for `root`, `repl` and `debian` users and saves them to the node attributes. If the passwords are not found in the databag, it prints a message that they'll be generated by the mysql cookbook. + +Then it adds the application databag database settings to a hash, to use later. + +Then it will iterate over the databases and create them with the `mysql_database` resource while adding privileges for application specific database users using the `mysql_database_user` resource. + +### slave +_TODO_: Retrieve the master status from a data bag, then start replication using a ruby block. The replication status needs to be handled in some other way for now since the master recipe above doesn't actually set it in the databag anymore. + +### snapshot +Run via Chef Solo. Retrieves the db snapshot configuration from the specified JSON file. Uses the `mysql_database` resource to lock and unlock tables, and does a filesystem freeze and EBS snapshot. + + +Deprecated Recipes +------------------ +The following recipe is considered deprecated. It is kept for reference purposes. + +### ebs_backup +Older style of doing mysql snapshot and replication using Adam Jacob's [ec2_mysql](http://github.com/adamhjk/ec2_mysql) script and library. + + +Data Bags +--------- +This cookbook uses the apps data bag item for the specified application; see the `application` cookbook's README.md. It also creates data bag items in a bag named 'aws' for storing volume information. In order to interact with EC2, it expects aws to have a main item: + +```javascript +{ + "id": "main", + "ec2_private_key": "private key as a string", + "ec2_cert": "certificate as a string", + "aws_account_id": "", + "aws_secret_access_key": "", + "aws_access_key_id": "" +} +``` + +Note: with the Open Source Chef Server, the server using the database recipes must be an admin client or it will not be able to create data bag items. You can modify whether the client is admin by editing it with knife. + + knife client edit + { + ... + "admin": true + ... + } + +This is not required if the Chef Server is Chef Software Hosted Chef, instead use the ACL feature to modify access for the node to be able to update the data bag. + + +Usage +----- +Aside from the application data bag (see the README in the application cookbook), create a role for the database master. Use a `role.rb` in your chef-repo, or create the role directly with knife. + +```javascript +{ + "name": "my_app_database_master", + "chef_type": "role", + "json_class": "Chef::Role", + "default_attributes": {}, + "description": "", + "run_list": [ + "recipe[mysql::server]", + "recipe[database::master]" + ], + "override_attributes": {} +} +``` + +Create a `production` environment. This is also used in the `application` cookbook. + +```javascript +{ + "name": "production", + "description": "", + "cookbook_versions": {}, + "json_class": "Chef::Environment", + "chef_type": "environment", + "default_attributes": {}, + "override_attributes": {} +} +``` + +The cookbook `my_app_database` is recommended to set up any +application specific database resources such as configuration +templates, trending monitors, etc. It is not required, but you would +need to create it separately in `site-cookbooks`. Add it to the +`my_app_database_master` role. + +License & Authors +----------------- +- Author:: Adam Jacob () +- Author:: Joshua Timberman () +- Author:: AJ Christensen () +- Author:: Seth Chisamore () +- Author:: Lamont Granquist () +- Author:: Sean OMeara () + +```text +Copyright 2009-2015, Chef Software, Inc. + +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/cookbooks/database/libraries/matchers.rb b/cookbooks/database/libraries/matchers.rb new file mode 100644 index 0000000..a45f2d1 --- /dev/null +++ b/cookbooks/database/libraries/matchers.rb @@ -0,0 +1,151 @@ +# +# Author:: Douglas Thrift () +# Cookbook Name:: database +# Library:: matchers +# +# Copyright 2014, Chef Software, Inc. +# +# 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. +# + +if defined?(ChefSpec) + # database + # + def create_database(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:database, :create, resource_name) + end + + def drop_database(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:database, :drop, resource_name) + end + + def query_database(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:database, :query, resource_name) + end + + # database user + # + def create_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:database_user, :create, resource_name) + end + + def drop_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:database_user, :drop, resource_name) + end + + def grant_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:database_user, :grant, resource_name) + end + + # mysql database + # + def create_mysql_database(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:mysql_database, :create, resource_name) + end + + def drop_mysql_database(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:mysql_database, :drop, resource_name) + end + + def query_mysql_database(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:mysql_database, :query, resource_name) + end + + # mysql database user + # + def create_mysql_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:mysql_database_user, :create, resource_name) + end + + def drop_mysql_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:mysql_database_user, :drop, resource_name) + end + + def grant_mysql_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:mysql_database_user, :grant, resource_name) + end + + # postgresql database + # + def create_postgresql_database(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:postgresql_database, :create, resource_name) + end + + def drop_postgresql_database(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:postgresql_database, :drop, resource_name) + end + + def query_postgresql_database(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:postgresql_database, :query, resource_name) + end + + # postgresql database schema + # + def create_postgresql_database_schema(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:postgresql_database_schema, :create, resource_name) + end + + def drop_postgresql_database_schema(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:postgresql_database_schema, :drop, resource_name) + end + + # postgresql database user + # + def create_postgresql_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:postgresql_database_user, :create, resource_name) + end + + def drop_postgresql_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:postgresql_database_user, :drop, resource_name) + end + + def grant_postgresql_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:postgresql_database_user, :grant, resource_name) + end + + def grant_schema_postgresql_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:postgresql_database_user, :grant_schema, resource_name) + end + + # sql server database + # + def create_sql_server_database(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:sql_server_database, :create, resource_name) + end + + def drop_database(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:sql_server_database, :drop, resource_name) + end + + def query_database(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:sql_server_database, :query, resource_name) + end + + # sql server database user + # + def create_sql_server_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:sql_server_database_user, :create, resource_name) + end + + def drop_sql_server_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:sql_server_database_user, :drop, resource_name) + end + + def grant_sql_server_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:sql_server_database_user, :grant, resource_name) + end + + def alter_roles_sql_server_database_user(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:sql_server_database_user, :alter_roles, resource_name) + end +end diff --git a/cookbooks/database/libraries/provider_database_mysql.rb b/cookbooks/database/libraries/provider_database_mysql.rb new file mode 100644 index 0000000..b37286d --- /dev/null +++ b/cookbooks/database/libraries/provider_database_mysql.rb @@ -0,0 +1,158 @@ +# +# Author:: Seth Chisamore () +# Author:: Sean OMeara () +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# 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. +# + +class Chef + class Provider + class Database + class Mysql < Chef::Provider::LWRPBase + use_inline_resources if defined?(use_inline_resources) + + def whyrun_supported? + true + end + + action :create do + # test + schema_present = nil + + begin + test_sql = 'SHOW SCHEMAS;' + Chef::Log.debug("#{new_resource.name}: Performing query [#{test_sql}]") + test_sql_results = test_client.query(test_sql) + test_sql_results.each do |r| + schema_present = true if r['Database'] == new_resource.database_name + end + ensure + close_test_client + end + + # repair + unless schema_present + converge_by "Creating schema '#{new_resource.database_name}'" do + begin + repair_sql = "CREATE SCHEMA IF NOT EXISTS `#{new_resource.database_name}`" + repair_sql += " CHARACTER SET = #{new_resource.encoding}" if new_resource.encoding + repair_sql += " COLLATE = #{new_resource.collation}" if new_resource.collation + Chef::Log.debug("#{new_resource.name}: Performing query [#{repair_sql}]") + repair_client.query(repair_sql) + ensure + close_repair_client + end + end + end + end + + action :drop do + # test + schema_present = nil + + begin + test_sql = 'SHOW SCHEMAS;' + Chef::Log.debug("Performing query [#{test_sql}]") + test_sql_results = test_client.query(test_sql) + test_sql_results.each do |r| + schema_present = true if r['Database'] == new_resource.database_name + end + ensure + close_test_client + end + + # repair + if schema_present + converge_by "Dropping schema '#{new_resource.database_name}'" do + begin + repair_sql = "DROP SCHEMA IF EXISTS `#{new_resource.database_name}`" + Chef::Log.debug("Performing query [#{repair_sql}]") + repair_client.query(repair_sql) + ensure + close_repair_client + end + end + end + end + + action :query do + begin + query_sql = new_resource.sql_query + Chef::Log.debug("Performing query [#{query_sql}]") + query_client.query(query_sql) + ensure + close_query_client + end + end + + private + + def test_client + require 'mysql2' + @test_client ||= + Mysql2::Client.new( + host: new_resource.connection[:host], + socket: new_resource.connection[:socket], + username: new_resource.connection[:username], + password: new_resource.connection[:password], + port: new_resource.connection[:port] + ) + end + + def close_test_client + @test_client.close if @test_client + rescue Mysql2::Error + @test_client = nil + end + + def repair_client + require 'mysql2' + @repair_client ||= + Mysql2::Client.new( + host: new_resource.connection[:host], + socket: new_resource.connection[:socket], + username: new_resource.connection[:username], + password: new_resource.connection[:password], + port: new_resource.connection[:port] + ) + end + + def close_repair_client + @repair_client.close if @repair_client + rescue Mysql2::Error + @repair_client = nil + end + + def query_client + require 'mysql2' + @query_client ||= + Mysql2::Client.new( + host: new_resource.connection[:host], + socket: new_resource.connection[:socket], + username: new_resource.connection[:username], + password: new_resource.connection[:password], + port: new_resource.connection[:port] + ) + end + + def close_query_client + @query_client.close + rescue Mysql2::Error + @query_client = nil + end + end + end + end +end diff --git a/cookbooks/database/libraries/provider_database_mysql_user.rb b/cookbooks/database/libraries/provider_database_mysql_user.rb new file mode 100644 index 0000000..5a650de --- /dev/null +++ b/cookbooks/database/libraries/provider_database_mysql_user.rb @@ -0,0 +1,193 @@ +# +# Author:: Seth Chisamore () +# Author:: Sean OMeara () +# Copyright:: 2011-2015 Chef Software, Inc. +# 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. +# + +require File.join(File.dirname(__FILE__), 'provider_database_mysql') + +class Chef + class Provider + class Database + class MysqlUser < Chef::Provider::Database::Mysql + use_inline_resources if defined?(use_inline_resources) + + def whyrun_supported? + true + end + + action :create do + # test + user_present = nil + begin + test_sql = "SELECT User,Host from mysql.user WHERE User='#{new_resource.username}' AND Host='#{new_resource.host}';" + test_sql_results = test_client.query(test_sql) + test_sql_results.each do |r| + user_present = true if r['User'] == new_resource.username + end + ensure + close_test_client + end + + # repair + unless user_present + converge_by "Creating user '#{new_resource.username}'@'#{new_resource.host}'" do + begin + repair_sql = "CREATE USER '#{new_resource.username}'@'#{new_resource.host}'" + repair_sql += " IDENTIFIED BY '#{new_resource.password}'" if new_resource.password + repair_client.query(repair_sql) + ensure + close_repair_client + end + end + end + end + + action :drop do + # test + user_present = nil + begin + test_sql = 'SELECT User,Host' + test_sql += ' from mysql.user' + test_sql += " WHERE User='#{new_resource.username}'" + test_sql += " AND Host='#{new_resource.host}'" + test_sql_results = test_client.query test_sql + test_sql_results.each do |r| + user_present = true if r['User'] == new_resource.username + end + ensure + close_test_client + end + + # repair + if user_present + converge_by "Dropping user '#{new_resource.username}'@'#{new_resource.host}'" do + begin + repair_sql = 'DROP USER' + repair_sql += " '#{new_resource.username}'@'#{new_resource.host}'" + repair_client.query repair_sql + ensure + close_repair_client + end + end + end + end + + action :grant do + # gratuitous function + def ishash? + return true if (/(\A\*[0-9A-F]{40}\z)/i).match(new_resource.password) + end + + db_name = new_resource.database_name ? "`#{new_resource.database_name}`" : '*' + tbl_name = new_resource.table ? new_resource.table : '*' + + # Test + incorrect_privs = nil + begin + test_sql = 'SELECT * from mysql.db' + test_sql += " WHERE User='#{new_resource.username}'" + test_sql += " AND Host='#{new_resource.host}'" + test_sql += " AND Db='#{new_resource.database_name}'" + test_sql_results = test_client.query test_sql + + incorrect_privs = true if test_sql_results.size == 0 + # These should all by 'Y' + test_sql_results.each do |r| + new_resource.privileges.each do |p| + key = "#{p.capitalize}_priv" + incorrect_privs = true if r[key] != 'Y' + end + end + ensure + close_test_client + end + + # Repair + if incorrect_privs + converge_by "Granting privs for '#{new_resource.username}'@'#{new_resource.host}'" do + begin + repair_sql = "GRANT #{new_resource.privileges.join(',')}" + repair_sql += " ON #{db_name}.#{tbl_name}" + repair_sql += " TO '#{new_resource.username}'@'#{new_resource.host}' IDENTIFIED BY" + repair_sql += " '#{new_resource.password}'" + repair_sql += ' REQUIRE SSL' if new_resource.require_ssl + repair_sql += ' WITH GRANT OPTION' if new_resource.grant_option + + repair_client.query(repair_sql) + repair_client.query('FLUSH PRIVILEGES') + ensure + close_repair_client + end + end + end + end + + def action_revoke + db_name = new_resource.database_name ? "`#{new_resource.database_name}`" : '*' + tbl_name = new_resource.table ? new_resource.table : '*' + + revoke_statement = "REVOKE #{@new_resource.privileges.join(', ')}" + revoke_statement += " ON #{db_name}.#{tbl_name}" + revoke_statement += " FROM `#{@new_resource.username}`@`#{@new_resource.host}` " + Chef::Log.info("#{@new_resource}: revoking access with statement [#{revoke_statement}]") + db.query(revoke_statement) + @new_resource.updated_by_last_action(true) + ensure + close + end + + private + + def test_client + require 'mysql2' + @test_client ||= + Mysql2::Client.new( + host: new_resource.connection[:host], + socket: new_resource.connection[:socket], + username: new_resource.connection[:username], + password: new_resource.connection[:password], + port: new_resource.connection[:port] + ) + end + + def close_test_client + @test_client.close if @test_client + rescue Mysql2::Error + @test_client = nil + end + + def repair_client + require 'mysql2' + @repair_client ||= + Mysql2::Client.new( + host: new_resource.connection[:host], + socket: new_resource.connection[:socket], + username: new_resource.connection[:username], + password: new_resource.connection[:password], + port: new_resource.connection[:port] + ) + end + + def close_repair_client + @repair_client.close if @repair_client + rescue Mysql2::Error + @repair_client = nil + end + end + end + end +end diff --git a/cookbooks/database/libraries/provider_database_postgresql.rb b/cookbooks/database/libraries/provider_database_postgresql.rb new file mode 100644 index 0000000..387c3b8 --- /dev/null +++ b/cookbooks/database/libraries/provider_database_postgresql.rb @@ -0,0 +1,144 @@ +# +# Author:: Seth Chisamore () +# Author:: Lamont Granquist () +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# 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. +# + +require 'chef/provider' + +class Chef + class Provider + class Database + class Postgresql < Chef::Provider::LWRPBase + use_inline_resources if defined?(use_inline_resources) + + def whyrun_supported? + true + end + + def load_current_resource + Gem.clear_paths + require 'pg' + @current_resource = Chef::Resource::Database.new(@new_resource.name) + @current_resource.database_name(@new_resource.database_name) + @current_resource + end + + action :create do + unless exists? + begin + encoding = @new_resource.encoding + if encoding != 'DEFAULT' + encoding = "'#{@new_resource.encoding}'" + end + Chef::Log.debug("#{@new_resource}: Creating database #{new_resource.database_name}") + create_sql = "CREATE DATABASE \"#{new_resource.database_name}\"" + create_sql += " TEMPLATE = #{new_resource.template}" if new_resource.template + create_sql += " ENCODING = #{encoding}" if new_resource.encoding + create_sql += " TABLESPACE = #{new_resource.tablespace}" if new_resource.tablespace + create_sql += " LC_CTYPE = '#{new_resource.collation}' LC_COLLATE = '#{new_resource.collation}'" if new_resource.collation + create_sql += " CONNECTION LIMIT = #{new_resource.connection_limit}" if new_resource.connection_limit + create_sql += " OWNER = \"#{new_resource.owner}\"" if new_resource.owner + Chef::Log.debug("#{@new_resource}: Performing query [#{create_sql}]") + db('template1').query(create_sql) + @new_resource.updated_by_last_action(true) + ensure + close + end + end + end + + action :drop do + if exists? + begin + Chef::Log.debug("#{@new_resource}: Dropping database #{new_resource.database_name}") + db('template1').query("DROP DATABASE \"#{new_resource.database_name}\"") + @new_resource.updated_by_last_action(true) + ensure + close + end + end + end + + action :query do + if exists? + begin + Chef::Log.debug("#{@new_resource}: Performing query [#{new_resource.sql_query}]") + db(@new_resource.database_name).query(@new_resource.sql_query) + Chef::Log.debug("#{@new_resource}: query [#{new_resource.sql_query}] succeeded") + @new_resource.updated_by_last_action(true) + ensure + close + end + end + end + + private + + def exists? + begin + Chef::Log.debug("#{@new_resource}: checking if database #{@new_resource.database_name} exists") + ret = db('template1').query("SELECT * FROM pg_database where datname = '#{@new_resource.database_name}'").num_tuples != 0 + ret ? Chef::Log.debug("#{@new_resource}: database #{@new_resource.database_name} exists") : + Chef::Log.debug("#{@new_resource}: database #{@new_resource.database_name} does not exist") + ensure + close + end + ret + end + + # Test if text is psql keyword + def keyword?(text) + begin + result = db('template1').exec_params('select * from pg_get_keywords() where word = $1', [text.downcase]).num_tuples != 0 + ensure + close + end + result + end + + # + # Specifying the database in the connection parameter for the postgres resource is not recommended. + # + # - action_create/drop/exists will use the "template1" database to do work by default. + # - action_query will use the resource database_name. + # - specifying a database in the connection will override this behavior + # + def db(dbname = nil) + close if @db + dbname = @new_resource.connection[:database] if @new_resource.connection[:database] + host = @new_resource.connection[:host] + port = @new_resource.connection[:port] || 5432 + user = @new_resource.connection[:username] || 'postgres' + Chef::Log.debug("#{@new_resource}: connecting to database #{dbname} on #{host}:#{port} as #{user}") + password = @new_resource.connection[:password] || node[:postgresql][:password][:postgres] + @db = ::PGconn.new( + host: host, + port: port, + dbname: dbname, + user: user, + password: password + ) + end + + def close + @db.close rescue nil + @db = nil + end + end + end + end +end diff --git a/cookbooks/database/libraries/provider_database_postgresql_schema.rb b/cookbooks/database/libraries/provider_database_postgresql_schema.rb new file mode 100644 index 0000000..7c8bd89 --- /dev/null +++ b/cookbooks/database/libraries/provider_database_postgresql_schema.rb @@ -0,0 +1,74 @@ +# +# Author:: Marco Betti () +# Copyright:: Copyright (c) 2013 Chef Software, Inc. +# 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. +# + +require File.join(File.dirname(__FILE__), 'provider_database_postgresql') + +class Chef + class Provider + class Database + class PostgresqlSchema < Chef::Provider::Database::Postgresql + include Chef::Mixin::ShellOut + + def load_current_resource + Gem.clear_paths + require 'pg' + @current_resource = Chef::Resource::PostgresqlDatabaseSchema.new(@new_resource.name) + @current_resource.schema_name(@new_resource.schema_name) + @current_resource + end + + def action_create + unless exists? + begin + if new_resource.owner + db(@new_resource.database_name).query("CREATE SCHEMA \"#{@new_resource.schema_name}\" AUTHORIZATION \"#{@new_resource.owner}\"") + else + db(@new_resource.database_name).query("CREATE SCHEMA \"#{@new_resource.schema_name}\"") + end + @new_resource.updated_by_last_action(true) + ensure + close + end + end + end + + def action_drop + if exists? + begin + db(@new_resource.database_name).query("DROP SCHEMA \"#{@new_resource.schema_name}\"") + @new_resource.updated_by_last_action(true) + ensure + close + end + end + end + + private + + def exists? + begin + exists = db(@new_resource.database_name).query("SELECT schema_name FROM information_schema.schemata WHERE schema_name='#{@new_resource.schema_name}'").num_tuples != 0 + ensure + close + end + exists + end + end + end + end +end diff --git a/cookbooks/database/libraries/provider_database_postgresql_user.rb b/cookbooks/database/libraries/provider_database_postgresql_user.rb new file mode 100644 index 0000000..079ae1d --- /dev/null +++ b/cookbooks/database/libraries/provider_database_postgresql_user.rb @@ -0,0 +1,103 @@ +# +# Author:: Seth Chisamore () +# Author:: Lamont Granquist () +# Author:: Marco Betti () +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# 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. +# + +require File.join(File.dirname(__FILE__), 'provider_database_postgresql') + +class Chef + class Provider + class Database + class PostgresqlUser < Chef::Provider::Database::Postgresql + include Chef::Mixin::ShellOut + + def load_current_resource + Gem.clear_paths + require 'pg' + @current_resource = Chef::Resource::DatabaseUser.new(@new_resource.name) + @current_resource.username(@new_resource.name) + @current_resource + end + + def action_create + unless exists? + begin + options = '' + options += " PASSWORD '#{@new_resource.password}'" if @new_resource.password + options += " #{@new_resource.createdb ? 'CREATEDB' : 'NOCREATEDB'}" + options += " #{@new_resource.createrole ? 'CREATEROLE' : 'NOCREATEROLE'}" + options += " #{@new_resource.login ? 'LOGIN' : 'NOLOGIN'}" + options += " #{@new_resource.replication ? 'REPLICATION' : 'NOREPLICATION'}" if keyword?('REPLICATION') + options += " #{@new_resource.superuser ? 'SUPERUSER' : 'NOSUPERUSER'}" + + statement = "CREATE USER \"#{@new_resource.username}\"" + if options.length > 0 + statement += " WITH #{options}" + end + + db('template1').query(statement) + @new_resource.updated_by_last_action(true) + ensure + close + end + end + end + + def action_drop + if exists? + begin + db('template1').query("DROP USER \"#{@new_resource.username}\"") + @new_resource.updated_by_last_action(true) + ensure + close + end + end + end + + def action_grant + grant_statement = "GRANT #{@new_resource.privileges.join(', ')} ON DATABASE \"#{@new_resource.database_name}\" TO \"#{@new_resource.username}\"" + Chef::Log.info("#{@new_resource}: granting access with statement [#{grant_statement}]") + db(@new_resource.database_name).query(grant_statement) + @new_resource.updated_by_last_action(true) + ensure + close + end + + def action_grant_schema + grant_statement = "GRANT #{@new_resource.privileges.join(', ')} ON SCHEMA \"#{@new_resource.schema_name}\" TO \"#{@new_resource.username}\"" + Chef::Log.info("#{@new_resource}: granting access with statement [#{grant_statement}]") + db(@new_resource.database_name).query(grant_statement) + @new_resource.updated_by_last_action(true) + ensure + close + end + + private + + def exists? + begin + exists = db('template1').query("SELECT * FROM pg_user WHERE usename='#{@new_resource.username}'").num_tuples != 0 + ensure + close + end + exists + end + end + end + end +end diff --git a/cookbooks/database/libraries/provider_database_sql_server.rb b/cookbooks/database/libraries/provider_database_sql_server.rb new file mode 100644 index 0000000..518929c --- /dev/null +++ b/cookbooks/database/libraries/provider_database_sql_server.rb @@ -0,0 +1,111 @@ +# +# Author:: Seth Chisamore () +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# 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. +# + +require 'chef/provider' + +class Chef + class Provider + class Database + class SqlServer < Chef::Provider + include Chef::Mixin::ShellOut + + def load_current_resource + Gem.clear_paths + require 'tiny_tds' + @current_resource = Chef::Resource::Database.new(@new_resource.name) + @current_resource.database_name(@new_resource.database_name) + @current_resource + end + + def action_create + unless exists? + begin + Chef::Log.debug("#{@new_resource}: Creating database #{new_resource.database_name}") + create_sql = "CREATE DATABASE [#{new_resource.database_name}]" + create_sql += " COLLATE #{new_resource.collation}" if new_resource.collation + db.execute(create_sql).do + @new_resource.updated_by_last_action(true) + ensure + close + end + end + end + + def action_drop + if exists? + begin + Chef::Log.debug("#{@new_resource}: Dropping database #{new_resource.database_name}") + db.execute("DROP DATABASE [#{new_resource.database_name}]").do + @new_resource.updated_by_last_action(true) + ensure + close + end + end + end + + def action_query + if exists? + begin + # db.select_db(@new_resource.database_name) if @new_resource.database_name + Chef::Log.debug("#{@new_resource}: Performing query [#{new_resource.sql_query}]") + db.execute(@new_resource.sql_query).do + @new_resource.updated_by_last_action(true) + ensure + close + end + end + end + + private + + def exists? + exists = false + begin + result = db.execute('SELECT name FROM sys.databases') + result.each do |row| + if row['name'] == @new_resource.database_name + exists = true + break + end + end + result.cancel + ensure + close + end + exists + end + + def db + @db ||= begin + ::TinyTds::Client.new( + host: @new_resource.connection[:host], + username: @new_resource.connection[:username], + password: @new_resource.connection[:password], + port: @new_resource.connection[:port] || 1433 + ) + end + end + + def close + @db.close rescue nil + @db = nil + end + end + end + end +end diff --git a/cookbooks/database/libraries/provider_database_sql_server_user.rb b/cookbooks/database/libraries/provider_database_sql_server_user.rb new file mode 100644 index 0000000..d92338e --- /dev/null +++ b/cookbooks/database/libraries/provider_database_sql_server_user.rb @@ -0,0 +1,152 @@ +# +# Author:: Seth Chisamore () +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# 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. +# + +require File.join(File.dirname(__FILE__), 'provider_database_sql_server') + +class Chef + class Provider + class Database + class SqlServerUser < Chef::Provider::Database::SqlServer + include Chef::Mixin::ShellOut + + def load_current_resource + Gem.clear_paths + require 'tiny_tds' + @current_resource = Chef::Resource::DatabaseUser.new(@new_resource.name) + @current_resource.username(@new_resource.name) + @current_resource + end + + def action_create + unless exists?(:logins) + if @new_resource.windows_user + db.execute("CREATE LOGIN [#{@new_resource.username}] FROM WINDOWS").do + else + db.execute("CREATE LOGIN [#{@new_resource.username}] WITH PASSWORD = '#{@new_resource.password}', CHECK_POLICY = OFF").do + end + @new_resource.updated_by_last_action(true) + end + unless exists?(:users) + if @new_resource.database_name + Chef::Log.info("#{@new_resource} creating user in '#{@new_resource.database_name}' database context.") + db.execute("USE [#{@new_resource.database_name}]").do + else + Chef::Log.info("#{@new_resource} database_name not provided, creating user in global context.") + end + db.execute("CREATE USER [#{@new_resource.username}] FOR LOGIN [#{@new_resource.username}]").do + @new_resource.updated_by_last_action(true) + end + ensure + close + end + + def action_drop + if exists?(:users) + db.execute("DROP USER [#{@new_resource.username}]").do + @new_resource.updated_by_last_action(true) + end + if exists?(:logins) + db.execute("DROP LOGIN [#{@new_resource.username}]").do + @new_resource.updated_by_last_action(true) + end + ensure + close + end + + def action_grant + if @new_resource.password || (@new_resource.windows_user && !exists?(:logins)) + action_create + end + Chef::Application.fatal!('Please provide a database_name, SQL Server does not support global GRANT statements.') unless @new_resource.database_name + grant_statement = "GRANT #{@new_resource.privileges.join(', ')} ON DATABASE::[#{@new_resource.database_name}] TO [#{@new_resource.username}]" + Chef::Log.info("#{@new_resource} granting access with statement [#{grant_statement}]") + db.execute("USE [#{@new_resource.database_name}]").do + db.execute(grant_statement).do + @new_resource.updated_by_last_action(true) + ensure + close + end + + def action_alter_roles + if @new_resource.password || (@new_resource.windows_user && !exists?(:logins)) + action_create + end + Chef::Application.fatal!('Please provide a database_name, SQL Server does not support global GRANT statements.') unless @new_resource.database_name + db.execute("USE [#{@new_resource.database_name}]").do + @new_resource.sql_roles.each do | sql_role, role_action | + alter_statement = "ALTER ROLE [#{sql_role}] #{role_action} MEMBER [#{@new_resource.username}]" + Chef::Log.info("#{@new_resource} granting access with statement [#{alter_statement}]") + db.execute(alter_statement).do + end + @new_resource.updated_by_last_action(true) + ensure + close + end + + def action_alter_sys_roles + if @new_resource.password || (@new_resource.windows_user && !exists?(:logins)) + action_create + end + server_version = db.execute("SELECT SERVERPROPERTY('productversion')").each.first.values.first + Chef::Log.info("SQL Server Version: #{server_version.inspect}") + db.execute('USE [master]').do + @new_resource.sql_sys_roles.each do | sql_sys_role, role_action | + case role_action + when 'ADD' + if server_version < '11.00.0000.00' + alter_statement = "EXEC sp_addsrvrolemember '#{@new_resource.username}', '#{sql_sys_role}'" + else + alter_statement = "ALTER SERVER ROLE #{sql_role} #{role_action} MEMBER [#{@new_resource.username}]" + end + Chef::Log.info("#{@new_resource} granting server role membership with statement [#{alter_statement}]") + when 'DROP' + if server_version < '11.00.0000.00' + alter_statement = "EXEC sp_dropsrvrolemember '#{@new_resource.username}', '#{sql_sys_role}'" + else + alter_statement = "ALTER SERVER ROLE #{sql_role} #{role_action} MEMBER [#{@new_resource.username}]" + end + Chef::Log.info("#{@new_resource} revoking server role membership with statement [#{alter_statement}]") + end + db.execute(alter_statement).do + end + @new_resource.updated_by_last_action(true) + ensure + close + end + + private + + def exists?(type = :users) + case type + when :users + table = 'database_principals' + if @new_resource.database_name + Chef::Log.debug("#{@new_resource} searching for existing user in '#{@new_resource.database_name}' database context.") + db.execute("USE [#{@new_resource.database_name}]").do + end + when :logins + table = 'server_principals' + end + + result = db.execute("SELECT name FROM sys.#{table} WHERE name='#{@new_resource.username}'") + result.each.any? + end + end + end + end +end diff --git a/cookbooks/database/libraries/resource_database.rb b/cookbooks/database/libraries/resource_database.rb new file mode 100644 index 0000000..fba2491 --- /dev/null +++ b/cookbooks/database/libraries/resource_database.rb @@ -0,0 +1,118 @@ +# +# Author:: Seth Chisamore () +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# 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. +# + +require 'chef/resource' + +class Chef + class Resource + class Database < Chef::Resource + def initialize(name, run_context = nil) + super + @resource_name = :database + @database_name = name + @allowed_actions.push(:create, :drop, :query) + @action = :create + end + + def database_name(arg = nil) + set_or_return( + :database_name, + arg, + kind_of: String + ) + end + + def connection(arg = nil) + set_or_return( + :connection, + arg, + required: true + ) + end + + def sql(arg = nil, &block) + arg ||= block + set_or_return( + :sql, + arg, + kind_of: [String, Proc] + ) + end + + def sql_query + if sql.is_a?(Proc) + sql.call + else + sql + end + end + + def template(arg = nil) + set_or_return( + :template, + arg, + kind_of: String, + default: 'DEFAULT' + ) + end + + def collation(arg = nil) + set_or_return( + :collation, + arg, + kind_of: String + ) + end + + def encoding(arg = nil) + set_or_return( + :encoding, + arg, + kind_of: String, + default: 'DEFAULT' + ) + end + + def tablespace(arg = nil) + set_or_return( + :tablespace, + arg, + kind_of: String, + default: 'DEFAULT' + ) + end + + def connection_limit(arg = nil) + set_or_return( + :connection_limit, + arg, + kind_of: String, + default: '-1' + ) + end + + def owner(arg = nil) + set_or_return( + :owner, + arg, + kind_of: String + ) + end + end + end +end diff --git a/cookbooks/database/libraries/resource_database_user.rb b/cookbooks/database/libraries/resource_database_user.rb new file mode 100644 index 0000000..1247a0b --- /dev/null +++ b/cookbooks/database/libraries/resource_database_user.rb @@ -0,0 +1,105 @@ +# +# Author:: Seth Chisamore () +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# 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. +# + +require File.join(File.dirname(__FILE__), 'resource_database') + +class Chef + class Resource + class DatabaseUser < Chef::Resource::Database + def initialize(name, run_context = nil) + super + @resource_name = :database_user + @username = name + + @database_name = nil + @table = nil + @host = 'localhost' + @privileges = [:all] + @grant_option = false + @require_ssl = false + + @allowed_actions.push(:create, :drop, :grant, :revoke) + @action = :create + end + + def database_name(arg = nil) + set_or_return( + :database_name, + arg, + kind_of: String + ) + end + + def username(arg = nil) + set_or_return( + :username, + arg, + kind_of: String + ) + end + + def require_ssl(arg = nil) + set_or_return( + :require_ssl, + arg, + kind_of: [TrueClass, FalseClass] + ) + end + + def password(arg = nil) + set_or_return( + :password, + arg, + kind_of: String + ) + end + + def table(arg = nil) + set_or_return( + :table, + arg, + kind_of: String + ) + end + + def host(arg = nil) + set_or_return( + :host, + arg, + kind_of: String + ) + end + + def privileges(arg = nil) + set_or_return( + :privileges, + arg, + kind_of: Array + ) + end + + def grant_option(arg = nil) + set_or_return( + :grant_option, + arg, + kind_of: [TrueClass, FalseClass], default: false + ) + end + end + end +end diff --git a/cookbooks/database/libraries/resource_mysql_database.rb b/cookbooks/database/libraries/resource_mysql_database.rb new file mode 100644 index 0000000..f82550c --- /dev/null +++ b/cookbooks/database/libraries/resource_mysql_database.rb @@ -0,0 +1,32 @@ +# +# Author:: Seth Chisamore () +# Author:: Sean OMeara () +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# 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. +# + +require File.join(File.dirname(__FILE__), 'provider_database_mysql') + +class Chef + class Resource + class MysqlDatabase < Chef::Resource::Database + def initialize(name, run_context = nil) + super + @resource_name = :mysql_database + @provider = Chef::Provider::Database::Mysql + end + end + end +end diff --git a/cookbooks/database/libraries/resource_mysql_database_user.rb b/cookbooks/database/libraries/resource_mysql_database_user.rb new file mode 100644 index 0000000..afe173c --- /dev/null +++ b/cookbooks/database/libraries/resource_mysql_database_user.rb @@ -0,0 +1,32 @@ +# +# Author:: Seth Chisamore () +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# 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. +# + +require File.join(File.dirname(__FILE__), 'resource_database_user') +require File.join(File.dirname(__FILE__), 'provider_database_mysql_user') + +class Chef + class Resource + class MysqlDatabaseUser < Chef::Resource::DatabaseUser + def initialize(name, run_context = nil) + super + @resource_name = :mysql_database_user + @provider = Chef::Provider::Database::MysqlUser + end + end + end +end diff --git a/cookbooks/database/libraries/resource_postgresql_database.rb b/cookbooks/database/libraries/resource_postgresql_database.rb new file mode 100644 index 0000000..975176f --- /dev/null +++ b/cookbooks/database/libraries/resource_postgresql_database.rb @@ -0,0 +1,33 @@ +# +# Author:: Seth Chisamore () +# Author:: Lamont Granquist () +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# 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. +# + +require File.join(File.dirname(__FILE__), 'resource_database') +require File.join(File.dirname(__FILE__), 'provider_database_postgresql') + +class Chef + class Resource + class PostgresqlDatabase < Chef::Resource::Database + def initialize(name, run_context = nil) + super + @resource_name = :postgresql_database + @provider = Chef::Provider::Database::Postgresql + end + end + end +end diff --git a/cookbooks/database/libraries/resource_postgresql_database_schema.rb b/cookbooks/database/libraries/resource_postgresql_database_schema.rb new file mode 100644 index 0000000..82c7c5d --- /dev/null +++ b/cookbooks/database/libraries/resource_postgresql_database_schema.rb @@ -0,0 +1,43 @@ +# +# Author:: Marco Betti () +# Copyright:: Copyright (c) 2013 Chef Software, Inc. +# 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. +# + +require File.join(File.dirname(__FILE__), 'resource_database') +require File.join(File.dirname(__FILE__), 'provider_database_postgresql_schema') + +class Chef + class Resource + class PostgresqlDatabaseSchema < Chef::Resource::Database + def initialize(name, run_context = nil) + super + @resource_name = :postgresql_database_schema + @schema_name = name + @allowed_actions.push(:create, :drop) + @action = :create + @provider = Chef::Provider::Database::PostgresqlSchema + end + + def schema_name(arg = nil) + set_or_return( + :schema_name, + arg, + kind_of: String + ) + end + end + end +end diff --git a/cookbooks/database/libraries/resource_postgresql_database_user.rb b/cookbooks/database/libraries/resource_postgresql_database_user.rb new file mode 100644 index 0000000..f73feb0 --- /dev/null +++ b/cookbooks/database/libraries/resource_postgresql_database_user.rb @@ -0,0 +1,89 @@ +# +# Author:: Seth Chisamore () +# Author:: Lamont Granquist () +# Author:: Marco Betti () +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# 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. +# + +require File.join(File.dirname(__FILE__), 'resource_database_user') +require File.join(File.dirname(__FILE__), 'provider_database_postgresql_user') + +class Chef + class Resource + class PostgresqlDatabaseUser < Chef::Resource::DatabaseUser + def initialize(name, run_context = nil) + super + @resource_name = :postgresql_database_user + @provider = Chef::Provider::Database::PostgresqlUser + @createdb = false + @createrole = false + @login = true + @replication = false + @superuser = false + @schema_name = nil + @allowed_actions.push(:create, :drop, :grant, :grant_schema) + end + + def createdb(arg = nil) + set_or_return( + :createdb, + arg, + equal_to: [true, false] + ) + end + + def createrole(arg = nil) + set_or_return( + :createrole, + arg, + equal_to: [true, false] + ) + end + + def login(arg = nil) + set_or_return( + :login, + arg, + equal_to: [true, false] + ) + end + + def replication(arg = nil) + set_or_return( + :replication, + arg, + equal_to: [true, false] + ) + end + + def schema_name(arg = nil) + set_or_return( + :schema_name, + arg, + kind_of: String + ) + end + + def superuser(arg = nil) + set_or_return( + :superuser, + arg, + equal_to: [true, false] + ) + end + end + end +end diff --git a/cookbooks/database/libraries/resource_sql_server_database.rb b/cookbooks/database/libraries/resource_sql_server_database.rb new file mode 100644 index 0000000..0822710 --- /dev/null +++ b/cookbooks/database/libraries/resource_sql_server_database.rb @@ -0,0 +1,32 @@ +# +# Author:: Seth Chisamore () +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# 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. +# + +require File.join(File.dirname(__FILE__), 'resource_database') +require File.join(File.dirname(__FILE__), 'provider_database_sql_server') + +class Chef + class Resource + class SqlServerDatabase < Chef::Resource::Database + def initialize(name, run_context = nil) + super + @resource_name = :sql_server_database + @provider = Chef::Provider::Database::SqlServer + end + end + end +end diff --git a/cookbooks/database/libraries/resource_sql_server_database_user.rb b/cookbooks/database/libraries/resource_sql_server_database_user.rb new file mode 100644 index 0000000..083cb84 --- /dev/null +++ b/cookbooks/database/libraries/resource_sql_server_database_user.rb @@ -0,0 +1,63 @@ +# +# Author:: Seth Chisamore () +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# 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. +# + +require File.join(File.dirname(__FILE__), 'resource_database_user') +require File.join(File.dirname(__FILE__), 'provider_database_sql_server_user') + +class Chef + class Resource + class SqlServerDatabaseUser < Chef::Resource::DatabaseUser + def initialize(name, run_context = nil) + super + @sql_roles = {} + @sql_sys_roles = {} + @resource_name = :sql_server_database_user + @provider = Chef::Provider::Database::SqlServerUser + @allowed_actions.push(:alter_roles, :alter_sys_roles) + @windows_user = false + end + end + + def windows_user(arg = nil) + set_or_return( + :windows_user, + arg, + kind_of: [TrueClass, FalseClass], + default: false + ) + end + + def sql_roles(arg = nil) + Chef::Log.debug("Received roles: #{arg.inspect}") + set_or_return( + :sql_roles, + arg, + kind_of: Hash + ) + end + + def sql_sys_roles(arg = nil) + Chef::Log.debug("Received Server roles: #{arg.inspect}") + set_or_return( + :sql_sys_roles, + arg, + kind_of: Hash + ) + end + end +end diff --git a/cookbooks/database/metadata.json b/cookbooks/database/metadata.json new file mode 100644 index 0000000..91e954a --- /dev/null +++ b/cookbooks/database/metadata.json @@ -0,0 +1 @@ +{"name":"database","version":"4.0.6","description":"provides LWRPs for common database tasks","long_description":"","maintainer":"Chef Software, Inc.","maintainer_email":"cookbooks@chef.io","license":"Apache 2.0","platforms":{"debian":">= 0.0.0","ubuntu":">= 0.0.0","centos":">= 0.0.0","suse":">= 0.0.0","fedora":">= 0.0.0","redhat":">= 0.0.0","scientific":">= 0.0.0","amazon":">= 0.0.0"},"dependencies":{"postgresql":">= 1.0.0"},"recommendations":{},"suggestions":{},"conflicting":{},"providing":{},"replacing":{},"attributes":{},"groupings":{},"recipes":{}} \ No newline at end of file diff --git a/cookbooks/database/recipes/postgresql.rb b/cookbooks/database/recipes/postgresql.rb new file mode 100644 index 0000000..0c3774e --- /dev/null +++ b/cookbooks/database/recipes/postgresql.rb @@ -0,0 +1,20 @@ +# +# Author:: Jesse Howarth () +# +# Copyright:: Copyright (c) 2012, Chef Software, Inc. () +# 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. +# + +include_recipe 'postgresql::ruby' diff --git a/cookbooks/database/templates/default/app_grants.sql.erb b/cookbooks/database/templates/default/app_grants.sql.erb new file mode 100644 index 0000000..35d8b98 --- /dev/null +++ b/cookbooks/database/templates/default/app_grants.sql.erb @@ -0,0 +1,8 @@ +# Generated by Chef. Local modifications will be overwritten. +<% @db_info.each do |env,db| -%> +# Privileges for databases in <%= env %> +GRANT ALL ON <%= db['database'] %>.* TO '<%= db['username'] %>'@'localhost' IDENTIFIED BY '<%= db['password'] %>'; +GRANT ALL ON <%= db['database'] %>.* TO '<%= db['username'] %>'@'<%= node['fqdn'] %>' IDENTIFIED BY '<%= db['password'] %>'; +GRANT ALL ON <%= db['database'] %>.* TO '<%= db['username'] %>'@'%' IDENTIFIED BY '<%= db['password'] %>'; +<% end -%> +flush privileges; diff --git a/cookbooks/database/templates/default/aws_config.erb b/cookbooks/database/templates/default/aws_config.erb new file mode 100644 index 0000000..e5ae368 --- /dev/null +++ b/cookbooks/database/templates/default/aws_config.erb @@ -0,0 +1,3 @@ +AWS_ACCESS_KEY_ID=<%= @access_key %> +AWS_SECRET_ACCESS_KEY=<%= @secret_key %> +BUCKET_BASE_NAME=db-backups diff --git a/cookbooks/database/templates/default/chef-solo-database-snapshot.cron.erb b/cookbooks/database/templates/default/chef-solo-database-snapshot.cron.erb new file mode 100644 index 0000000..db40768 --- /dev/null +++ b/cookbooks/database/templates/default/chef-solo-database-snapshot.cron.erb @@ -0,0 +1,6 @@ +# Managed by Chef +# m h dom mon dow command +# Keep 1 day of hourly snapshots +PATH=/usr/sbin:/usr/bin:/sbin:/bin +<% cs = "chef-solo -j #{@json_attribs} -c #{@config_file}" %> +<%= @schedule %> root <%= cs %> diff --git a/cookbooks/database/templates/default/chef-solo-database-snapshot.json.erb b/cookbooks/database/templates/default/chef-solo-database-snapshot.json.erb new file mode 100644 index 0000000..13cdb64 --- /dev/null +++ b/cookbooks/database/templates/default/chef-solo-database-snapshot.json.erb @@ -0,0 +1 @@ +<%= require 'json'; JSON.pretty_generate(@output) %> diff --git a/cookbooks/database/templates/default/chef-solo-database-snapshot.rb.erb b/cookbooks/database/templates/default/chef-solo-database-snapshot.rb.erb new file mode 100644 index 0000000..12806cf --- /dev/null +++ b/cookbooks/database/templates/default/chef-solo-database-snapshot.rb.erb @@ -0,0 +1,6 @@ +executable_path ENV['PATH'] ? ENV['PATH'].split(File::PATH_SEPARATOR) : [] +<% if @cookbook_path.is_a? Array %> + cookbook_path [ <%= @cookbook_path.collect { |cb| "\"#{cb}\""}.join(", ") -%> ] +<% else %> + cookbook_path "<%= @cookbook_path -%>" +<% end %> diff --git a/cookbooks/database/templates/default/ebs-backup-cron.erb b/cookbooks/database/templates/default/ebs-backup-cron.erb new file mode 100644 index 0000000..9293fdf --- /dev/null +++ b/cookbooks/database/templates/default/ebs-backup-cron.erb @@ -0,0 +1,2 @@ +# Chef Name: ebs_db_backup +15 0 * * * root /usr/local/bin/db-backup.sh diff --git a/cookbooks/database/templates/default/ebs-db-backup.sh.erb b/cookbooks/database/templates/default/ebs-db-backup.sh.erb new file mode 100644 index 0000000..60e1c91 --- /dev/null +++ b/cookbooks/database/templates/default/ebs-db-backup.sh.erb @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Back up a MySQL database via EBS snapshot + +. /mnt/aws-config/config + +/opt/ec2_mysql/bin/ec2_mysql -a $AWS_ACCESS_KEY_ID -s $AWS_SECRET_ACCESS_KEY -p '<%= @mysql_root_passwd %>' -k 5 master +echo "done" diff --git a/cookbooks/database/templates/default/ebs-db-restore.sh.erb b/cookbooks/database/templates/default/ebs-db-restore.sh.erb new file mode 100644 index 0000000..47afef6 --- /dev/null +++ b/cookbooks/database/templates/default/ebs-db-restore.sh.erb @@ -0,0 +1,10 @@ +#!/bin/bash +# +# Restore a MySQL database from EBS + +mkdir -p /mnt/restore + +. /mnt/aws-config/config + +/opt/ec2_mysql/bin/ec2_mysql -a $AWS_ACCESS_KEY_ID -s $AWS_SECRET_ACCESS_KEY -p '<%= @mysql_root_password %>' -v '<%= @ebs_vol_id %>' -m /mnt/restore -d <%= @mysql_device %> -r <%= @mysql_device %> -l debug -n slave +echo "done" diff --git a/cookbooks/database/templates/default/s3cfg.erb b/cookbooks/database/templates/default/s3cfg.erb new file mode 100644 index 0000000..f193656 --- /dev/null +++ b/cookbooks/database/templates/default/s3cfg.erb @@ -0,0 +1,27 @@ +[default] +access_key = <%= @aws['aws_access_key_id'] %> +acl_public = False +bucket_location = US +debug_syncmatch = False +default_mime_type = binary/octet-stream +delete_removed = False +dry_run = False +encrypt = False +force = False +gpg_command = /usr/bin/gpg +gpg_decrypt = %(gpg_command)s -d --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s +gpg_encrypt = %(gpg_command)s -c --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s +gpg_passphrase = +guess_mime_type = False +host_base = s3.amazonaws.com +host_bucket = %(bucket)s.s3.amazonaws.com +human_readable_sizes = False +preserve_attrs = True +proxy_host = +proxy_port = 0 +recv_chunk = 4096 +secret_key = <%= @aws['aws_secret_access_key'] %> +send_chunk = 4096 +simpledb_host = sdb.amazonaws.com +use_https = True +verbosity = WARNING diff --git a/cookbooks/firewall/CHANGELOG.md b/cookbooks/firewall/CHANGELOG.md new file mode 100644 index 0000000..b74e18e --- /dev/null +++ b/cookbooks/firewall/CHANGELOG.md @@ -0,0 +1,121 @@ +firewall Cookbook CHANGELOG +======================= +This file is used to list changes made in each version of the firewall cookbook. + +v1.2.0 (2015-05-28) +------------------- +* #64 - Support the newer version of poise + +v1.1.2 (2015-05-19) +------------------- +* #60 - Always add /32 or /128 to ipv4 or ipv6 addresses, respectively. + - Make comment quoting optional; iptables on Ubuntu strips quotes on strings without any spaces + +v1.1.1 (2015-05-11) +------------------- +* #57 - Suppress warning: already initialized constant XXX while Chefspec + +v1.1.0 (2015-04-27) +------------------- +* #56 - Better ipv6 support for firewalld and iptables +* #54 - Document raw parameter + +v1.0.2 (2015-04-03) +------------------- +* #52 - Typo in :masquerade action name + +v1.0.1 (2015-03-28) +------------------- +* #49 - Fix position attribute of firewall_rule providers to be correctly used as a string in commands + +v1.0.0 (2015-03-25) +------------------- +* Major upgrade and rewrite as HWRP using poise +* Adds support for iptables and firewalld +* Modernize tests and other files +* Fix many bugs from ufw defaults to multiport suppot + +v0.11.8 (2014-05-20) +-------------------- +* Corrects issue where on a secondary converge would not distinguish between inbound and outbound rules + + +v0.11.6 (2014-02-28) +-------------------- +[COOK-4385] - UFW provider is broken + + +v0.11.4 (2014-02-25) +-------------------- +[COOK-4140] Only notify when a rule is actually added + + +v0.11.2 +------- +### Bug +- **[COOK-3615](https://tickets.opscode.com/browse/COOK-3615)** - Install required UFW package on Debian + +v0.11.0 +------- +### Improvement +- [COOK-2932]: ufw providers work on debian but cannot be used + +v0.10.2 +------- +- [COOK-2250] - improve readme + +v0.10.0 +------ +- [COOK-1234] - allow multiple ports per rule + +v0.9.2 +------ +- [COOK-1615] - Firewall example docs have incorrect direction syntax + +v0.9.0 +------ +The default action for firewall LWRP is now :enable, the default action for firewall_rule LWRP is now :reject. This is in line with a "default deny" policy. + +- [COOK-1429] - resolve foodcritic warnings + +v0.8.0 +------ +- refactor all resources and providers into LWRPs +- removed :reset action from firewall resource (couldn't find a good way to make it idempotent) +- removed :logging action from firewall resource...just set desired level via the log_level attribute + +v0.6.0 +------ +- [COOK-725] Firewall cookbook firewall_rule LWRP needs to support logging attribute. +- Firewall cookbook firewall LWRP needs to support :logging + +v0.5.7 +------ +- [COOK-696] Firewall cookbook firewall_rule LWRP needs to support interface +- [COOK-697] Firewall cookbook firewall_rule LWRP needs to support the direction for the rules + +v0.5.6 +------ +- [COOK-695] Firewall cookbook firewall_rule LWRP needs to support destination port + +v0.5.5 +------ +- [COOK-709] fixed :nothing action for the 'firewall_rule' resource. + +v0.5.4 +------ +- [COOK-694] added :reject action to the 'firewall_rule' resource. + +v0.5.3 +------ +- [COOK-698] added :reset action to the 'firewall' resource. + +v0.5.2 +------ +- Add missing 'requires' statements. fixes 'NameError: uninitialized constant' error. +thanks to Ernad Husremović for the fix. + +v0.5.0 +------ +- [COOK-686] create firewall and firewall_rule resources +- [COOK-687] create UFW providers for all resources diff --git a/cookbooks/firewall/README.md b/cookbooks/firewall/README.md new file mode 100644 index 0000000..a3b55ab --- /dev/null +++ b/cookbooks/firewall/README.md @@ -0,0 +1,189 @@ +firewall Cookbook +================= +[![Build Status](https://secure.travis-ci.org/opscode-cookbooks/firewall.png?branch=master)](http://travis-ci.org/opscode-cookbooks/firewall) + +Provides a set of primitives for managing firewalls and associated rules. + +PLEASE NOTE - The resource/providers in this cookbook are under heavy development. An attempt is being made to keep the resource simple/stupid by starting with less sophisticated firewall implementations first and refactor/vet the resource definition with each successive provider. + + +Requirements +------------ +### Platform +* Ubuntu +* Debian +* Redhat +* CentOS + +Tested on: +* Ubuntu 12.04 +* Ubuntu 14.04 +* Debian 7.8 +* CentOS 6.5 +* CentOS 7.0 + + +Recipes +------- +### default +The default recipe creates a firewall resource with action install, and if `node['firewall']['allow_ssh']`, opens port 22 from the world. + + +Attributes +---------- + +* `default['firewall']['ufw']['defaults']` hash for template `/etc/default/ufw` + +Resources/Providers +------------------- +- See `librariez/z_provider_mapping.rb` for a full list of providers for each platform and version. + +### firewall +#### Actions +- `:enable`: *Default action* enable the firewall. this will make any rules that have been defined 'active'. +- `:disable`: disable the firewall. drop any rules and put the node in an unprotected state. +- `:flush`: Runs `iptables -F`. Only supported by the iptables firewall provider. +- `:save`: Runs `service iptables save` under iptables, adds rules permanently under firewall. Not supported in ufw. + +#### Attribute Parameters +- name: name attribute. arbitrary name to uniquely identify this resource +- log_level: level of verbosity the firewall should log at. valid values are: :low, :medium, :high, :full. default is :low. + +#### Examples + +```ruby +# enable platform default firewall +firewall 'ufw' do + action :enable +end + +# increase logging past default of 'low' +firewall 'debug firewalls' do + log_level :high + action :enable +end +``` + +### firewall_rule + +#### Actions +- `:allow`: the rule should allow incoming traffic. +- `:deny`: the rule should deny incoming traffic. +- `:reject`: *Default action: the rule should reject incoming traffic. +- `:masqerade`: Add masqerade rule +- `:redirect`: Add redirect-type rule +- `:log`: Configure logging +- `:remove`: Remove all rules + +#### Attribute Parameters +- name: name attribute. arbitrary name to uniquely identify this firewall rule +- protocol: valid values are: :udp, :tcp. default is all protocols +- port: incoming port number (ie. 22 to allow inbound SSH), or an array of incoming port numbers (ie. [80,443] to allow inbound HTTP & HTTPS). NOTE: `protocol` attribute is required with multiple ports, or a range of incoming port numbers (ie. 60000..61000 to allow inbound mobile-shell. NOTE: `protocol`, or an attribute is required with a range of ports. +- source: ip address or subnet to filter on incoming traffic. default is `0.0.0.0/0` (ie Anywhere) +- destination: ip address or subnet to filter on outgoing traffic. +- dest_port: outgoing port number. +- position: position to insert rule at. if not provided rule is inserted at the end of the rule list. +- direction: direction of the rule. valid values are: :in, :out, default is :in +- interface: interface to apply rule (ie. 'eth0'). +- logging: may be added to enable logging for a particular rule. valid values are: :connections, :packets. In the ufw provider, :connections logs new connections while :packets logs all packets. +- raw: for passing a raw command to the provider (for use with custom modules, also used by zap provider to clean up non-chef managed rules) + +#### Examples + +```ruby +# open standard ssh port, enable firewall +firewall_rule 'ssh' do + port 22 + action :allow + notifies :enable, 'firewall[ufw]' +end + +# open standard http port to tcp traffic only; insert as first rule +firewall_rule 'http' do + port 80 + protocol :tcp + position 1 + action :allow +end + +# restrict port 13579 to 10.0.111.0/24 on eth0 +firewall_rule 'myapplication' do + port 13579 + source '10.0.111.0/24' + direction :in + interface 'eth0' + action :allow +end + +# open UDP ports 60000..61000 for mobile shell (mosh.mit.edu), note +# that the protocol attribute is required when using port_range +firewall_rule 'mosh' do + protocol :udp + port 60000..61000 + action :allow +end + +# open multiple ports for http/https, note that the protocol +# attribute is required when using ports +firewall_rule 'http/https' do + protocol :tcp + port [80, 443] + action :allow +end + +firewall 'ufw' do + action :nothing +end +``` + + +Development +----------- +This section details "quick development" steps. For a detailed explanation, see [[Contributing.md]]. + +1. Clone this repository from GitHub: + + $ git clone git@github.com:opscode-cookbooks/firewall.git + +2. Create a git branch + + $ git checkout -b my_bug_fix + +3. Install dependencies: + + $ bundle install + +4. Make your changes/patches/fixes, committing appropiately +5. **Write tests** +6. Run the tests: + - `bundle exec foodcritic -f any .` + - `bundle exec rspec` + - `bundle exec rubocop` + - `bundle exec kitchen test` + + In detail: + - Foodcritic will catch any Chef-specific style errors + - RSpec will run the unit tests + - Rubocop will check for Ruby-specific style errors + - Test Kitchen will run and converge the recipes + + +License & Authors +----------------- +- Author:: Seth Chisamore () + +```text +Copyright:: Copyright (c) 2011-2015 Opscode, Inc. + +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/cookbooks/firewall/attributes/default.rb b/cookbooks/firewall/attributes/default.rb new file mode 100644 index 0000000..0047bdf --- /dev/null +++ b/cookbooks/firewall/attributes/default.rb @@ -0,0 +1 @@ +default['firewall']['allow_ssh'] = false diff --git a/cookbooks/firewall/attributes/ufw.rb b/cookbooks/firewall/attributes/ufw.rb new file mode 100644 index 0000000..de9bc2f --- /dev/null +++ b/cookbooks/firewall/attributes/ufw.rb @@ -0,0 +1,12 @@ +default['firewall']['ufw']['defaults'] = { + :ipv6 => 'yes', + :manage_builtins => 'no', + :ipt_sysctl => '/etc/ufw/sysctl.conf', + :ipt_modules => 'nf_conntrack_ftp nf_nat_ftp nf_conntrack_netbios_ns', + :policy => { + :input => 'DROP', + :output => 'ACCEPT', + :forward => 'DROP', + :application => 'SKIP' + } +} diff --git a/cookbooks/firewall/libraries/helpers.rb b/cookbooks/firewall/libraries/helpers.rb new file mode 100644 index 0000000..3e28585 --- /dev/null +++ b/cookbooks/firewall/libraries/helpers.rb @@ -0,0 +1,13 @@ +module FirewallCookbook + module Helpers + def port_to_s(p) + if p && p.is_a?(Integer) + p.to_s + elsif p && p.is_a?(Array) + p.join(',') + elsif p && p.is_a?(Range) + "#{p.first}:#{p.last} " + end + end + end +end diff --git a/cookbooks/firewall/libraries/matchers.rb b/cookbooks/firewall/libraries/matchers.rb new file mode 100644 index 0000000..bca47cb --- /dev/null +++ b/cookbooks/firewall/libraries/matchers.rb @@ -0,0 +1,32 @@ +if defined?(ChefSpec) + ChefSpec.define_matcher(:firewall) + ChefSpec.define_matcher(:firewall_rule) + + def enable_firewall(resource) + ChefSpec::Matchers::ResourceMatcher.new(:firewall, :enable, resource) + end + + def disable_firewall(resource) + ChefSpec::Matchers::ResourceMatcher.new(:firewall, :disable, resource) + end + + def allow_firewall_rule(resource) + ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :allow, resource) + end + + def deny_firewall_rule(resource) + ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :deny, resource) + end + + def reject_firewall_rule(resource) + ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :reject, resource) + end + + def log_firewall_rule(resource) + ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :log, resource) + end + + def remove_firewall_rule(resource) + ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :remove, resource) + end +end diff --git a/cookbooks/firewall/libraries/provider_firewall_firewalld.rb b/cookbooks/firewall/libraries/provider_firewall_firewalld.rb new file mode 100644 index 0000000..7a3c022 --- /dev/null +++ b/cookbooks/firewall/libraries/provider_firewall_firewalld.rb @@ -0,0 +1,92 @@ +# +# Author:: Ronald Doorn () +# Cookbook Name:: firewall +# Resource:: default +# +# 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. +# +require 'poise' + +class Chef + class Provider::FirewallFirewalld < Provider + include Poise + include Chef::Mixin::ShellOut + + def action_enable + # prints all the firewall rules + # pp @new_resource.subresources + log_current_firewalld + if active? + Chef::Log.debug("#{@new_resource} already enabled.") + else + Chef::Log.debug("#{@new_resource} is about to be enabled") + shell_out!('service', 'firewalld', 'start') + shell_out!('firewall-cmd', '--set-default-zone=drop') + Chef::Log.info("#{@new_resource} enabled.") + new_resource.updated_by_last_action(true) + end + end + + def action_disable + if active? + shell_out!('firewall-cmd', '--set-default-zone=public') + shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'INPUT') + shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'OUTPUT') + Chef::Log.info("#{@new_resource} disabled") + new_resource.updated_by_last_action(true) + else + Chef::Log.debug("#{@new_resource} already disabled.") + end + end + + def action_flush + shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'INPUT') + shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'OUTPUT') + shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'INPUT') + shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'OUTPUT') + Chef::Log.info("#{@new_resource} flushed.") + end + + def action_save + if shell_out!('firewall-cmd', '--direct', '--get-all-rules').stdout != shell_out!('firewall-cmd', '--direct', '--permanent', '--get-all-rules').stdout + shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'INPUT') + shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'OUTPUT') + shell_out!('firewall-cmd', '--direct', '--get-all-rules').stdout.lines do |line| + shell_out!("firewall-cmd --direct --permanent --add-rule #{line}") + end + Chef::Log.info("#{@new_resource} saved.") + new_resource.updated_by_last_action(true) + else + Chef::Log.info("#{@new_resource} already up-to-date.") + end + end + + private + + def active? + @active ||= begin + cmd = shell_out('firewall-cmd', '--state') + cmd.stdout =~ /^running$/ + end + end + + def log_current_firewalld + cmdstr = 'firewall-cmd --direct --get-all-rules' + Chef::Log.info("#{@new_resource} log_current_firewalld (#{cmdstr}):") + cmd = shell_out!(cmdstr) + Chef::Log.info(cmd.inspect) + rescue + Chef::Log.info("#{@new_resource} log_current_firewalld failed!") + end + end +end diff --git a/cookbooks/firewall/libraries/provider_firewall_iptables.rb b/cookbooks/firewall/libraries/provider_firewall_iptables.rb new file mode 100644 index 0000000..1fbe910 --- /dev/null +++ b/cookbooks/firewall/libraries/provider_firewall_iptables.rb @@ -0,0 +1,110 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: firewall +# Resource:: default +# +# Copyright:: 2011, Opscode, Inc. +# +# 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. +# +require 'poise' + +class Chef + class Provider::FirewallIptables < Provider + include Poise + include Chef::Mixin::ShellOut + + def action_enable + converge_by('install package iptables and default DROP if no rules exist') do + package 'iptables' do + action :install + end + + # prints all the firewall rules + # pp new_resource.subresources + log_current_iptables + if active? + Chef::Log.info("#{new_resource} already enabled.") + else + Chef::Log.debug("#{new_resource} is about to be enabled") + shell_out!('iptables -P INPUT DROP') + shell_out!('iptables -P OUTPUT DROP') + shell_out!('iptables -P FORWARD DROP') + + shell_out!('ip6tables -P INPUT DROP') + shell_out!('ip6tables -P OUTPUT DROP') + shell_out!('ip6tables -P FORWARD DROP') + Chef::Log.info("#{new_resource} enabled.") + new_resource.updated_by_last_action(true) + end + end + end + + def action_disable + if active? + shell_out!('iptables -P INPUT ACCEPT') + shell_out!('iptables -P OUTPUT ACCEPT') + shell_out!('iptables -P FORWARD ACCEPT') + shell_out!('iptables -F') + + shell_out!('ip6tables -P INPUT ACCEPT') + shell_out!('ip6tables -P OUTPUT ACCEPT') + shell_out!('ip6tables -P FORWARD ACCEPT') + shell_out!('ip6tables -F') + Chef::Log.info("#{new_resource} disabled") + new_resource.updated_by_last_action(true) + else + Chef::Log.debug("#{new_resource} already disabled.") + end + end + + def action_flush + shell_out!('iptables -F') + shell_out!('ip6tables -F') + Chef::Log.info("#{new_resource} flushed.") + end + + def action_save + shell_out!('service iptables save') + shell_out!('service ip6tables save') + Chef::Log.info("#{new_resource} saved.") + end + + private + + def active? + @active ||= begin + cmd = shell_out!('iptables-save') + cmd.stdout =~ /INPUT ACCEPT/ + end + @active_v6 ||= begin + cmd = shell_out!('ip6tables-save') + cmd.stdout =~ /INPUT ACCEPT/ + end + @active && @active_v6 + end + + def log_current_iptables + cmdstr = 'iptables -L' + Chef::Log.info("#{new_resource} log_current_iptables (#{cmdstr}):") + cmd = shell_out!(cmdstr) + Chef::Log.info(cmd.inspect) + cmdstr = 'ip6tables -L' + Chef::Log.info("#{new_resource} log_current_iptables (#{cmdstr}):") + cmd = shell_out!(cmdstr) + Chef::Log.info(cmd.inspect) + rescue + Chef::Log.info("#{new_resource} log_current_iptables failed!") + end + end +end diff --git a/cookbooks/firewall/libraries/provider_firewall_rule_firewalld.rb b/cookbooks/firewall/libraries/provider_firewall_rule_firewalld.rb new file mode 100644 index 0000000..efd6592 --- /dev/null +++ b/cookbooks/firewall/libraries/provider_firewall_rule_firewalld.rb @@ -0,0 +1,213 @@ +# +# Author:: Ronald Doorn () +# Cookbook Name:: firewall +# Provider:: rule_iptables +# +# 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. +# +require 'poise' + +class Chef + class Provider::FirewallRuleFirewalld < Provider + include Poise + include Chef::Mixin::ShellOut + include FirewallCookbook::Helpers + + def action_allow + apply_rule(:allow) + end + + def action_deny + apply_rule(:deny) + end + + def action_reject + apply_rule(:reject) + end + + def action_redirect + apply_rule(:redirect) + end + + def action_masquerade + apply_rule(:masquerade) + end + + def action_log + apply_rule(:log) + end + + def action_remove + # TODO: specify which target to delete + # for now this will remove raw + all targeted lines + remove_rule(:allow) + remove_rule(:deny) + remove_rule(:reject) + remove_rule(:redirect) + remove_rule(:masquerade) + end + + private + + CHAIN = { :in => 'INPUT', :out => 'OUTPUT', :pre => 'PREROUTING', :post => 'POSTROUTING' } unless defined? CHAIN # , nil => "FORWARD"} + TARGET = { :allow => 'ACCEPT', :reject => 'REJECT', :deny => 'DROP', :masquerade => 'MASQUERADE', :redirect => 'REDIRECT', :log => 'LOG --log-prefix \'iptables: \' --log-level 7' } unless defined? TARGET + + def apply_rule(type = nil) + ip_versions.each do |ip_version| + firewall_command = 'firewall-cmd --direct --add-rule ' + + # TODO: implement logging for :connections :packets + firewall_rule = build_firewall_rule(type, ip_version) + + Chef::Log.debug("#{new_resource}: #{firewall_rule}") + if rule_exists?(firewall_rule) + Chef::Log.info("#{new_resource} #{type} rule exists... won't apply") + else + cmdstr = firewall_command + firewall_rule + converge_by("firewall_rule[#{new_resource.name}] #{firewall_rule}") do + notifying_block do + shell_out!(cmdstr) # shell_out! is already logged + new_resource.updated_by_last_action(true) + end + end + end + end + end + + def remove_rule(type = nil) + ip_versions.each do |_ip_version| + firewall_command = 'firewall-cmd --direct --remove-rule ' + + # TODO: implement logging for :connections :packets + firewall_rule = build_firewall_rule(type) + + Chef::Log.debug("#{new_resource}: #{firewall_rule}") + if rule_exists?(firewall_rule) + cmdstr = firewall_command + firewall_rule + converge_by("firewall_rule[#{new_resource.name}] #{firewall_rule}") do + notifying_block do + shell_out!(cmdstr) # shell_out! is already logged + new_resource.updated_by_last_action(true) + end + end + else + Chef::Log.info("#{new_resource} #{type} rule does not exists... won't remove") + end + end + end + + def ipv4_rule? + if (new_resource.source && IPAddr.new(new_resource.source).ipv4?) || + (new_resource.destination && IPAddr.new(new_resource.destination).ipv4?) + true + else + false + end + end + + def ipv6_rule? + if (new_resource.source && IPAddr.new(new_resource.source).ipv6?) || + (new_resource.destination && IPAddr.new(new_resource.destination).ipv6?) + true + else + false + end + end + + def ip_versions + if ipv4_rule? + versions = ['ipv4'] + elsif ipv6_rule? + versions = ['ipv6'] + else # no source or destination address, add rules for both ipv4 and ipv6 + versions = %w(ipv4 ipv6) + end + versions + end + + def build_firewall_rule(type = nil, ip_version = 'ipv4') + if new_resource.raw + firewall_rule = new_resource.raw.strip + else + firewall_rule = "#{ip_version} filter " + if new_resource.direction + firewall_rule << "#{CHAIN[new_resource.direction.to_sym]} " + else + firewall_rule << 'FORWARD ' + end + firewall_rule << "#{new_resource.position ? new_resource.position : 1} " + + if [:pre, :post].include?(new_resource.direction) + firewall_rule << '-t nat ' + end + + # Firewalld order of prameters is important here see example output below: + # ipv4 filter INPUT 1 -s 1.2.3.4/32 -d 5.6.7.8/32 -i lo -p tcp -m tcp -m state --state NEW -m comment --comment "hello" -j DROP + firewall_rule << "-s #{ip_with_mask(new_resource.source)} " if new_resource.source && new_resource.source != '0.0.0.0/0' + firewall_rule << "-d #{new_resource.destination} " if new_resource.destination + + firewall_rule << "-i #{new_resource.interface} " if new_resource.interface + firewall_rule << "-o #{new_resource.dest_interface} " if new_resource.dest_interface + + firewall_rule << "-p #{new_resource.protocol} " if new_resource.protocol + firewall_rule << '-m tcp ' if new_resource.protocol.to_sym == :tcp + + # using multiport here allows us to simplify our greps and rule building + firewall_rule << "-m multiport --sports #{port_to_s(new_resource.source_port)} " if new_resource.source_port + firewall_rule << "-m multiport --dports #{port_to_s(dport_calc)} " if dport_calc + + firewall_rule << "-m state --state #{new_resource.stateful.is_a?(Array) ? new_resource.stateful.join(',').upcase : new_resource.stateful.upcase} " if new_resource.stateful + firewall_rule << "-m comment --comment '#{new_resource.description}' " + firewall_rule << "-j #{TARGET[type]} " + firewall_rule << "--to-ports #{new_resource.redirect_port} " if type == 'redirect' + firewall_rule.strip! + end + firewall_rule + end + + def rule_exists?(rule) + fail 'no rule supplied' unless rule + + # match quotes generously + detect_rule = rule.gsub(/'/, "'*") + detect_rule = detect_rule.gsub(/"/, '"*') + + match = shell_out!('firewall-cmd --direct --get-all-rules').stdout.lines.find do |line| + # Chef::Log.debug("matching: [#{detect_rule}] to [#{line.chomp.rstrip}]") + line =~ /#{detect_rule}/ + end + + match + rescue Mixlib::ShellOut::ShellCommandFailed + Chef::Log.debug("#{new_resource} check fails with: " + match.inspect) + Chef::Log.debug("#{new_resource} assuming #{rule} rule does not exist") + false + end + + def dport_calc + new_resource.dest_port || new_resource.port + end + + def ip_with_mask(ip) + if ip.include?('/') + ip + elsif ipv4_rule? + "#{ip}/32" + elsif ipv6_rule? + "#{ip}/128" + else + ip + end + end + end +end diff --git a/cookbooks/firewall/libraries/provider_firewall_rule_iptables.rb b/cookbooks/firewall/libraries/provider_firewall_rule_iptables.rb new file mode 100644 index 0000000..18fbcef --- /dev/null +++ b/cookbooks/firewall/libraries/provider_firewall_rule_iptables.rb @@ -0,0 +1,231 @@ +# +# Cookbook Name:: firewall +# Provider:: rule_iptables +# +# Copyright 2012, computerlyrik +# +# 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. +# +require 'poise' + +class Chef + class Provider::FirewallRuleIptables < Provider + include Poise + include Chef::Mixin::ShellOut + include FirewallCookbook::Helpers + + def action_allow + apply_rule(:allow) + end + + def action_deny + apply_rule(:deny) + end + + def action_reject + apply_rule(:reject) + end + + def action_redirect + apply_rule(:redirect) + end + + def action_masquerade + apply_rule(:masquerade) + end + + def action_log + apply_rule(:log) + end + + def action_remove + # TODO: specify which target to delete + # for now this will remove raw + all targeted lines + remove_rule(:allow) + remove_rule(:deny) + remove_rule(:reject) + remove_rule(:redirect) + remove_rule(:masquerade) + end + + private + + CHAIN = { :in => 'INPUT', :out => 'OUTPUT', :pre => 'PREROUTING', :post => 'POSTROUTING' } unless defined? CHAIN # , nil => "FORWARD"} + TARGET = { :allow => 'ACCEPT', :reject => 'REJECT', :deny => 'DROP', :masquerade => 'MASQUERADE', :redirect => 'REDIRECT', :log => 'LOG --log-prefix "iptables: " --log-level 7' } unless defined? TARGET + + def apply_rule(type = nil) + firewall_commands = determine_iptables_commands + firewall_commands.each do |firewall_command| + ipv6 = (firewall_command == 'ip6tables') ? true : false + if new_resource.position + firewall_command << ' -I ' + else + firewall_command << ' -A ' + end + + # TODO: implement logging for :connections :packets + firewall_rule = build_firewall_rule(type) + + Chef::Log.debug("#{new_resource}: #{firewall_rule}") + if rule_exists?(firewall_rule, ipv6) + Chef::Log.info("#{new_resource} #{type} rule exists... won't apply") + else + cmdstr = firewall_command + firewall_rule + converge_by("firewall_rule[#{new_resource.name}] #{firewall_rule}") do + notifying_block do + shell_out!(cmdstr) # shell_out! is already logged + new_resource.updated_by_last_action(true) + end + end + end + end + end + + def remove_rule(type = nil) + firewall_commands = determine_iptables_commands + firewall_commands.each do |firewall_command| + ipv6 = (firewall_command == 'ip6tables') ? true : false + + # TODO: implement logging for :connections :packets + firewall_rule = build_firewall_rule(type) + + Chef::Log.debug("#{new_resource}: #{firewall_rule}") + if rule_exists?(firewall_rule, ipv6) + cmdstr = firewall_command + firewall_rule + converge_by("firewall_rule[#{new_resource.name}] #{firewall_rule}") do + notifying_block do + shell_out!(cmdstr) # shell_out! is already logged + new_resource.updated_by_last_action(true) + end + end + else + Chef::Log.info("#{new_resource} #{type} rule does not exist... won't remove") + end + end + end + + def ipv4_rule? + if (new_resource.source && IPAddr.new(new_resource.source).ipv4?) || + (new_resource.destination && IPAddr.new(new_resource.destination).ipv4?) + true + else + false + end + end + + def ipv6_rule? + if (new_resource.source && IPAddr.new(new_resource.source).ipv6?) || + (new_resource.destination && IPAddr.new(new_resource.destination).ipv6?) + true + else + false + end + end + + def determine_iptables_commands + if ipv4_rule? + commands = ['iptables'] + elsif ipv6_rule? + commands = ['ip6tables'] + else # no source or destination address, add rules for both ipv4 and ipv6 + commands = %w(iptables ip6tables) + end + commands + end + + def build_firewall_rule(type = nil) + if new_resource.raw + firewall_rule = new_resource.raw.strip + else + firewall_rule = '' + if new_resource.direction + firewall_rule << "#{CHAIN[new_resource.direction.to_sym]} " + else + firewall_rule << 'FORWARD ' + end + firewall_rule << "#{new_resource.position} " if new_resource.position + + if [:pre, :post].include?(new_resource.direction) + firewall_rule << '-t nat ' + end + + # Iptables order of prameters is important here see example output below: + # -A INPUT -s 1.2.3.4/32 -d 5.6.7.8/32 -i lo -p tcp -m tcp -m state --state NEW -m comment --comment "hello" -j DROP + firewall_rule << "-s #{ip_with_mask(new_resource.source)} " if new_resource.source && new_resource.source != '0.0.0.0/0' + firewall_rule << "-d #{new_resource.destination} " if new_resource.destination + + firewall_rule << "-i #{new_resource.interface} " if new_resource.interface + firewall_rule << "-o #{new_resource.dest_interface} " if new_resource.dest_interface + + firewall_rule << "-p #{new_resource.protocol} " if new_resource.protocol + firewall_rule << '-m tcp ' if new_resource.protocol.to_sym == :tcp + + # using multiport here allows us to simplify our greps and rule building + firewall_rule << "-m multiport --sports #{port_to_s(new_resource.source_port)} " if new_resource.source_port + firewall_rule << "-m multiport --dports #{port_to_s(dport_calc)} " if dport_calc + + firewall_rule << "-m state --state #{new_resource.stateful.is_a?(Array) ? new_resource.stateful.join(',').upcase : new_resource.stateful.upcase} " if new_resource.stateful + firewall_rule << "-m comment --comment \"#{new_resource.description}\" " + firewall_rule << "-j #{TARGET[type]} " + firewall_rule << "--to-ports #{new_resource.redirect_port} " if type == 'redirect' + firewall_rule.strip! + end + firewall_rule + end + + def rule_exists?(rule, ipv6 = false) + fail 'no rule supplied' unless rule + if new_resource.position + detect_rule = rule.gsub(/#{CHAIN[new_resource.direction]}\s(\d+)/, '\1' + " -A #{CHAIN[new_resource.direction]}") + else + detect_rule = rule + end + + # match quotes generously + detect_rule = detect_rule.gsub(/'/, "'*") + detect_rule = detect_rule.gsub(/"/, '"*') + + line_number = 0 + match = shell_out!(ipv6 ? 'ip6tables-save' : 'iptables-save').stdout.lines.find do |line| + next if line !~ /#{CHAIN[new_resource.direction]}/ + next if line[0] != '-' + line_number += 1 + line = "#{line_number} #{line}" if new_resource.position + # Chef::Log.debug("matching: [#{detect_rule}] to [#{line.chomp.rstrip}]") + line =~ /#{detect_rule}/ + end + + match + rescue Mixlib::ShellOut::ShellCommandFailed + Chef::Log.debug("#{new_resource} check fails with: " + match.inspect) + Chef::Log.debug("#{new_resource} assuming #{rule} rule does not exist") + false + end + + def dport_calc + new_resource.dest_port || new_resource.port + end + + def ip_with_mask(ip) + if ip.include?('/') + ip + elsif ipv4_rule? + "#{ip}/32" + elsif ipv6_rule? + "#{ip}/128" + else + ip + end + end + end +end diff --git a/cookbooks/firewall/libraries/provider_firewall_rule_ufw.rb b/cookbooks/firewall/libraries/provider_firewall_rule_ufw.rb new file mode 100644 index 0000000..e7f84e2 --- /dev/null +++ b/cookbooks/firewall/libraries/provider_firewall_rule_ufw.rb @@ -0,0 +1,241 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: firwall +# Resource:: rule +# +# Copyright:: 2011, Opscode, Inc. +# +# 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. +# +require 'poise' + +class Chef + class Provider::FirewallRuleUfw < Provider + include Poise + include Chef::Mixin::ShellOut + include FirewallCookbook::Helpers + + def action_allow + if rule_exists? + Chef::Log.info("#{new_resource.name} already allowed, skipping") + else + apply_rule(:allow) + end + end + + def action_deny + if rule_exists? + Chef::Log.info("#{new_resource.name} already denied, skipping") + else + apply_rule(:deny) + end + end + + def action_reject + if rule_exists? + Chef::Log.info("#{new_resource.name} already rejected, skipping") + else + apply_rule(:reject) + end + end + + private + + def apply_rule(type = nil) # rubocop:disable MethodLength + Chef::Log.info("#{new_resource.name} apply_rule #{type}") + # if we don't do this, we may see some bugs where traffic is opened on all ports to all hosts when only RELATED,ESTABLISHED was intended + if new_resource.stateful + msg = '' + msg << "firewall_rule[#{new_resource.name}] was asked to " + msg << "#{type} a stateful rule using #{new_resource.stateful} " + msg << 'but ufw does not support this kind of rule. Consider guarding by platform_family.' + fail msg + end + + # some examples: + # ufw allow from 192.168.0.4 to any port 22 + # ufw deny proto tcp from 10.0.0.0/8 to 192.168.0.1 port 25 + # ufw insert 1 allow proto tcp from 0.0.0.0/0 to 192.168.0.1 port 25 + + ufw_command = ['ufw'] + if new_resource.position + ufw_command << 'insert' + ufw_command << new_resource.position.to_s + end + ufw_command << type.to_s + ufw_command << rule.split + + converge_by("firewall_rule[#{new_resource.name}] #{rule}") do + notifying_block do + # fail 'should be no actions' + shell_out!(*ufw_command.flatten) + shell_out!('ufw', 'status', 'verbose') # purely for the Chef::Log.debug output + new_resource.updated_by_last_action(true) + end + end + end + + def rule + rule = '' + rule << rule_interface + rule << rule_logging + rule << rule_proto + rule << rule_dest_port + rule << rule_source_port + rule.strip + end + + def rule_interface + rule = '' + rule << "#{new_resource.direction} " if new_resource.direction + if new_resource.interface + if new_resource.direction + rule << "on #{new_resource.interface} " + else + rule << "in on #{new_resource.interface} " + end + end + rule + end + + def rule_proto + rule = '' + rule << "proto #{new_resource.protocol} " if new_resource.protocol + rule + end + + def rule_dest_port + rule = '' + if new_resource.destination + rule << "to #{new_resource.destination} " + else + rule << 'to any ' + end + rule << "port #{port_to_s(dport_calc)} " if dport_calc + rule + end + + def rule_source_port + rule = '' + + if new_resource.source + rule << "from #{new_resource.source} " + else + rule << 'from any ' + end + + if new_resource.source_port + rule << "port #{port_to_s(new_resource.source_port)} " + end + rule + end + + def rule_logging + case new_resource.logging && new_resource.logging.to_sym + when :connections + 'log ' + when :packets + 'log-all ' + else + '' + end + end + + # TODO: currently only works when firewall is enabled + def rule_exists? + Chef::Log.info("#{new_resource.name} rule_exists?") + # To Action From + # -- ------ ---- + # 22 ALLOW Anywhere + # 192.168.0.1 25/tcp DENY 10.0.0.0/8 + # 22 ALLOW Anywhere + # 3309 on eth9 ALLOW Anywhere + # Anywhere ALLOW Anywhere + # 80 ALLOW Anywhere (log) + # 8080 DENY 192.168.1.0/24 + # 1.2.3.5 5469/udp ALLOW 1.2.3.4 5469/udp + # 3308 ALLOW OUT Anywhere on eth8 + + to = rule_exists_to? # col 1 + action = rule_exists_action? # col 2 + from = rule_exists_from? # col 3 + + # full regex from columns + regex = rule_exists_regex?(to, action, from) + + match = shell_out!('ufw', 'status').stdout.lines.find do |line| + # TODO: support IPv6 + return false if line =~ /\(v6\)$/ + line =~ regex + end + + match + end + + def rule_exists_to? + to = '' + to << rule_exists_dest? + + proto = rule_exists_proto? + to << proto if proto + + if to.empty? + to << "Anywhere\s" + else + to + end + end + + def rule_exists_action? + action = new_resource.action + action = action.first if action.is_a?(Enumerable) + "#{Regexp.escape(action.to_s.upcase)}\s" + end + + def rule_exists_from? + if new_resource.source && new_resource.source != '0.0.0.0/0' + Regexp.escape(new_resource.source) + elsif new_resource.source + Regexp.escape('Anywhere') + end + end + + def rule_exists_dest? + if new_resource.destination + "#{Regexp.escape(new_resource.destination)}\s" + else + '' + end + end + + def rule_exists_regex?(to, action, from) + if to && new_resource.direction && new_resource.direction.to_sym == :out + /^#{to}.*#{action}OUT\s.*#{from}$/ + elsif to + /^#{to}.*#{action}.*#{from}$/ + end + end + + def rule_exists_proto? + if new_resource.protocol && dport_calc + "#{Regexp.escape(port_to_s(dport_calc))}/#{Regexp.escape(new_resource.protocol)}\s " + elsif dport_calc + "#{Regexp.escape(port_to_s(dport_calc))}\s " + end + end + + def dport_calc + new_resource.dest_port || new_resource.port + end + end +end diff --git a/cookbooks/firewall/libraries/provider_firewall_ufw.rb b/cookbooks/firewall/libraries/provider_firewall_ufw.rb new file mode 100644 index 0000000..a5d4148 --- /dev/null +++ b/cookbooks/firewall/libraries/provider_firewall_ufw.rb @@ -0,0 +1,77 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: firewall +# Resource:: default +# +# Copyright:: 2011, Opscode, Inc. +# +# 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. +# +require 'poise' + +class Chef + class Provider::FirewallUfw < Provider + include Poise + include Chef::Mixin::ShellOut + + def action_enable + converge_by('install ufw, template some defaults, and ufw enable') do + package 'ufw' do + action :nothing + end.run_action(:install) # need this now if running in a provider + + template '/etc/default/ufw' do + action [:create] + owner 'root' + group 'root' + mode '0644' + source 'ufw/default.erb' + cookbook 'firewall' + action :nothing + end.run_action(:create) # need this now if running in a provider + + # new_resource.subresources contains all the firewall rules + if active? + Chef::Log.debug("#{new_resource} already enabled.") + else + shell_out!('ufw', 'enable', :input => 'yes') + Chef::Log.info("#{new_resource} enabled") + if new_resource.log_level + shell_out!('ufw', 'logging', new_resource.log_level.to_s) + Chef::Log.info("#{new_resource} logging enabled at '#{new_resource.log_level}' level") + end + new_resource.updated_by_last_action(true) + end + end + end + + def action_disable + if active? + shell_out!('ufw', 'disable') + Chef::Log.info("#{new_resource} disabled") + new_resource.updated_by_last_action(true) + else + Chef::Log.debug("#{new_resource} already disabled.") + end + end + + private + + def active? + @active ||= begin + cmd = shell_out!('ufw', 'status') + cmd.stdout =~ /^Status:\sactive/ + end + end + end +end diff --git a/cookbooks/firewall/libraries/resource_firewall.rb b/cookbooks/firewall/libraries/resource_firewall.rb new file mode 100644 index 0000000..7551f47 --- /dev/null +++ b/cookbooks/firewall/libraries/resource_firewall.rb @@ -0,0 +1,10 @@ +require 'poise' + +class Chef + class Resource::Firewall < Resource + include Poise(:container => true) + + actions(:enable, :disable, :flush, :save) + attribute(:log_level, :kind_of => [Symbol, String], :equal_to => [:low, :medium, :high, :full, 'low', 'medium', 'high', 'full'], :default => :low) + end +end diff --git a/cookbooks/firewall/libraries/resource_firewall_rule.rb b/cookbooks/firewall/libraries/resource_firewall_rule.rb new file mode 100644 index 0000000..6dc3f6d --- /dev/null +++ b/cookbooks/firewall/libraries/resource_firewall_rule.rb @@ -0,0 +1,36 @@ +require 'poise' + +class Chef + class Resource::FirewallRule < Resource + include Poise(Chef::Resource::Firewall) + + actions(:reject, :allow, :deny, :masquerade, :redirect, :log, :remove) + + attribute(:protocol, :kind_of => [Symbol, String], :equal_to => [:udp, :tcp, :icmp, 'tcp', 'udp', 'icmp'], :default => :tcp) + attribute(:direction, :kind_of => [Symbol, String], :equal_to => [:in, :out, :pre, :post, 'in', 'out', 'pre', 'post'], :default => :in) + attribute(:logging, :kind_of => [Symbol, String], :equal_to => [:connections, :packets, 'connections', 'packets']) + + attribute(:source, :callbacks => { 'must be a valid ip address' => ->(s) { valid_ip?(s) } }) + attribute(:source_port, :kind_of => [Integer, Array, Range]) # source port + attribute(:interface, :kind_of => String) + + attribute(:port, :kind_of => [Integer, Array, Range]) # shorthand for dest_port + attribute(:destination, :callbacks => { 'must be a valid ip address' => ->(s) { valid_ip?(s) } }) + attribute(:dest_port, :kind_of => [Integer, Array, Range]) + attribute(:dest_interface, :kind_of => String) + + attribute(:position, :kind_of => Integer) + attribute(:stateful, :kind_of => [Symbol, String, Array]) + attribute(:redirect_port, :kind_of => Integer) + attribute(:description, :kind_of => String, :name_attribute => true) + + # for when you just want to pass a raw rule + attribute(:raw, :kind_of => String) + + def self.valid_ip?(ip) + IPAddr.new(ip) ? true : false + rescue + false + end + end +end diff --git a/cookbooks/firewall/libraries/z_provider_mapping.rb b/cookbooks/firewall/libraries/z_provider_mapping.rb new file mode 100644 index 0000000..99cc31a --- /dev/null +++ b/cookbooks/firewall/libraries/z_provider_mapping.rb @@ -0,0 +1,22 @@ +# provider mappings for Chef 11 +# https://www.chef.io/blog/2015/02/10/chef-12-provider-resolver/ + +######### +# firewall +######### +Chef::Platform.set platform: :centos, version: '< 7.0', resource: :firewall, provider: Chef::Provider::FirewallIptables +Chef::Platform.set platform: :centos, version: '>= 7.0', resource: :firewall, provider: Chef::Provider::FirewallFirewalld +Chef::Platform.set platform: :redhat, version: '< 7.0', resource: :firewall, provider: Chef::Provider::FirewallIptables +Chef::Platform.set platform: :redhat, version: '>= 7.0', resource: :firewall, provider: Chef::Provider::FirewallFirewalld +Chef::Platform.set platform: :debian, resource: :firewall, provider: Chef::Provider::FirewallUfw +Chef::Platform.set platform: :ubuntu, resource: :firewall, provider: Chef::Provider::FirewallUfw + +######### +# firewall_rule +######### +Chef::Platform.set platform: :centos, version: '< 7.0', resource: :firewall_rule, provider: Chef::Provider::FirewallRuleIptables +Chef::Platform.set platform: :centos, version: '>= 7.0', resource: :firewall_rule, provider: Chef::Provider::FirewallRuleFirewalld +Chef::Platform.set platform: :redhat, version: '< 7.0', resource: :firewall_rule, provider: Chef::Provider::FirewallRuleIptables +Chef::Platform.set platform: :redhat, version: '>= 7.0', resource: :firewall_rule, provider: Chef::Provider::FirewallRuleFirewalld +Chef::Platform.set platform: :debian, resource: :firewall_rule, provider: Chef::Provider::FirewallRuleUfw +Chef::Platform.set platform: :ubuntu, resource: :firewall_rule, provider: Chef::Provider::FirewallRuleUfw diff --git a/cookbooks/firewall/metadata.json b/cookbooks/firewall/metadata.json new file mode 100644 index 0000000..da2a86c --- /dev/null +++ b/cookbooks/firewall/metadata.json @@ -0,0 +1 @@ +{"name":"firewall","version":"1.2.0","description":"Provides a set of primitives for managing firewalls and associated rules.","long_description":"firewall Cookbook\n=================\n[![Build Status](https://secure.travis-ci.org/opscode-cookbooks/firewall.png?branch=master)](http://travis-ci.org/opscode-cookbooks/firewall)\n\nProvides a set of primitives for managing firewalls and associated rules.\n\nPLEASE NOTE - The resource/providers in this cookbook are under heavy development. An attempt is being made to keep the resource simple/stupid by starting with less sophisticated firewall implementations first and refactor/vet the resource definition with each successive provider.\n\n\nRequirements\n------------\n### Platform\n* Ubuntu\n* Debian\n* Redhat\n* CentOS\n\nTested on:\n* Ubuntu 12.04\n* Ubuntu 14.04\n* Debian 7.8\n* CentOS 6.5\n* CentOS 7.0\n\n\nRecipes\n-------\n### default\nThe default recipe creates a firewall resource with action install, and if `node['firewall']['allow_ssh']`, opens port 22 from the world.\n\n\nAttributes\n----------\n\n* `default['firewall']['ufw']['defaults']` hash for template `/etc/default/ufw`\n\nResources/Providers\n-------------------\n- See `librariez/z_provider_mapping.rb` for a full list of providers for each platform and version.\n\n### firewall\n#### Actions\n- `:enable`: *Default action* enable the firewall. this will make any rules that have been defined 'active'.\n- `:disable`: disable the firewall. drop any rules and put the node in an unprotected state.\n- `:flush`: Runs `iptables -F`. Only supported by the iptables firewall provider.\n- `:save`: Runs `service iptables save` under iptables, adds rules permanently under firewall. Not supported in ufw.\n\n#### Attribute Parameters\n- name: name attribute. arbitrary name to uniquely identify this resource\n- log_level: level of verbosity the firewall should log at. valid values are: :low, :medium, :high, :full. default is :low.\n\n#### Examples\n\n```ruby\n# enable platform default firewall\nfirewall 'ufw' do\n action :enable\nend\n\n# increase logging past default of 'low'\nfirewall 'debug firewalls' do\n log_level :high\n action :enable\nend\n```\n\n### firewall_rule\n\n#### Actions\n- `:allow`: the rule should allow incoming traffic.\n- `:deny`: the rule should deny incoming traffic.\n- `:reject`: *Default action: the rule should reject incoming traffic.\n- `:masqerade`: Add masqerade rule\n- `:redirect`: Add redirect-type rule\n- `:log`: Configure logging\n- `:remove`: Remove all rules\n\n#### Attribute Parameters\n- name: name attribute. arbitrary name to uniquely identify this firewall rule\n- protocol: valid values are: :udp, :tcp. default is all protocols\n- port: incoming port number (ie. 22 to allow inbound SSH), or an array of incoming port numbers (ie. [80,443] to allow inbound HTTP & HTTPS). NOTE: `protocol` attribute is required with multiple ports, or a range of incoming port numbers (ie. 60000..61000 to allow inbound mobile-shell. NOTE: `protocol`, or an attribute is required with a range of ports.\n- source: ip address or subnet to filter on incoming traffic. default is `0.0.0.0/0` (ie Anywhere)\n- destination: ip address or subnet to filter on outgoing traffic.\n- dest_port: outgoing port number.\n- position: position to insert rule at. if not provided rule is inserted at the end of the rule list.\n- direction: direction of the rule. valid values are: :in, :out, default is :in\n- interface: interface to apply rule (ie. 'eth0').\n- logging: may be added to enable logging for a particular rule. valid values are: :connections, :packets. In the ufw provider, :connections logs new connections while :packets logs all packets.\n- raw: for passing a raw command to the provider (for use with custom modules, also used by zap provider to clean up non-chef managed rules)\n\n#### Examples\n\n```ruby\n# open standard ssh port, enable firewall\nfirewall_rule 'ssh' do\n port 22\n action :allow\n notifies :enable, 'firewall[ufw]'\nend\n\n# open standard http port to tcp traffic only; insert as first rule\nfirewall_rule 'http' do\n port 80\n protocol :tcp\n position 1\n action :allow\nend\n\n# restrict port 13579 to 10.0.111.0/24 on eth0\nfirewall_rule 'myapplication' do\n port 13579\n source '10.0.111.0/24'\n direction :in\n interface 'eth0'\n action :allow\nend\n\n# open UDP ports 60000..61000 for mobile shell (mosh.mit.edu), note\n# that the protocol attribute is required when using port_range\nfirewall_rule 'mosh' do\n protocol :udp\n port 60000..61000\n action :allow\nend\n\n# open multiple ports for http/https, note that the protocol\n# attribute is required when using ports\nfirewall_rule 'http/https' do\n protocol :tcp\n port [80, 443]\n action :allow\nend\n\nfirewall 'ufw' do\n action :nothing\nend\n```\n\n\nDevelopment\n-----------\nThis section details \"quick development\" steps. For a detailed explanation, see [[Contributing.md]].\n\n1. Clone this repository from GitHub:\n\n $ git clone git@github.com:opscode-cookbooks/firewall.git\n\n2. Create a git branch\n\n $ git checkout -b my_bug_fix\n\n3. Install dependencies:\n\n $ bundle install\n\n4. Make your changes/patches/fixes, committing appropiately\n5. **Write tests**\n6. Run the tests:\n - `bundle exec foodcritic -f any .`\n - `bundle exec rspec`\n - `bundle exec rubocop`\n - `bundle exec kitchen test`\n\n In detail:\n - Foodcritic will catch any Chef-specific style errors\n - RSpec will run the unit tests\n - Rubocop will check for Ruby-specific style errors\n - Test Kitchen will run and converge the recipes\n\n\nLicense & Authors\n-----------------\n- Author:: Seth Chisamore ()\n\n```text\nCopyright:: Copyright (c) 2011-2015 Opscode, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n","maintainer":"Opscode, Inc.","maintainer_email":"cookbooks@opscode.com","license":"Apache 2.0","platforms":{"ubuntu":">= 0.0.0","redhat":">= 0.0.0","centos":">= 0.0.0"},"dependencies":{"poise":"~> 2.0"},"recommendations":{},"suggestions":{},"conflicting":{},"providing":{},"replacing":{},"attributes":{},"groupings":{},"recipes":{}} \ No newline at end of file diff --git a/cookbooks/firewall/recipes/default.rb b/cookbooks/firewall/recipes/default.rb new file mode 100644 index 0000000..6d87dbc --- /dev/null +++ b/cookbooks/firewall/recipes/default.rb @@ -0,0 +1,29 @@ +# +# Cookbook Name:: firewall +# Recipe:: default +# +# Copyright 2011, Opscode, Inc. +# +# 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. +# + +firewall 'default' do + action :enable +end + +firewall_rule 'allow world to ssh' do + port 22 + source '0.0.0.0/0' + action [:allow] + only_if { node['firewall']['allow_ssh'] } +end diff --git a/cookbooks/firewall/templates/default/ufw/default.erb b/cookbooks/firewall/templates/default/ufw/default.erb new file mode 100644 index 0000000..5e6e9c2 --- /dev/null +++ b/cookbooks/firewall/templates/default/ufw/default.erb @@ -0,0 +1,13 @@ +# /etc/default/ufw +# This file is managed by Chef. Do not edit. + +IPV6=<%= node['firewall']['ufw']['defaults']['ipv6'] %> +MANAGE_BUILTINS=<%= node['firewall']['ufw']['defaults']['manage_builtins'] %> + +<% node['firewall']['ufw']['defaults']['policy'].each do |policy, value| -%> +<%= "DEFAULT_#{policy.upcase}_POLICY=\"#{value}\"" %> +<% end -%> + +IPT_SYSCTL="<%= node['firewall']['ufw']['defaults']['ipt_sysctl'] %>" + +IPT_MODULES="<%= node['firewall']['ufw']['defaults']['ipt_modules'] %>" diff --git a/cookbooks/homebrew/CHANGELOG.md b/cookbooks/homebrew/CHANGELOG.md new file mode 100644 index 0000000..0361900 --- /dev/null +++ b/cookbooks/homebrew/CHANGELOG.md @@ -0,0 +1,140 @@ +homebrew Cookbook CHANGELOG +=========================== +This file is used to list changes made in each version of the homebrew cookbook. + +v1.12.0 (2015-01-29) +-------------------- + +- #67 Add attribute and recipe for installing homebrew taps + +v1.11.0 (2015-01-12) +-------------------- + +- #59 Update Homebrew Cask if auto-update attribute is true +- #52 Manage Homebrew Cask's install directories +- #56 Fix check for existing casks +- #61 Fix owner class for Chef 12 +- Depend on build-essential cookbook 2.1.2+ to support OS X 10.10 +- #64, #66 add and fix ChefSpec tests for default recipe + +v1.10.0 (2014-12-09) +-------------------- + +- #55 This cookbook no longer sets its `homebrew_package` as the + `package` provider for OS X when running under Chef 12 +- List CHEF as the maintainer instead of Opscode. + +v1.9.2 (2014-10-09) +------------------- + +Bug Fixes: + +- #57 Update url per homebrew error: Upstream, the homebrew project + has changed the URL for the installation script. All users of this + cookbook are advised to update to this version. + +v1.9.0 (2014-07-29) +------------------- + +Improvements: + +- #35 Modernize the cask provider (use why run mode, inline resources) +- #43 Use `brew cask list` to determine if casks are installed +- #45 Add `default_action` and print warning messages on earlier + versions of Chef (10.10) + +New Features: + +- #44 Add `:install` and `:uninstall` actions and alias previous `:cask`, + `:uncask` actions to them + +Bug Fixes: + +- #27 Fix name for taps adding the `/homebrew` prefix +- #28 Set `RUBYOPT` to `nil` so Chef can execute in a bundle (bundler + sets `RUBYOPT` and this can cause issues when running the + underlying `brew` commands) +- #40 Fix regex for cask to match current homebrew conventions +- #42 Fix attribute for list of formulas to match the README and + maintain backward compat for 6 day old version + +v1.8.0 (2014-07-23) +------------------- +- Add recipes to install an array of formulas/casks + +v1.7.2 (2014-06-26) +------------------- +- Implement attribute to control auto-update + + +v1.7.0 (2014-06-26) +------------------- +#38 - Add homebrew::cask recipe + + +v1.6.6 (2014-05-29) +------------------- +- [COOK-3283] Use homebrew_owner for cask and tap +- [COOK-4670] homebrew_tap provider is not idempotent +- [COOK-4671] Syntax Error in README + + +v1.6.4 (2014-05-08) +------------------- +- Fixing cask provider correctly this time. "brew cask list" + + +v1.6.2 (2014-05-08) +------------------- +- Fixing typo in cask provider: 's/brew brew/brew/' + + +v1.6.0 (2014-04-23) +------------------- +- [COOK-3960] Added LWRP for brew cask +- [COOK-4508] Add ChefSpec matchers for homebrew_tap +- [COOK-4566] Guard against "HEAD only" formulae + + +v1.5.4 +------ +- [COOK-4023] Fix installer script's URL. +- Fixing up style for rubocop + + +v1.5.2 +------ +- [COOK-3825] setting $HOME on homebrew_package + + +v1.5.0 +------ +### Bug +- **[COOK-3589](https://tickets.opscode.com/browse/COOK-3589)** - Add homebrew as the default package manager on OS X Server + +v1.4.0 +------ +### Bug +- **[COOK-3283](https://tickets.opscode.com/browse/COOK-3283)** - Support running homebrew cookbook as root user, with sudo, or a non-privileged user + +v1.3.2 +------ +- [COOK-1793] - use homebrew "go" script to install homebrew +- [COOK-1821] - Discovered version using Homebrew Formula factory fails check that verifies that version is a String +- [COOK-1843] - Homebrew README.md contains non-ASCII characters, triggering same issue as COOK-522 + +v1.3.0 +------ +- [COOK-1425] - use new json output format for formula +- [COOK-1578] - Use shell_out! instead of popen4 + +v1.2.0 +------ +Opscode has taken maintenance of this cookbook as the original author has other commitments. This is the initial release with Opscode as maintainer. + +Changes in this release: + +- [pull/2] - support for option passing to brew +- [pull/3] - add brew upgrade and control return value from command +- [pull/9] - added LWRP for "brew tap" +- README is now markdown, not rdoc. diff --git a/cookbooks/homebrew/README.md b/cookbooks/homebrew/README.md new file mode 100644 index 0000000..2a18087 --- /dev/null +++ b/cookbooks/homebrew/README.md @@ -0,0 +1,167 @@ +# Homebrew Cookbook + +This cookbook installs [Homebrew](http://mxcl.github.com/homebrew/) and under Chef 11 and earlier versions, its package provider replaces MacPorts as the *default package provider* for the package resource on OS X systems. + +This cookbook is maintained by CHEF. The original author, maintainer and copyright holder is Graeme Mathieson. The cookbook remains licensed under the Apache License version 2. + +[Original blog post by Graeme](http://woss.name/2011/01/23/converging-your-home-directory-with-chef/) + +# Requirements + +Chef 12: The package provider is not necessary on Chef 12, as the default [OS X package provider](https://github.com/opscode/chef-rfc/blob/master/rfc016-homebrew-osx-package-provider.md) is homebrew. + +Chef <= 11: The package provider will be set as the default provider for OS X. + +## Prerequisites + +In order for this recipe to work, your userid must own `/usr/local`. This is outside the scope of the cookbook because it's possible that you'll run the cookbook as your own user, not root and you'd have to be root to take ownership of the directory. Easiest way to get started: + +```bash +sudo chown -R `whoami`:staff /usr/local +``` + +Bear in mind that this will take ownership of the entire folder and its contents, so if you've already got stuff in there (eg MySQL owned by a `mysql` user) you'll need to be a touch more careful. This is a recommendation from the Homebrew project. + +**Note** This cookbook *only* supports installing in `/usr/local`. While the Homebrew project itself allows for alternative installations, this cookbook doesn't. + +## Platform + +- Mac OS X (10.6+) + +The only platform supported by Homebrew itself at the time of this writing is Mac OS X. It should work fine on Server edition as well, and on platforms that Homebrew supports in the future. + +## Cookbooks + +- build-essential: homebrew itself doesn't work well if XCode is not installed. + +# Attributes + +- `node['homebrew']['owner']` - The user that will own the Homebrew installation and packages. Setting this will override the default behavior which is to use the non-privileged user that has invoked the Chef run (or the `SUDO_USER` if invoked with sudo). The default is `nil`. +- `node['homebrew']['auto-update']` - Whether the default recipe should automatically update homebrew each run or not. The default is `true` to maintain compatibility. Set to false or nil to disable. Note that disabling this feature may cause formula to not work. +- `node['homebrew']['formulas']` - An Array of formula that should be installed using homebrew by default, used only in the `homebrew::install_formulas` recipe. +- `node['homebrew']['casks']` - An Array of casks that should be installed using brew cask by default, used only in the `homebrew::install_casks` recipe. +- `node['homebrew']['taps']` - An Array of taps that should be installed using brew tap by default, used only in the `homebrew::install_taps` recipe. + +# Resources and Providers + +This cookbook includes a package resource provider to use homebrew. Under Chef 12+, this is not necessary, and the code doesn't actually get used on Chef 12+. This was preserved to maintain backwards compatiblity with older versions of Chef. + +## package / homebrew\_package + +This cookbook provides a package provider called `homebrew_package` which will install/remove packages using Homebrew. This becomes the default provider for `package` if your platform is Mac OS X. + +As this extends the built-in package resource/provider in Chef, it has all the resource attributes and actions available to the package resource. However, a couple notes: + +- Homebrew itself doesn't have a notion of "upgrade" per se. The "upgrade" action will simply perform an install, and if the Homebrew Formula for the package is newer, it will upgrade. +- Likewise, Homebrew doesn't have a purge, but the "purge" action will act like "remove". + +#### Examples + +```ruby +package 'mysql' do + action :install +end + +homebrew_package 'mysql' + +package 'mysql' do + provider Chef::Provider::Package::Homebrew +end + +package 'wireshark' do + options '--with-qt --devel' +end +``` + +### homebrew\_tap + +LWRP for `brew tap`, a Homebrew command used to add additional formula repositories. From the `brew` man page: + +```text +tap [tap] + Tap a new formula repository from GitHub, or list existing taps. + + tap is of the form user/repo, e.g. brew tap homebrew/dupes. +``` + +Default action is `:tap` which enables the repository. Use `:untap` to disable a tapped repository. + +#### Examples + +```ruby +homebrew_tap 'homebrew/dupes' + +homebrew_tap 'homebrew/dupes' do + action :untap +end +``` + +## homebrew\_cask + +LWRP for `brew cask`, a Homebrew-style CLI workflow for the administration +of Mac applications distributed as binaries. It's implemented as a homebrew +"external command" called cask. + +[homebrew-cask on GitHub](https://github.com/caskroom/homebrew-cask) + +### Prerequisites + +You must have the homebrew-cask repository tapped. + +```ruby +homebrew_tap 'caskroom/cask' +``` + +And then install the homebrew cask package before using this LWRP. + +```ruby +package "brew-cask" do + action :install + end +``` + +You can include the `homebrew::cask` recipe to do this. + +### Examples + +```ruby +homebrew_cask "google-chrome" + +homebrew_cask "google-chrome" do + action :uncask +end +``` + +Default action is `:cask` which installs the Application binary . Use `:uncask` to +uninstall a an Application. + +[View the list of available Casks](https://github.com/caskroom/homebrew-cask/tree/master/Casks) + +# Usage + +We strongly recommend that you put "recipe[homebrew]" in your node's run list, to ensure that it is available on the system and that Homebrew itself gets installed. Putting an explicit dependency in the metadata will cause the cookbook to be downloaded and the library loaded, thus resulting in changing the package provider on Mac OS X, so if you have systems you want to use the default (Mac Ports), they would be changed to Homebrew. + +The default recipe also ensures that Homebrew is installed and up to date if the auto update attribute (above) is true (default). + +# License and Authors + +- Author:: Graeme Mathieson () +- Author:: Joshua Timberman () + +```text +Copyright:: 2011, Graeme Mathieson +Copyright:: 2012, Opscode, Inc +Copyright:: 2014-2015, Chef Software, Inc. + +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/cookbooks/homebrew/attributes/default.rb b/cookbooks/homebrew/attributes/default.rb new file mode 100644 index 0000000..2431299 --- /dev/null +++ b/cookbooks/homebrew/attributes/default.rb @@ -0,0 +1,26 @@ +# +# Author:: Joshua Timberman () +# Author:: Graeme Mathieson () +# Cookbook Name:: homebrew +# Attributes:: default +# +# Copyright 2011-2013, Opscode, Inc. +# +# 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. +# + +default['homebrew']['owner'] = nil +default['homebrew']['auto-update'] = true +default['homebrew']['casks'] = [] +default['homebrew']['formulas'] = node['homebrew']['formula'] || [] +default['homebrew']['taps'] = [] diff --git a/cookbooks/homebrew/libraries/homebrew_mixin.rb b/cookbooks/homebrew/libraries/homebrew_mixin.rb new file mode 100644 index 0000000..cc0a2f6 --- /dev/null +++ b/cookbooks/homebrew/libraries/homebrew_mixin.rb @@ -0,0 +1,66 @@ +# +# Author:: Joshua Timberman () +# Author:: Graeme Mathieson () +# Cookbook Name:: homebrew +# Libraries:: homebrew_mixin +# +# Copyright 2011-2013, Opscode, Inc. +# +# 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. +# +# Include the mixin from Chef 12 if its defined, when we get to the +# #homebrew_owner method below... +class Chef12HomebrewUser + include Chef::Mixin::HomebrewUser if defined?(Chef::Mixin::HomebrewUser) +end + +module Homebrew + # Homebrew + module Mixin + def homebrew_owner + if defined?(Chef::Mixin::HomebrewUser) + begin + @homebrew_owner ||= Chef12HomebrewUser.new.find_homebrew_uid + rescue Chef::Exceptions::CannotDetermineHomebrewOwner + @homebrew_owner ||= calculate_owner + end + else + @homebrew_owner ||= calculate_owner + end + end + + private + + def calculate_owner + owner = homebrew_owner_attr || sudo_user || current_user + if owner == 'root' + fail Chef::Exceptions::User, + "Homebrew owner is 'root' which is not supported. " + + "To set an explicit owner, please set node['homebrew']['owner']." + end + owner + end + + def homebrew_owner_attr + node['homebrew']['owner'] + end + + def sudo_user + ENV['SUDO_USER'] + end + + def current_user + ENV['USER'] + end + end +end diff --git a/cookbooks/homebrew/libraries/homebrew_package.rb b/cookbooks/homebrew/libraries/homebrew_package.rb new file mode 100644 index 0000000..21f80cf --- /dev/null +++ b/cookbooks/homebrew/libraries/homebrew_package.rb @@ -0,0 +1,115 @@ +# +# Author:: Joshua Timberman () +# Author:: Graeme Mathieson () +# Cookbook Name:: homebrew +# Libraries:: homebrew_package +# +# Copyright 2011-2013, Opscode, Inc. +# +# 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. +# +# cookbook libraries are unconditionally included if the cookbook is +# present on a node. This approach should avoid creating this class if +# the node already has Chef::Provider::Package::Homebrew, such as with +# Chef 12. +# https://github.com/opscode/chef-rfc/blob/master/rfc016-homebrew-osx-package-provider.md +unless defined?(Chef::Provider::Package::Homebrew) && Chef::Platform.find('mac_os_x', nil)[:package] == Chef::Provider::Package::Homebrew + require 'chef/provider/package' + require 'chef/resource/package' + require 'chef/platform' + require 'chef/mixin/shell_out' + + class Chef + class Provider + class Package + # Package + class Homebrew < Package + # Homebrew packagex + include Chef::Mixin::ShellOut + include ::Homebrew::Mixin + + def load_current_resource + @current_resource = Chef::Resource::Package.new(@new_resource.name) + @current_resource.package_name(@new_resource.package_name) + @current_resource.version(current_installed_version) + + @current_resource + end + + def install_package(name, version) + brew('install', @new_resource.options, name) + end + + def upgrade_package(name, version) + brew('upgrade', name) + end + + def remove_package(name, version) + brew('uninstall', @new_resource.options, name) + end + + # Homebrew doesn't really have a notion of purging, so just remove. + def purge_package(name, version) + @new_resource.options = ((@new_resource.options || '') << ' --force').strip + remove_package(name, version) + end + + protected + + def brew(*args) + get_response_from_command("brew #{args.join(' ')}") + end + + def current_installed_version + pkg = get_version_from_formula + versions = pkg.to_hash['installed'].map { |v| v['version'] } + versions.join(' ') unless versions.empty? + end + + def candidate_version + pkg = get_version_from_formula + pkg.stable ? pkg.stable.version.to_s : pkg.version.to_s + end + + def get_version_from_command(command) + version = get_response_from_command(command).chomp + version.empty? ? nil : version + end + + def get_version_from_formula + brew_cmd = shell_out!('brew --prefix', :user => homebrew_owner) + libpath = ::File.join(brew_cmd.stdout.chomp, 'Library', 'Homebrew') + $LOAD_PATH.unshift(libpath) + + require 'global' + require 'cmd/info' + + Formula[new_resource.package_name] + end + + def get_response_from_command(command) + require 'etc' + home_dir = Etc.getpwnam(homebrew_owner).dir + + Chef::Log.debug "Executing '#{command}' as #{homebrew_owner}" + output = shell_out!(command, :user => homebrew_owner, :environment => { 'HOME' => home_dir, 'RUBYOPT' => nil }) + output.stdout + end + end + end + end + end + + Chef::Platform.set :platform => :mac_os_x_server, :resource => :package, :provider => Chef::Provider::Package::Homebrew + Chef::Platform.set :platform => :mac_os_x, :resource => :package, :provider => Chef::Provider::Package::Homebrew +end diff --git a/cookbooks/homebrew/libraries/matchers.rb b/cookbooks/homebrew/libraries/matchers.rb new file mode 100644 index 0000000..2679528 --- /dev/null +++ b/cookbooks/homebrew/libraries/matchers.rb @@ -0,0 +1,43 @@ +if defined?(ChefSpec) + + def install_homebrew_package(pkg) + ChefSpec::Matchers::ResourceMatcher.new(:homebrew_package, :install, pkg) + end + + def upgrade_homebrew_package(pkg) + ChefSpec::Matchers::ResourceMatcher.new(:homebrew_package, :upgrade, pkg) + end + + def remove_homebrew_package(pkg) + ChefSpec::Matchers::ResourceMatcher.new(:homebrew_package, :remove, pkg) + end + + def purge_homebrew_package(pkg) + ChefSpec::Matchers::ResourceMatcher.new(:homebrew_package, :purge, pkg) + end + + def tap_homebrew_tap(tap) + ChefSpec::Matchers::ResourceMatcher.new(:homebrew_tap, :tap, tap) + end + + def untap_homebrew_tap(tap) + ChefSpec::Matchers::ResourceMatcher.new(:homebrew_tap, :untap, tap) + end + + def cask_homebrew_cask(cask) + ChefSpec::Matchers::ResourceMatcher.new(:homebrew_cask, :cask, cask) + end + + def uncask_homebrew_cask(cask) + ChefSpec::Matchers::ResourceMatcher.new(:homebrew_cask, :uncask, cask) + end + + def install_homebrew_cask(cask) + ChefSpec::Matchers::ResourceMatcher.new(:homebrew_cask, :install, cask) + end + + def uninstall_homebrew_cask(cask) + ChefSpec::Matchers::ResourceMatcher.new(:homebrew_cask, :uninstall, cask) + end + +end diff --git a/cookbooks/homebrew/metadata.json b/cookbooks/homebrew/metadata.json new file mode 100644 index 0000000..34032be --- /dev/null +++ b/cookbooks/homebrew/metadata.json @@ -0,0 +1,33 @@ +{ + "name": "homebrew", + "version": "1.12.0", + "description": "Install Homebrew, and use it as the OS X package provider on Chef versions =< 11", + "long_description": "", + "maintainer": "Chef Software, Inc.", + "maintainer_email": "cookbooks@chef.io", + "license": "Apache 2.0", + "platforms": { + "mac_os_x": ">= 0.0.0", + "mac_os_x_server": ">= 0.0.0" + }, + "dependencies": { + "build-essential": ">= 2.1.2" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + "homebrew": "Install Homebrew" + } +} \ No newline at end of file diff --git a/cookbooks/homebrew/metadata.rb b/cookbooks/homebrew/metadata.rb new file mode 100644 index 0000000..0a3f2e1 --- /dev/null +++ b/cookbooks/homebrew/metadata.rb @@ -0,0 +1,10 @@ +name 'homebrew' +maintainer 'Chef Software, Inc.' +maintainer_email 'cookbooks@chef.io' +license 'Apache 2.0' +description 'Install Homebrew, and use it as the OS X package provider on Chef versions =< 11' +version '1.12.0' +recipe 'homebrew', 'Install Homebrew' +supports 'mac_os_x' +supports 'mac_os_x_server' +depends 'build-essential', '>= 2.1.2' diff --git a/cookbooks/homebrew/providers/cask.rb b/cookbooks/homebrew/providers/cask.rb new file mode 100644 index 0000000..4073ccc --- /dev/null +++ b/cookbooks/homebrew/providers/cask.rb @@ -0,0 +1,36 @@ +require 'chef/mixin/shell_out' +include Chef::Mixin::ShellOut +include ::Homebrew::Mixin + +use_inline_resources if defined?(:use_inline_resources) + +def whyrun_supported? + true +end + +def load_current_resource + @cask = Chef::Resource::HomebrewCask.new(new_resource.name) + Chef::Log.debug("Checking whether #{new_resource.name} is installed") + @cask.casked shell_out("/usr/local/bin/brew cask list | grep #{new_resource.name}").exitstatus == 0 +end + +action :install do + unless @cask.casked + execute "installing cask #{new_resource.name}" do + command "/usr/local/bin/brew cask install #{new_resource.name}" + user homebrew_owner + end + end +end + +action :uninstall do + if @cask.casked + execute "uninstalling cask #{new_resource.name}" do + command "/usr/local/bin/brew cask uninstall #{new_resource.name}" + user homebrew_owner + end + end +end + +alias_method :action_cask, :action_install +alias_method :action_uncask, :action_uninstall diff --git a/cookbooks/homebrew/providers/tap.rb b/cookbooks/homebrew/providers/tap.rb new file mode 100644 index 0000000..f3b8461 --- /dev/null +++ b/cookbooks/homebrew/providers/tap.rb @@ -0,0 +1,54 @@ +# +# Author:: Joshua Timberman () +# Author:: Graeme Mathieson () +# Cookbook Name:: homebrew +# Providers:: tap +# +# Copyright 2011-2013, Opscode, Inc. +# +# 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. +# + +include ::Homebrew::Mixin + +def load_current_resource + @tap = Chef::Resource::HomebrewTap.new(new_resource.name) + tap_dir = @tap.name.gsub('/', '/homebrew-') + + Chef::Log.debug("Checking whether we've already tapped #{new_resource.name}") + if ::File.directory?("/usr/local/Library/Taps/#{tap_dir}") + @tap.tapped true + else + @tap.tapped false + end +end + +action :tap do + unless @tap.tapped + execute "tapping #{new_resource.name}" do + command "/usr/local/bin/brew tap #{new_resource.name}" + not_if "/usr/local/bin/brew tap | grep #{new_resource.name}" + user homebrew_owner + end + end +end + +action :untap do + if @tap.tapped + execute "untapping #{new_resource.name}" do + command "/usr/local/bin/brew untap #{new_resource.name}" + only_if "/usr/local/bin/brew tap | grep #{new_resource.name}" + user homebrew_owner + end + end +end diff --git a/cookbooks/homebrew/recipes/cask.rb b/cookbooks/homebrew/recipes/cask.rb new file mode 100644 index 0000000..a44c452 --- /dev/null +++ b/cookbooks/homebrew/recipes/cask.rb @@ -0,0 +1,39 @@ +# +# Cookbook Name:: homebrew +# Recipes:: cask +# +# Copyright 2014, Chef Software, Inc +# +# 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. +# +Chef::Resource.send(:include, Homebrew::Mixin) + +homebrew_tap 'caskroom/cask' + +package 'brew-cask' + +execute 'update homebrew cask from github' do + user node['homebrew']['owner'] || homebrew_owner + command '/usr/local/bin/brew upgrade brew-cask && /usr/local/bin/brew cask cleanup || true' + only_if { node['homebrew']['auto-update'] } +end + +directory '/opt/homebrew-cask' do + owner node['homebrew']['owner'] || homebrew_owner + mode 00775 +end + +directory '/opt/homebrew-cask/Caskroom' do + owner node['homebrew']['owner'] || homebrew_owner + mode 00775 +end diff --git a/cookbooks/homebrew/recipes/default.rb b/cookbooks/homebrew/recipes/default.rb new file mode 100644 index 0000000..2dabcc9 --- /dev/null +++ b/cookbooks/homebrew/recipes/default.rb @@ -0,0 +1,49 @@ +# +# Author:: Joshua Timberman () +# Author:: Graeme Mathieson () +# Cookbook Name:: homebrew +# Recipes:: default +# +# Copyright 2011-2013, Opscode, Inc. +# +# 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. +# + +Chef::Resource.send(:include, Homebrew::Mixin) +Chef::Recipe.send(:include, Homebrew::Mixin) + +homebrew_go = "#{Chef::Config[:file_cache_path]}/homebrew_go" + +Chef::Log.debug("Homebrew owner is '#{homebrew_owner}'") + +remote_file homebrew_go do + source 'https://raw.githubusercontent.com/Homebrew/install/master/install' + mode 00755 +end + +execute 'install homebrew' do + command homebrew_go + user node['homebrew']['owner'] || homebrew_owner + not_if { ::File.exist? '/usr/local/bin/brew' } +end + +if node['homebrew']['auto-update'] + package 'git' do + not_if 'which git' + end + + execute 'update homebrew from github' do + user homebrew_owner + command '/usr/local/bin/brew update || true' + end +end diff --git a/cookbooks/homebrew/recipes/install_casks.rb b/cookbooks/homebrew/recipes/install_casks.rb new file mode 100644 index 0000000..8ac4827 --- /dev/null +++ b/cookbooks/homebrew/recipes/install_casks.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: homebrew +# Recipes:: install_casks +# +# Copyright 2014, Chef Software, Inc +# +# 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. +# + +include_recipe 'homebrew::cask' + +node['homebrew']['casks'].each do |cask| + homebrew_cask cask +end diff --git a/cookbooks/homebrew/recipes/install_formulas.rb b/cookbooks/homebrew/recipes/install_formulas.rb new file mode 100644 index 0000000..0bb8495 --- /dev/null +++ b/cookbooks/homebrew/recipes/install_formulas.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: homebrew +# Recipes:: install_casks +# +# Copyright 2014, Chef Software, Inc +# +# 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. +# + +include_recipe 'homebrew' + +node['homebrew']['formulas'].each do |formula| + package formula +end diff --git a/cookbooks/homebrew/recipes/install_taps.rb b/cookbooks/homebrew/recipes/install_taps.rb new file mode 100644 index 0000000..83d0f7b --- /dev/null +++ b/cookbooks/homebrew/recipes/install_taps.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: homebrew +# Recipes:: install_taps +# +# Copyright 2015, Chef Software, Inc +# +# 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. +# + +include_recipe 'homebrew' + +node['homebrew']['taps'].each do |tap| + homebrew_tap tap +end diff --git a/cookbooks/homebrew/resources/cask.rb b/cookbooks/homebrew/resources/cask.rb new file mode 100644 index 0000000..587bcea --- /dev/null +++ b/cookbooks/homebrew/resources/cask.rb @@ -0,0 +1,19 @@ +actions :cask, :uncask, :install, :uninstall +attribute :name, + :name_attribute => true, + :kind_of => String, + :regex => /^[\w-]+$/ + +attribute :casked, + :kind_of => [TrueClass, FalseClass] + +if defined?(:default_action) + default_action :install +else + Chef::Log.warn("It appears you have Chef version #{Chef::VERSION},") + Chef::Log.warn('homebrew_cask resource will remove support for versions of Chef < 10.10 in the next major release of the cookbook') + def initialize(*args) + super + @action = :install + end +end diff --git a/cookbooks/homebrew/resources/tap.rb b/cookbooks/homebrew/resources/tap.rb new file mode 100644 index 0000000..e912f77 --- /dev/null +++ b/cookbooks/homebrew/resources/tap.rb @@ -0,0 +1,35 @@ +# +# Author:: Joshua Timberman () +# Author:: Graeme Mathieson () +# Cookbook Name:: homebrew +# Resources:: tap +# +# Copyright 2011-2013, Opscode, Inc. +# +# 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. +# + +actions :tap, :untap +attribute :name, + :name_attribute => true, + :kind_of => String, + :regex => /^[\w-]+(?:\/[\w-]+)+$/ + +attribute :tapped, + :kind_of => [TrueClass, FalseClass] + +### hax for default action +def initialize(*args) + super + @action = :tap +end diff --git a/cookbooks/hostname/.gitignore b/cookbooks/hostname/.gitignore new file mode 100644 index 0000000..771929b --- /dev/null +++ b/cookbooks/hostname/.gitignore @@ -0,0 +1,15 @@ +*# +*.un~ +*~ +.#* +.*.sw[a-z] +.bundle/* +.kitchen.local.yml +.kitchen/ +.vagrant +/cookbooks +Berksfile.lock +Gemfile.lock +\#*# +bin/* +metadata.json diff --git a/cookbooks/hostname/.kitchen.yml b/cookbooks/hostname/.kitchen.yml new file mode 100644 index 0000000..e63cddd --- /dev/null +++ b/cookbooks/hostname/.kitchen.yml @@ -0,0 +1,57 @@ +--- +driver_plugin: vagrant +driver_config: + require_chef_omnibus: true + +platforms: +- name: ubuntu-12.04 + driver_config: + box: opscode_ubuntu-12.04_provisionerless + box_url: https://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_ubuntu-12.04_chef-provisionerless.box +- name: ubuntu-10.04 + driver_config: + box: opscode_ubuntu-10.04_provisionerless + box_url: https://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_ubuntu-10.04_chef-provisionerless.box +- name: debian-6 + driver_config: + box: opscode_debian-6.0.8_provisionerless + box_url: https://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_debian-6.0.8_chef-provisionerless.box +- name: debian-7 + driver_config: + box: opscode_debian-7.2.0_provisionerless + box_url: https://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_debian-7.2.0_chef-provisionerless.box +- name: centos-6.4 + driver_config: + box: opscode_centos-6.4_provisionerless + box_url: https://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_centos-6.4_chef-provisionerless.box +#- name: freebsd-9.2 +# driver_config: +# box: opscode_freebsd-9.2_provisionerless +# box_url: https://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_freebsd-9.2_chef-provisionerless.box +- name: freebsd-10 + driver_config: + box: opscode_freebsd-10.0_provisionerless + box_url: https://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_freebsd-10.0_chef-provisionerless.box +- name: fedora-19 + driver_config: + box: opscode_fedora-19_chef-provisionerless + box_url: https://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_fedora-19_chef-provisionerless.box +- name: fedora-20 + driver_config: + box: opscode_fedora-20_chef-provisionerless + box_url: https://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_fedora-20_chef-provisionerless.box + +suites: +- name: default + run_list: + - recipe[hostname::default] + attributes: + set_fqdn: test.example.com +- name: wildcard + run_list: + - recipe[hostname::default] + provisioner: + solo_rb: + node_name: test + attributes: + set_fqdn: '*.example.com' diff --git a/cookbooks/hostname/.rubocop.yml b/cookbooks/hostname/.rubocop.yml new file mode 100644 index 0000000..00d809d --- /dev/null +++ b/cookbooks/hostname/.rubocop.yml @@ -0,0 +1,9 @@ +AllCops: + Excludes: + - tmp/** + +Documentation: + Enabled: false + +LineLength: + Enabled: false diff --git a/cookbooks/hostname/.travis.yml b/cookbooks/hostname/.travis.yml new file mode 100644 index 0000000..c8a111e --- /dev/null +++ b/cookbooks/hostname/.travis.yml @@ -0,0 +1,13 @@ +bundler_args: --without integration +before_script: bundle exec berks install +script: bundle exec strainer test +language: ruby +rvm: + - 1.9.3 +notifications: + hipchat: + secure: ! 'mTePogP3SZVXbZ8l3o1yN+uszEEwQY3WgBXXXQc4hV1nLGhAXgjgj5ueCKAE + + JzFSZEjp+31OOg4CIMKs+awwv/SUnFUjB1hlHPrpVnWqUORez6l2fvPABiIs + + kshNJ3x5zPlxJwo/U8jObaxJ7XdjxFKugod3q2rzTsq3TlLF2WA=' diff --git a/cookbooks/hostname/Berksfile b/cookbooks/hostname/Berksfile new file mode 100644 index 0000000..f25cd60 --- /dev/null +++ b/cookbooks/hostname/Berksfile @@ -0,0 +1,8 @@ +# -*- ruby -*- +site :opscode + +metadata + +group :integration do + cookbook "minitest-handler" +end diff --git a/cookbooks/hostname/CHANGELOG.md b/cookbooks/hostname/CHANGELOG.md new file mode 100644 index 0000000..9c53865 --- /dev/null +++ b/cookbooks/hostname/CHANGELOG.md @@ -0,0 +1,46 @@ +# Change History + +0.3.0 +===== + - Fixed (and tested) FreeBSD support + - #17: added support for RedHat & CentOS (Damien Roche, Marta Paciorkowska) + - added instructions on manual testing with reboot (Marta Paciorkowska) + +0.2.0 +===== + - Refresh and fix tests, add Rubocop style checks (Marta Paciorkowska) + - #16: do not remove existing /etc/hosts entries on 127.0.0.1, use + configurable IP for hostname entry we need to have for ourselves + (Jean Mertz, Marta Paciorkowska) + +0.1.0 +===== + - Tests: chefspec, test-kitchen + - Substitute `*` in `set_fqdn` with `node.name` to allow fully + automatic FQDN setup. + +0.0.6 +===== + - Clean up backup files from community.opscode.com release + +0.0.5 +===== + - Added change log + - `metadata.rb` explicitly states cookbook name (Chulki Lee) + - New recipe `hostname::vmware` (tily) + - Use hostsfile cookbook to manipulate `/etc/hosts`, use 127.0.1.1 + instead of trying to figure out own IP (Guilhem Lettron) + +0.0.4 +===== + - Use attribute levels when setting for Chef 11 compatibility (Alan + Wilhelm) + +0.0.3 +===== + - Syntax fix for compatibility with Ruby 1.9.2 + +0.0.2 and before +================ + +This is prehistory. diff --git a/cookbooks/hostname/Gemfile b/cookbooks/hostname/Gemfile new file mode 100644 index 0000000..92369be --- /dev/null +++ b/cookbooks/hostname/Gemfile @@ -0,0 +1,14 @@ +source 'https://rubygems.org' + +gem 'berkshelf' +gem 'chef', '~> 11.10' +gem 'chefspec' +gem 'foodcritic', '~> 3.0' +gem 'rake' +gem 'strainer' +gem 'rubocop' + +group :integration do + gem 'test-kitchen', '~> 1.0' + gem 'kitchen-vagrant' +end diff --git a/cookbooks/hostname/README.md b/cookbooks/hostname/README.md new file mode 100644 index 0000000..f4b144b --- /dev/null +++ b/cookbooks/hostname/README.md @@ -0,0 +1,44 @@ +# hostname cookbook + +## Description + +Sets hostname and FQDN of the node. The latest code is hosted at +https://github.com/3ofcoins/chef-cookbook-hostname + +### Important + +Setting hostname on FQDN is not (and won't be) supported. Unfortunately, using dots in the hostname can cause +[inconsistent results for any system that consumes DNS](http://serverfault.com/questions/229331/can-i-have-dots-in-a-hostname) +and [is not allowed by RFC952](http://tools.ietf.org/html/rfc952). If a user +needs additional info in their shell prompt, they can change PS1 in etc/profile +to include the FQDN together with any information they find useful (such as +the customer, the environment, etc). + +## Attributes + +- `node['set_fqdn']` - FQDN to set. + +The asterisk character will be replaced with `node.name`. This way, +you can add this to base role: + +```ruby +default_attributes :set_fqdn => '*.project-domain.com' +``` + +and have node set its FQDN and hostname based on its chef node name +(which is provided on `chef-client` first run's command line). + +- `node['hostname_cookbook']['hostsfile_ip']` -- IP used in + `/etc/hosts` to correctly set FQDN (default: `127.0.1.1`) + + +## Recipes + +* `hostname::default` -- will set node's FQDN to value of `set_fqdn` attribute, +and hostname to its host part (up to first dot). +* `hostname::vmware` -- sets hostname automatically using vmtoolsd. +You do not need to set `node["set_fqdn"]`. + +## Author + +Author: Maciej Pasternacki maciej@3ofcoins.net diff --git a/cookbooks/hostname/Strainerfile b/cookbooks/hostname/Strainerfile new file mode 100644 index 0000000..23dc97f --- /dev/null +++ b/cookbooks/hostname/Strainerfile @@ -0,0 +1,4 @@ +rubocop: rubocop $COOKBOOK +knife test: knife cookbook test $COOKBOOK +foodcritic: foodcritic --tags ~FC015 --epic-fail any $SANDBOX/$COOKBOOK +chefspec: rspec $SANDBOX/$COOKBOOK diff --git a/cookbooks/hostname/TESTING.md b/cookbooks/hostname/TESTING.md new file mode 100644 index 0000000..a47af94 --- /dev/null +++ b/cookbooks/hostname/TESTING.md @@ -0,0 +1,46 @@ +Testing +======= + +Preparation +----- + + $ bundle install + +And then, to install all cookbooks: + + $ bundle exec berks install + +Local +----- + + $ bundle exec strainer test + +The above runs: + + - rubocop + - `knife cookbook test` tests + - Food Critic lint + - Chefspec tests + +Chefspec tests (the interesting part) are in `spec/`. + +Integration +----------- + + $ bundle exec kitchen test + + See `.kitchen.yml` and `test/` directory for details. + + It is important to check if the applied hostname and fqdn values remain the same + also after machine reboot. As it is impossible to reboot using kitchen, it has to + be done manually: + + - run `kitchen converge` and `kitchen verify` to ensure your run is error-free, + - log in to the machine. If you're on a Debian-based system, you need to ensure + that the `/tmp` folder won't be deleted with rebooting, since that's where busser + resides. If `/tmp` is deleted, `kitchen verify` will fail after reboot even if a + manual check confirms that hostname, fqdn and dnsdomainname are correct. You can + preserve the `/tmp` folder by typing the following in your terminal: + `sudo find /etc/default/rcS -type f -exec sed -i 's/TMPTIME=0/TMPTIME=-1/g' {} \;` + - run `sudo reboot`, + - wait for the machine to reboot and run `kitchen verify` again. diff --git a/cookbooks/hostname/Thorfile b/cookbooks/hostname/Thorfile new file mode 100644 index 0000000..c6f8757 --- /dev/null +++ b/cookbooks/hostname/Thorfile @@ -0,0 +1,41 @@ +# -*- ruby -*- + +require 'rubygems' +require 'bundler/setup' + +require 'shellwords' + +class Cookbook < Thor + COOKBOOK_NAME = 'hostname' + COOKBOOK_CATEGORY = 'utilities' + + include Thor::Actions + + desc :edit, "Edit cookbook in browser" + def edit + open "http://community.opscode.com/cookbooks/#{COOKBOOK_NAME}/edit" + end + + desc :browse, "Go to cookbook's page on Opscode's community website" + def browse + open "http://community.opscode.com/cookbooks/#{COOKBOOK_NAME}/" + end + + desc :upload, "Upload cookbook to Opscode's community website" + def upload + run "knife cookbook site share #{COOKBOOK_NAME} #{Shellwords.escape(COOKBOOK_CATEGORY)} -o #{Shellwords.escape(File.dirname(File.dirname(__FILE__)))}" + end + + private + + def open(what) + run "#{open_cmd} #{Shellwords.escape(what)}" + end + + def open_cmd + @open_cmd ||= %w[open xdg-open].find do |command| + system "which #{command} >/dev/null 2>&1" + $?.success? + end + end +end diff --git a/cookbooks/hostname/attributes/default.rb b/cookbooks/hostname/attributes/default.rb new file mode 100644 index 0000000..f84d8d6 --- /dev/null +++ b/cookbooks/hostname/attributes/default.rb @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +default['hostname_cookbook']['hostsfile_ip'] = '127.0.1.1' +default['hostname_cookbook']['hostsfile_ip_interface'] = 'lo0' if platform == 'freebsd' diff --git a/cookbooks/hostname/chefignore b/cookbooks/hostname/chefignore new file mode 100644 index 0000000..a6de142 --- /dev/null +++ b/cookbooks/hostname/chefignore @@ -0,0 +1,96 @@ +# Put files/directories that should be ignored in this file when uploading +# or sharing to the community site. +# Lines that start with '# ' are comments. + +# OS generated files # +###################### +.DS_Store +Icon? +nohup.out +ehthumbs.db +Thumbs.db + +# SASS # +######## +.sass-cache + +# EDITORS # +########### +\#* +.#* +*~ +*.sw[a-z] +*.bak +REVISION +TAGS* +tmtags +*_flymake.* +*_flymake +*.tmproj +.project +.settings +mkmf.log + +## COMPILED ## +############## +a.out +*.o +*.pyc +*.so +*.com +*.class +*.dll +*.exe +*/rdoc/ + +# Testing # +########### +.watchr +.rspec +spec/* +spec/fixtures/* +test/* +features/* +Guardfile +Procfile + +# SCM # +####### +.git +*/.git +.gitignore +.gitmodules +.gitconfig +.gitattributes +.svn +*/.bzr/* +*/.hg/* +*/.svn/* + +# Berkshelf # +############# +Berksfile +Berksfile.lock +cookbooks/* +tmp + +# Cookbooks # +############# +CONTRIBUTING +CHANGELOG* + +# Strainer # +############ +Colanderfile +Strainerfile +.colander +.strainer + +# Vagrant # +########### +.vagrant +Vagrantfile + +# Travis # +########## +.travis.yml diff --git a/cookbooks/hostname/metadata.rb b/cookbooks/hostname/metadata.rb new file mode 100644 index 0000000..ea14333 --- /dev/null +++ b/cookbooks/hostname/metadata.rb @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +name 'hostname' +maintainer 'Maciej Pasternacki' +maintainer_email 'maciej@3ofcoins.net' +license 'MIT' +description 'Configures hostname and FQDN' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '0.3.0' + +supports 'debian' +supports 'ubuntu' +supports 'freebsd' + +depends 'hostsfile' diff --git a/cookbooks/hostname/recipes/default.rb b/cookbooks/hostname/recipes/default.rb new file mode 100644 index 0000000..b1e23b4 --- /dev/null +++ b/cookbooks/hostname/recipes/default.rb @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +# +# Cookbook Name:: hostname +# Recipe:: default +# +# Copyright 2011, Maciej Pasternacki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +fqdn = node['set_fqdn'] +if fqdn + fqdn = fqdn.sub('*', node.name) + fqdn =~ /^([^.]+)/ + hostname = Regexp.last_match[1] + + case node['platform'] + when 'freebsd' + directory '/etc/rc.conf.d' do + mode '0755' + end + + rc_conf_lines = ["hostname=#{fqdn}\n"] + if node['hostname_cookbook']['hostsfile_ip_interface'] + rc_conf_lines << + "ifconfig_#{node['hostname_cookbook']['hostsfile_ip_interface']}_alias=\"inet #{node['hostname_cookbook']['hostsfile_ip']}/32\"\n" + service 'netif' + end + + file '/etc/rc.conf.d/hostname' do + content rc_conf_lines.join + mode '0644' + notifies :reload, 'service[netif]', :immediately \ + if node['hostname_cookbook']['hostsfile_ip_interface'] + end + + execute "hostname #{fqdn}" do + only_if { node['fqdn'] != fqdn } + notifies :reload, 'ohai[reload]', :immediately + end + + when 'centos', 'redhat', 'amazon', 'scientific' + hostfile = '/etc/sysconfig/network' + ruby_block "Update #{hostfile}" do + block do + file = Chef::Util::FileEdit.new(hostfile) + file.search_file_replace_line('^HOSTNAME', "HOSTNAME=#{fqdn}") + file.write_file + end + notifies :reload, 'ohai[reload]', :immediately + end + # this is to persist the correct hostname after machine reboot + sysctl = '/etc/sysctl.conf' + ruby_block "Update #{sysctl}" do + block do + file = Chef::Util::FileEdit.new(sysctl) + file.insert_line_if_no_match("kernel.hostname=#{hostname}", \ + "kernel.hostname=#{hostname}") + file.write_file + end + notifies :reload, 'ohai[reload]', :immediately + end + execute "hostname #{hostname}" do + only_if { node['hostname'] != hostname } + notifies :reload, 'ohai[reload]', :immediately + end + service 'network' do + action :restart + end + + else + file '/etc/hostname' do + content "#{hostname}\n" + mode '0644' + notifies :reload, 'ohai[reload]', :immediately + end + + execute "hostname #{hostname}" do + only_if { node['hostname'] != hostname } + notifies :reload, 'ohai[reload]', :immediately + end + end + + hostsfile_entry 'localhost' do + ip_address '127.0.0.1' + hostname 'localhost' + action :append + end + + hostsfile_entry 'set hostname' do + ip_address node['hostname_cookbook']['hostsfile_ip'] + hostname fqdn + aliases [hostname] + action :create + notifies :reload, 'ohai[reload]', :immediately + end + + ohai 'reload' do + action :nothing + end +else + log 'Please set the set_fqdn attribute to desired hostname' do + level :warn + end +end diff --git a/cookbooks/hostname/recipes/vmware.rb b/cookbooks/hostname/recipes/vmware.rb new file mode 100644 index 0000000..ac10f42 --- /dev/null +++ b/cookbooks/hostname/recipes/vmware.rb @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# +# Cookbook Name:: hostname +# Recipe:: vmware +# +# Copyright 2011, Maciej Pasternacki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +if node['virtualization']['system'] != 'vmware' + Chef::Log.warn('node["virtualization"]["system"] is not "vmware".') +end + +unless FileTest.executable?('/usr/sbin/vmtoolsd') + Chef::Application.fatal!('/usr/sbin/vmtoolsd is not found or not executable.') +end + +node.default['set_fqdn'] = Mixlib::ShellOut.new("/usr/sbin/vmtoolsd --cmd 'info-get guestinfo.hostname'").stdout.chomp +include_recipe 'hostname::default' diff --git a/cookbooks/hostsfile/CHANGELOG.md b/cookbooks/hostsfile/CHANGELOG.md new file mode 100644 index 0000000..79838bb --- /dev/null +++ b/cookbooks/hostsfile/CHANGELOG.md @@ -0,0 +1,73 @@ +hostsfile Cookbook CHANGELOG +======================= +This file is used to list changes made in each version of the hostsfile cookbook. + + +v2.4.5 (2014-06-24) +------------------- +- Fix notifications and why-run mode + + +v2.4.4 (2014-02-25) +------------------- +- Bump Berkshelf version +- Remove scope pieces from IPv6 addresses + + +v2.4.3 (2014-02-01) +------------------- + +- Package custom ChefSpec matchers +- Update testing harness +- Avoid using `Chef::Application.fatal!` +- Use Chef::Resource::File for atomic updates + + +v2.4.2 +------ +- Fix Travis CI integration +- Remove newline characters +- Allow specifying a custom hostsfile path + + +v2.4.1 +------ +- Force a new upload to the community site + + +v2.4.0 +------ +- Convert everything to Ruby 1.9 syntax because I'm tired of people removing trailing commas despite the **massive** warning in the README: ([#29](https://github.com/customink-webops/hostsfile/issues/29), [#30](https://github.com/customink-webops/hostsfile/issues/30), [#32](https://github.com/customink-webops/hostsfile/issues/32), [#33](https://github.com/customink-webops/hostsfile/issues/33), [#34](https://github.com/customink-webops/hostsfile/issues/34), [#35](https://github.com/customink-webops/hostsfile/issues/35), [#36](https://github.com/customink-webops/hostsfile/issues/36), [#38](https://github.com/customink-webops/hostsfile/issues/38), [#39](https://github.com/customink-webops/hostsfile/issues/39)) +- Update to the latest and greatest testing gems and practices +- Remove strainer in favor of a purer solution +- Update `.gitignore` to ignore additional files +- Add more platforms to the `.kitchen.yml` +- Use `converge_by` and support whyruny mode + +v2.0.0 +------ +- Completely manage the hostsfile, ensuring no duplicate entries + +v1.0.2 +------ +- Support Windows (thanks @igantt-daptiv) +- Specs + Travis support +- Throw fatal error if hostsfile does not exist (@jkerzner) +- Write priorities in hostsfile so they are read on subsequent Chef runs + +v0.2.0 +------ +- Updated README to require Ruby 1.9 +- Allow hypens in hostnames +- Ensure newline at end of file +- Allow priority ordering in hostsfile + +v0.1.1 +------ +- Fixed issue #1 +- Better unique object filtering +- Better handing of aliases + +v0.1.0 +------ +- Initial release diff --git a/cookbooks/hostsfile/README.md b/cookbooks/hostsfile/README.md new file mode 100644 index 0000000..f4574ce --- /dev/null +++ b/cookbooks/hostsfile/README.md @@ -0,0 +1,234 @@ +hostsfile LWRP +============== +[![Build Status](https://travis-ci.org/customink-webops/hostsfile.png?branch=master)](https://travis-ci.org/customink-webops/hostsfile) + +`hostsfile` provides an LWRP for managing your `/etc/hosts` (or Windows equivalent) file using Chef. + + +Requirements +------------ +- Chef 11 or higher +- **Ruby 1.9.3 or higher** + +**Please stop opening Pull Requests to restore Ruby 1.8 support!** Any of the `1.x.y` series of this cookbook will work with Chef 10 and Ruby 1.8. You can use Opscode's [Omnibus installer](http://www.opscode.com/blog/2012/06/29/omnibus-chef-packaging/) to install Ruby 1.9+ and Seth Chisamore's [Vagrant Omnibus plugin](https://github.com/schisamo/vagrant-omnibus) to get Ruby 1.9+ on your Vagrant box. + + +Attributes +---------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionExampleDefault
ip_address(name attribute) the IP address for the entry1.2.3.4
hostname(required) the hostname associated with the entryexample.com
uniqueremove any existing entries that have the same hostnametruefalse
aliasesarray of aliases for the entry['www.example.com'][]
commenta comment to append to the end of the entry'interal DNS server'nil
prioritythe relative position of this entry20(varies, see **Priorities** section)
+ + +Actions +------- +**Please note**: In `v0.1.2`, specifying a hostname or alias that existed in another automatically removed that hostname from the other entry before. In `v2.1.0`, the `unique` option was added to give the user case-by-case control of this behavior. For example, given an `/etc/hosts` file that contains: + + 1.2.3.4 example.com www.example.com + +when the Chef recipe below is converged: + +```ruby +hostsfile_entry '2.3.4.5' do + hostname 'www.example.com' + unique true +end +``` + +then the `/etc/hosts` file will look like this: + + 1.2.3.4 example.com + 2.3.4.5 www.example.com + +Not specifying the `unique` parameter will result in duplicate hostsfile entries. + +#### `create` +Creates a new hosts file entry. If an entry already exists, it will be overwritten by this one. + +```ruby +hostsfile_entry '1.2.3.4' do + hostname 'example.com' + action :create +end +``` + +This will create an entry like this: + + 1.2.3.4 example.com + +#### `create_if_missing` +Create a new hosts file entry, only if one does not already exist for the given IP address. If one exists, this does nothing. + +```ruby +hostsfile_entry '1.2.3.4' do + hostname 'example.com' + action :create_if_missing +end +``` + +#### `append` +Append a hostname or alias to an existing record. If the given IP address doesn't already exist in the hostsfile, this method behaves the same as create. Otherwise, it will append the additional hostname and aliases to the existing entry. + + 1.2.3.4 example.com www.example.com # Created by Chef + +```ruby +hostsfile_entry '1.2.3.4' do + hostname 'www2.example.com' + aliases ['foo.com', 'foobar.com'] + comment 'Append by Recipe X' + action :append +end +``` + +would yield: + + 1.2.3.4 example.com www.example.com www2.example.com foo.com foobar.com # Created by Chef, Appended by Recipe X + + +#### `update` +Updates the given hosts file entry. Does nothing if the entry does not exist. + +```ruby +hostsfile_entry '1.2.3.4' do + hostname 'example.com' + comment 'Update by Chef' + action :update +end +``` + +This will create an entry like this: + + 1.2.3.4 example # Updated by Chef + +#### `remove` +Removes an entry from the hosts file. Does nothing if the entry does not +exist. + +```ruby +hostsfile_entry '1.2.3.4' do + action :remove +end +``` + +This will remove the entry for `1.2.3.4`. + + +Usage +----- +If you're using [Berkshelf](http://berkshelf.com/), just add `hostsfile` to your `Berksfile`: + +```ruby +cookbook 'hostsfile' +``` + +Otherwise, install the cookbook from the community site: + + knife cookbook site install hostsfile + +Have any other cookbooks *depend* on hostsfile by editing editing the `metadata.rb` for your cookbook. + +```ruby +# metadata.rb +depends 'hostsfile' +``` + +Note that you can specify a custom path to your hosts file in the `['hostsfile']['path']` node attribute. Otherwise, it defaults to sensible paths depending on your OS. + +### Testing +If you are using [ChefSpec](https://github.com/sethvargo/chefspec) to unit test a cookbook that implements the `hostsfile_entry` LWRP, this cookbook packages customer matchers that you can use in your unit tests: + +- `append_hostsfile_entry` +- `create_hostsfile_entry` +- `create_hostsfile_entry_if_missing` +- `remove_hostsfile_entry` +- `update_hostsfile_entry` + +For example: + +```ruby +it 'creates a hostsfile entry for the DNS server' do + expect(chef_run).to create_hostsfile_entry('1.2.3.4') + .with_hostname('dns.example.com') +end +``` + +Priority +-------- +Priority is a relatively new addition to the cookbook. It gives you the ability to (somewhat) specify the relative order of entries. By default, the priority is calculated for you as follows: + +1. Local, loopback +2. IPV4 +3. IPV6 + +However, you can override it using the `priority` option. + + +Contributing +------------ +1. Fork the project +2. Create a feature branch corresponding to you change +3. Commit and test thoroughly +4. Create a Pull Request on github + + +License & Authors +----------------- +- Author:: Seth Vargo (sethvargo@gmail.com) + +```text +Copyright 2012-2013, Seth Vargo +Copyright 2012, CustomInk, LLC + +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/cookbooks/hostsfile/attributes/default.rb b/cookbooks/hostsfile/attributes/default.rb new file mode 100644 index 0000000..d9175e6 --- /dev/null +++ b/cookbooks/hostsfile/attributes/default.rb @@ -0,0 +1,22 @@ +# +# Author:: Seth Vargo +# Cookbook:: hostsfile +# Attribute:: default +# +# Copyright 2012-2013, Seth Vargo +# Copyright 2012, CustomInk, LCC +# +# 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. +# + +default['hostsfile']['path'] = nil diff --git a/cookbooks/hostsfile/libraries/entry.rb b/cookbooks/hostsfile/libraries/entry.rb new file mode 100644 index 0000000..53b79db --- /dev/null +++ b/cookbooks/hostsfile/libraries/entry.rb @@ -0,0 +1,183 @@ +# +# Author:: Seth Vargo +# Cookbook:: hostsfile +# Library:: entry +# +# Copyright 2012-2013, Seth Vargo +# Copyright 2012, CustomInk, LCC +# +# 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. +# + +require 'ipaddr' + +# An object representation of a single line in a hostsfile. +# +# @author Seth Vargo +class Entry + class << self + # Creates a new Hostsfile::Entry object by parsing a text line. The + # `line` attribute will be in the following format: + # + # 1.2.3.4 hostname [alias[, alias[, alias]]] [# comment [@priority]] + # + # @param [String] line + # the line to parse + # @return [Entry] + # a new entry object + def parse(line) + entry, comment = extract_comment(line) + comment, priority = extract_priority(comment) + entries = extract_entries(entry) + + # Return nil if the line is empty + return nil if entries.nil? || entries.empty? + + # If /etc/hosts has a broken content we throw a descriptive exception + if entries[0].nil? + raise ArgumentError, "/etc/hosts has a line without IP address: #{line}" + end + if entries[1].nil? + raise ArgumentError, "/etc/hosts has a line without hostname: #{line}" + end + + return self.new( + ip_address: entries[0], + hostname: entries[1], + aliases: entries[2..-1], + comment: comment, + priority: priority, + ) + end + + private + + def extract_comment(line) + return nil if presence(line).nil? + line.split('#', 2).collect { |part| presence(part) } + end + + def extract_priority(comment) + return nil if comment.nil? + + if comment.include?('@') + comment.split('@', 2).collect { |part| presence(part) } + else + [comment, nil] + end + end + + def extract_entries(entry) + return nil if entry.nil? + entry.split(/\s+/).collect { |entry| presence(entry) }.compact + end + + def presence(string) + return nil if string.nil? + return nil if string.strip.empty? + string.strip + end + end + + # @return [String] + attr_accessor :ip_address, :hostname, :aliases, :comment, :priority + + # Creates a new entry from the given options. + # + # @param [Hash] options + # a list of options to create the entry with + # @option options [String] :ip_address + # the IP Address for this entry + # @option options [String] :hostname + # the hostname for this entry + # @option options [String, Array] :aliases + # a alias or array of aliases for this entry + # @option options[String] :comment + # an optional comment for this entry + # @option options [Fixnum] :priority + # the relative priority of this entry (compared to others) + # + # @raise [ArgumentError] + # if neither :ip_address nor :hostname are supplied + def initialize(options = {}) + if options[:ip_address].nil? || options[:hostname].nil? + raise ArgumentError, ':ip_address and :hostname are both required options' + end + + @ip_address = IPAddr.new(remove_ip_scope(options[:ip_address])) + @hostname = options[:hostname] + @aliases = [options[:aliases]].flatten.compact + @comment = options[:comment] + @priority = options[:priority] || calculated_priority + end + + # Set a the new priority for an entry. + # + # @param [Fixnum] new_priority + # the new priority to set + def priority=(new_priority) + @calculated_priority = false + @priority = new_priority + end + + # The line representation of this entry. + # + # @return [String] + # the string representation of this entry + def to_line + hosts = [hostname, aliases].flatten.join(' ') + + comments = "# #{comment.to_s}".strip + comments << " @#{priority}" unless priority.nil? || @calculated_priority + comments = comments.strip + comments = nil if comments == '#' + + [ip_address, hosts, comments].compact.join("\t").strip + end + + # Returns true if priority is calculated + # + # @return [Boolean] + # true if priority is calculated and false otherwise + def calculated_priority? + @calculated_priority + end + + private + + # Calculates the relative priority of this entry. + # + # @return [Fixnum] + # the relative priority of this item + def calculated_priority + @calculated_priority = true + + return 81 if ip_address == IPAddr.new('127.0.0.1') + return 80 if IPAddr.new('127.0.0.0/8').include?(ip_address) # local + return 60 if ip_address.ipv4? # ipv4 + return 20 if ip_address.ipv6? # ipv6 + return 00 + end + + # Removes the scopes pieces of the address, because reasons. + # + # @see https://bugs.ruby-lang.org/issues/8464 + # @see https://github.com/customink-webops/hostsfile/issues/51 + # + # @return [String, nil] + # + def remove_ip_scope(address) + return nil if address.nil? + address.to_s.sub(/%.*/, '') + end +end diff --git a/cookbooks/hostsfile/libraries/manipulator.rb b/cookbooks/hostsfile/libraries/manipulator.rb new file mode 100644 index 0000000..6afc047 --- /dev/null +++ b/cookbooks/hostsfile/libraries/manipulator.rb @@ -0,0 +1,293 @@ +# +# Author:: Seth Vargo +# Cookbook:: hostsfile +# Library:: manipulator +# +# Copyright 2012-2013, Seth Vargo +# Copyright 2012, CustomInk, LCC +# +# 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. +# + +require 'chef/application' +require 'digest/sha2' + +class Manipulator + attr_reader :node + attr_reader :entries + + # Create a new Manipulator object (aka an /etc/hosts manipulator). If a + # hostsfile is not found, an exception is raised. + # + # @param [Chef::node] node + # the current Chef node + # @return [Manipulator] + # a class designed to manipulate the node's /etc/hosts file + def initialize(node) + @node = node + + # Fail if no hostsfile is found + unless ::File.exists?(hostsfile_path) + raise RuntimeError, "No hostsfile exists at `#{hostsfile_path}'!" + end + + @entries = [] + collect_and_flatten(::File.readlines(hostsfile_path)) + end + + # Return a list of all IP Addresses for this hostsfile. + # + # @return [Array] + # the list of IP Addresses + def ip_addresses + @entries.collect do |entry| + entry.ip_address + end.compact || [] + end + + # Add a new record to the hostsfile. + # + # @param [Hash] options + # a list of options to create the entry with + # @option options [String] :ip_address + # the IP Address for this entry + # @option options [String] :hostname + # the hostname for this entry + # @option options [String, Array] :aliases + # a alias or array of aliases for this entry + # @option options[String] :comment + # an optional comment for this entry + # @option options [Fixnum] :priority + # the relative priority of this entry (compared to others) + def add(options = {}) + entry = Entry.new( + ip_address: options[:ip_address], + hostname: options[:hostname], + aliases: options[:aliases], + comment: options[:comment], + priority: options[:priority], + ) + + @entries << entry + remove_existing_hostnames(entry) if options[:unique] + end + + # Update an existing entry. This method will do nothing if the entry + # does not exist. + # + # @param (see #add) + def update(options = {}) + if entry = find_entry_by_ip_address(options[:ip_address]) + entry.hostname = options[:hostname] + entry.aliases = options[:aliases] + entry.comment = options[:comment] + entry.priority = options[:priority] + + remove_existing_hostnames(entry) if options[:unique] + end + end + + # Append content to an existing entry. This method will add a new entry + # if one does not already exist. + # + # @param (see #add) + def append(options = {}) + if entry = find_entry_by_ip_address(options[:ip_address]) + hosts = normalize(entry.hostname, entry.aliases, options[:hostname], options[:aliases]) + entry.hostname = hosts.shift + entry.aliases = hosts + + unless entry.comment && options[:comment] && entry.comment.include?(options[:comment]) + entry.comment = normalize(entry.comment, options[:comment]).join(', ') + end + + remove_existing_hostnames(entry) if options[:unique] + else + add(options) + end + end + + # Remove an entry by it's IP Address + # + # @param [String] ip_address + # the IP Address of the entry to remove + def remove(ip_address) + if entry = find_entry_by_ip_address(ip_address) + @entries.delete(entry) + end + end + + # Save the new hostsfile to the target machine. This method will only write the + # hostsfile if the current version has changed. In other words, it is convergent. + def save + file = Chef::Resource::File.new(hostsfile_path, node.run_context) + file.content(new_content) + file.run_action(:create) + end + + # Determine if the content of the hostfile has changed by comparing sha + # values of existing file and new content + # + # @return [Boolean] + def content_changed? + new_sha = Digest::SHA512.hexdigest(new_content) + new_sha != current_sha + end + + # Find an entry by the given IP Address. + # + # @param [String] ip_address + # the IP Address of the entry to find + # @return [Entry, nil] + # the corresponding entry object, or nil if it does not exist + def find_entry_by_ip_address(ip_address) + @entries.find do |entry| + !entry.ip_address.nil? && entry.ip_address == ip_address + end + end + + # Determine if the current hostsfile contains the given resource. This + # is really just a proxy to {find_resource_by_ip_address} / + # + # @param [Chef::Resource] resource + # + # @return [Boolean] + def contains?(resource) + !!find_entry_by_ip_address(resource.ip_address) + end + + private + + # The path to the current hostsfile. + # + # @return [String] + # the full path to the hostsfile, depending on the operating system + # can also be overriden in the node attributes + def hostsfile_path + return @hostsfile_path if @hostsfile_path + @hostsfile_path = node['hostsfile']['path'] || case node['platform_family'] + when 'windows' + "#{node['kernel']['os_info']['system_directory']}\\drivers\\etc\\hosts" + else + '/etc/hosts' + end + end + + # The header of the new hostsfile + # + # @return [Array] + # an array of header comments + def hostsfile_header + lines = [] + lines << '#' + lines << '# This file is managed by Chef, using the hostsfile cookbook.' + lines << '# Editing this file by hand is highly discouraged!' + lines << '#' + lines << '# Comments containing an @ sign should not be modified or else' + lines << '# hostsfile will be unable to guarantee relative priority in' + lines << '# future Chef runs!' + lines << '#' + lines << '' + end + + # The content that will be written to the hostfile + # + # @return [String] + # the full contents of the hostfile to be written + def new_content + entries = hostsfile_header + entries += unique_entries.map(&:to_line) + entries << '' + entries.join("\n") + end + + # The current sha of the system hostsfile. + # + # @return [String] + # the sha of the current hostsfile + def current_sha + @current_sha ||= Digest::SHA512.hexdigest(File.read(hostsfile_path)) + end + + # Normalize the given list of elements into a single array with no nil + # values and no duplicate values. + # + # @param [Object] things + # + # @return [Array] + # a normalized array of things + def normalize(*things) + things.flatten.compact.uniq + end + + # This is a crazy way of ensuring unique objects in an array using a Hash. + # + # @return [Array] + # the sorted list of entires that are unique + def unique_entries + entries = Hash[*@entries.map { |entry| [entry.ip_address, entry] }.flatten].values + entries.sort_by { |e| [-e.priority.to_i, e.hostname.to_s] } + end + + # Takes /etc/hosts file contents and builds a flattened entries + # array so that each IP address has only one line and multiple hostnames + # are flattened into a list of aliases. + # + # @param [Array] contents + # Array of lines from /etc/hosts file + def collect_and_flatten(contents) + contents.each do |line| + entry = Entry.parse(line) + next if entry.nil? + + append( + ip_address: entry.ip_address, + hostname: entry.hostname, + aliases: entry.aliases, + comment: entry.comment, + priority: !entry.calculated_priority? && entry.priority, + ) + end + end + + # Removes duplicate hostnames in other files ensuring they are unique + # + # @param [Entry] entry + # the entry to keep the hostname and aliases from + # + # @return [nil] + def remove_existing_hostnames(entry) + @entries.delete(entry) + changed_hostnames = [entry.hostname, entry.aliases].flatten.uniq + + @entries = @entries.collect do |entry| + entry.hostname = nil if changed_hostnames.include?(entry.hostname) + entry.aliases = entry.aliases - changed_hostnames + + if entry.hostname.nil? + if entry.aliases.empty? + nil + else + entry.hostname = entry.aliases.shift + entry + end + else + entry + end + end.compact + + @entries << entry + + nil + end +end diff --git a/cookbooks/hostsfile/libraries/matchers.rb b/cookbooks/hostsfile/libraries/matchers.rb new file mode 100644 index 0000000..788e427 --- /dev/null +++ b/cookbooks/hostsfile/libraries/matchers.rb @@ -0,0 +1,21 @@ +if defined?(ChefSpec) + def append_hostsfile_entry(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:hostsfile_entry, :append, resource_name) + end + + def create_hostsfile_entry(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:hostsfile_entry, :create, resource_name) + end + + def create_hostsfile_entry_if_missing(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:hostsfile_entry, :create_if_missing, resource_name) + end + + def remove_hostsfile_entry(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:hostsfile_entry, :remove, resource_name) + end + + def update_hostsfile_entry(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:hostsfile_entry, :update, resource_name) + end +end diff --git a/cookbooks/hostsfile/metadata.json b/cookbooks/hostsfile/metadata.json new file mode 100644 index 0000000..c2296cf --- /dev/null +++ b/cookbooks/hostsfile/metadata.json @@ -0,0 +1,29 @@ +{ + "name": "hostsfile", + "version": "2.4.5", + "description": "Provides an LWRP for managing the /etc/hosts file", + "long_description": "hostsfile LWRP\n==============\n[![Build Status](https://travis-ci.org/customink-webops/hostsfile.png?branch=master)](https://travis-ci.org/customink-webops/hostsfile)\n\n`hostsfile` provides an LWRP for managing your `/etc/hosts` (or Windows equivalent) file using Chef.\n\n\nRequirements\n------------\n- Chef 11 or higher\n- **Ruby 1.9.3 or higher**\n\n**Please stop opening Pull Requests to restore Ruby 1.8 support!** Any of the `1.x.y` series of this cookbook will work with Chef 10 and Ruby 1.8. You can use Opscode's [Omnibus installer](http://www.opscode.com/blog/2012/06/29/omnibus-chef-packaging/) to install Ruby 1.9+ and Seth Chisamore's [Vagrant Omnibus plugin](https://github.com/schisamo/vagrant-omnibus) to get Ruby 1.9+ on your Vagrant box.\n\n\nAttributes\n----------\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
AttributeDescriptionExampleDefault
ip_address(name attribute) the IP address for the entry1.2.3.4
hostname(required) the hostname associated with the entryexample.com
uniqueremove any existing entries that have the same hostnametruefalse
aliasesarray of aliases for the entry['www.example.com'][]
commenta comment to append to the end of the entry'interal DNS server'nil
prioritythe relative position of this entry20(varies, see **Priorities** section)
\n\n\nActions\n-------\n**Please note**: In `v0.1.2`, specifying a hostname or alias that existed in another automatically removed that hostname from the other entry before. In `v2.1.0`, the `unique` option was added to give the user case-by-case control of this behavior. For example, given an `/etc/hosts` file that contains:\n\n 1.2.3.4 example.com www.example.com\n\nwhen the Chef recipe below is converged:\n\n```ruby\nhostsfile_entry '2.3.4.5' do\n hostname 'www.example.com'\n unique true\nend\n```\n\nthen the `/etc/hosts` file will look like this:\n\n 1.2.3.4 example.com\n 2.3.4.5 www.example.com\n\nNot specifying the `unique` parameter will result in duplicate hostsfile entries.\n\n#### `create`\nCreates a new hosts file entry. If an entry already exists, it will be overwritten by this one.\n\n```ruby\nhostsfile_entry '1.2.3.4' do\n hostname 'example.com'\n action :create\nend\n```\n\nThis will create an entry like this:\n\n 1.2.3.4 example.com\n\n#### `create_if_missing`\nCreate a new hosts file entry, only if one does not already exist for the given IP address. If one exists, this does nothing.\n\n```ruby\nhostsfile_entry '1.2.3.4' do\n hostname 'example.com'\n action :create_if_missing\nend\n```\n\n#### `append`\nAppend a hostname or alias to an existing record. If the given IP address doesn't already exist in the hostsfile, this method behaves the same as create. Otherwise, it will append the additional hostname and aliases to the existing entry.\n\n 1.2.3.4 example.com www.example.com # Created by Chef\n\n```ruby\nhostsfile_entry '1.2.3.4' do\n hostname 'www2.example.com'\n aliases ['foo.com', 'foobar.com']\n comment 'Append by Recipe X'\n action :append\nend\n```\n\nwould yield:\n\n 1.2.3.4 example.com www.example.com www2.example.com foo.com foobar.com # Created by Chef, Appended by Recipe X\n\n\n#### `update`\nUpdates the given hosts file entry. Does nothing if the entry does not exist.\n\n```ruby\nhostsfile_entry '1.2.3.4' do\n hostname 'example.com'\n comment 'Update by Chef'\n action :update\nend\n```\n\nThis will create an entry like this:\n\n 1.2.3.4 example # Updated by Chef\n\n#### `remove`\nRemoves an entry from the hosts file. Does nothing if the entry does not\nexist.\n\n```ruby\nhostsfile_entry '1.2.3.4' do\n action :remove\nend\n```\n\nThis will remove the entry for `1.2.3.4`.\n\n\nUsage\n-----\nIf you're using [Berkshelf](http://berkshelf.com/), just add `hostsfile` to your `Berksfile`:\n\n```ruby\ncookbook 'hostsfile'\n```\n\nOtherwise, install the cookbook from the community site:\n\n knife cookbook site install hostsfile\n\nHave any other cookbooks *depend* on hostsfile by editing editing the `metadata.rb` for your cookbook.\n\n```ruby\n# metadata.rb\ndepends 'hostsfile'\n```\n\nNote that you can specify a custom path to your hosts file in the `['hostsfile']['path']` node attribute. Otherwise, it defaults to sensible paths depending on your OS.\n\n### Testing\nIf you are using [ChefSpec](https://github.com/sethvargo/chefspec) to unit test a cookbook that implements the `hostsfile_entry` LWRP, this cookbook packages customer matchers that you can use in your unit tests:\n\n- `append_hostsfile_entry`\n- `create_hostsfile_entry`\n- `create_hostsfile_entry_if_missing`\n- `remove_hostsfile_entry`\n- `update_hostsfile_entry`\n\nFor example:\n\n```ruby\nit 'creates a hostsfile entry for the DNS server' do\n expect(chef_run).to create_hostsfile_entry('1.2.3.4')\n .with_hostname('dns.example.com')\nend\n```\n\nPriority\n--------\nPriority is a relatively new addition to the cookbook. It gives you the ability to (somewhat) specify the relative order of entries. By default, the priority is calculated for you as follows:\n\n1. Local, loopback\n2. IPV4\n3. IPV6\n\nHowever, you can override it using the `priority` option.\n\n\nContributing\n------------\n1. Fork the project\n2. Create a feature branch corresponding to you change\n3. Commit and test thoroughly\n4. Create a Pull Request on github\n\n\nLicense & Authors\n-----------------\n- Author:: Seth Vargo (sethvargo@gmail.com)\n\n```text\nCopyright 2012-2013, Seth Vargo\nCopyright 2012, CustomInk, LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "maintainer": "Seth Vargo", + "maintainer_email": "sethvargo@gmail.com", + "license": "Apache 2.0", + "platforms": { + }, + "dependencies": { + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + } +} \ No newline at end of file diff --git a/cookbooks/hostsfile/metadata.rb b/cookbooks/hostsfile/metadata.rb new file mode 100644 index 0000000..5474e5d --- /dev/null +++ b/cookbooks/hostsfile/metadata.rb @@ -0,0 +1,7 @@ +name 'hostsfile' +maintainer 'Seth Vargo' +maintainer_email 'sethvargo@gmail.com' +license 'Apache 2.0' +description 'Provides an LWRP for managing the /etc/hosts file' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '2.4.5' diff --git a/cookbooks/hostsfile/providers/entry.rb b/cookbooks/hostsfile/providers/entry.rb new file mode 100644 index 0000000..dde469a --- /dev/null +++ b/cookbooks/hostsfile/providers/entry.rb @@ -0,0 +1,138 @@ +# +# Author:: Seth Vargo +# Cookbook:: hostsfile +# Provider:: entry +# +# Copyright 2012-2013, Seth Vargo +# Copyright 2012, CustomInk, LCC +# +# 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. +# + +# Support whyrun +def whyrun_supported? + true +end + +# Creates a new hosts file entry. If an entry already exists, it will be +# overwritten by this one. +action :create do + if hostsfile.contains?(new_resource) + Chef::Log.debug "#{new_resource} already exists - overwriting." + end + + hostsfile.add( + ip_address: new_resource.ip_address, + hostname: new_resource.hostname, + aliases: new_resource.aliases, + comment: new_resource.comment, + priority: new_resource.priority, + unique: new_resource.unique, + ) + + if hostsfile.content_changed? + converge_by("Create #{new_resource}") { hostsfile.save } + else + Chef::Log.info "#{new_resource} content already matches - nothing to do." + end +end + +# Create a new hosts file entry, only if one does not already exist for +# the given IP address. If one exists, this does nothing. +action :create_if_missing do + if hostsfile.contains?(new_resource) + Chef::Log.info "#{new_resource} already exists - skipping create_if_missing." + else + converge_by("Create #{new_resource} if missing") do + hostsfile.add( + ip_address: new_resource.ip_address, + hostname: new_resource.hostname, + aliases: new_resource.aliases, + comment: new_resource.comment, + priority: new_resource.priority, + unique: new_resource.unique, + ) + hostsfile.save + end + end +end + +# Appends the given data to an existing entry. If an entry does not exist, +# one will be created +action :append do + unless hostsfile.contains?(new_resource) + Chef::Log.info "#{new_resource} does not exist - creating instead." + end + + hostsfile.append( + ip_address: new_resource.ip_address, + hostname: new_resource.hostname, + aliases: new_resource.aliases, + comment: new_resource.comment, + priority: new_resource.priority, + unique: new_resource.unique, + ) + + if hostsfile.content_changed? + converge_by("Append #{new_resource}") { hostsfile.save } + else + Chef::Log.info "#{new_resource} content already matches - nothing to do." + end +end + +# Updates the given hosts file entry. Does nothing if the entry does not +# exist. +action :update do + if hostsfile.contains?(new_resource) + + hostsfile.update( + ip_address: new_resource.ip_address, + hostname: new_resource.hostname, + aliases: new_resource.aliases, + comment: new_resource.comment, + priority: new_resource.priority, + unique: new_resource.unique, + ) + + if hostsfile.content_changed? + converge_by("Update #{new_resource}") { hostsfile.save } + else + Chef::Log.info "#{new_resource} content already matches - nothing to do." + end + else + Chef::Log.info "#{new_resource} does not exist - skipping update." + end +end + +# Removes an entry from the hosts file. Does nothing if the entry does +# not exist. +action :remove do + if hostsfile.contains?(new_resource) + converge_by("Remove #{new_resource}") do + hostsfile.remove(new_resource.ip_address) + hostsfile.save + end + else + Chef::Log.info "#{new_resource} does not exist - skipping remove." + end +end + +private + +# The hostsfile object +# +# @return [Manipulator] +# the manipulator for this hostsfile +def hostsfile + @hostsfile ||= Manipulator.new(node) +end diff --git a/cookbooks/hostsfile/resources/entry.rb b/cookbooks/hostsfile/resources/entry.rb new file mode 100644 index 0000000..781e665 --- /dev/null +++ b/cookbooks/hostsfile/resources/entry.rb @@ -0,0 +1,36 @@ +# +# Author:: Seth Vargo +# Cookbook:: hostsfile +# Resource:: entry +# +# Copyright 2012-2013, Seth Vargo +# Copyright 2012, CustomInk, LCC +# +# 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. +# + +# List of all actions supported by the provider +actions :create, :create_if_missing, :append, :update, :remove + +# Make create the default action +default_action :create + +# Required attributes +attribute :ip_address, kind_of: String, name_attribute: true +attribute :hostname, kind_of: String + +# Optional attributes +attribute :aliases, kind_of: Array +attribute :comment, kind_of: String +attribute :priority, kind_of: Fixnum +attribute :unique, kind_of: [TrueClass, FalseClass] diff --git a/cookbooks/iis/CHANGELOG.md b/cookbooks/iis/CHANGELOG.md new file mode 100644 index 0000000..fc4a489 --- /dev/null +++ b/cookbooks/iis/CHANGELOG.md @@ -0,0 +1,183 @@ +v4.1.1 (2015-05-07) +------------------- +- Detects changes in the physical path of apps. +- Adds support for gMSA identity. +- Performing add on a site will now reconfigure it if necessary. +- Lock and unlock commands on configuration sections now use -commit:apphost. +- Fix issue where popeline_mode was ignored during configuration of a pool. + +v4.1.0 (2015-03-04) +------------------- +- Removed iis_pool attribute 'set_profile_environment' incompatible with < IIS-8. +- Added pester test framework. +- Condensed and fixed change-log to show public releases only. +- Fixed bug where bindings were being overwritten by :config. +- Code-cleanup and cosmetic fixes. + +v4.0.0 (2015-02-12) +------------------- +- [#91](https://github.com/chef-cookbooks/iis/pull/91) - bulk addition of new features + - Virtual Directory Support (allows virtual directories to be added to both websites and to webapplications under sites). + - section unlock and lock support (this is used to allow for the web.config of a site to define the authentication methods). + - fixed issue with :add on pool provider not running all config (this was a known issue and is now resolved). + - fixed issue with :config on all providers causing application pool recycles (every chef-client run). + - moved to better method for XML checking of previous settings to detect changes (changed all check to use xml searching with appcmd instead of the previous method [none]). +- Improved pool resource with many more apppool properties that can be set. +- Fixed bug with default attribute inheritance. +- New recipe to enable ASP.NET 4.5. +- Skeleton serverspec+test-kitchen framework. +- Added Berksfile, Gemfile and .kitchen.yml to assist developers. +- Fixed issue [#107] function is_new_or_empty was returning reverse results. +- Removed dependency on "chef-client", ">= 3.7.0". +- Changed all files to UTF-8 file format. +- Fixed issue with iis_pool not putting ApplicationPoolIdentity and username/password. +- [#98] Fixed issues with bindings. +- added backwards compatibility for chef-client < 12.x.x Chef::Util::PathHelper. + +v2.1.6 (2014-11-12) +------------------- +- [#78] Adds new_resource.updated_by_last_action calls + +v2.1.5 (2014-09-15) +------------------- +- [#68] Add win_friendly_path to all appcmd.exe /physicalPath arguments + +v2.1.4 (2014-09-13) +------------------- +- [#72] Adds chefspec matchers +- [#57] Fixes site_id not being updated on a :config action + +v2.1.2 (2014-04-23) +------------------- +- [COOK-4559] Remove invalid UTF-8 characters + + +v2.1.0 (2014-03-25) +------------------- +[COOK-4426] - feature order correction for proper installation +[COOK-4428] - Add IIS FTP Feature Installation + + +v2.0.4 (2014-03-18) +------------------- +- [COOK-4420] Corrected incorrect feature names for mod_security + + +v2.0.2 (2014-02-25) +------------------- +- [COOK-4108] - Add documentation for the 'bindings' attribute in 'iis_site' LWRP + + +v2.0.0 (2014-01-03) +------------------- +Major version bump + + +v1.6.6 +------ +Adding extra windows platform checks to helper library + + +v1.6.4 +------ +### Bug +- **[COOK-4138](https://tickets.chef.io/browse/COOK-4138)** - iis cookbook won't load on non-Windows platforms + + +v1.6.2 +------ +### Improvement +- **[COOK-3634](https://tickets.chef.io/browse/COOK-3634)** - provide ability to set app pool managedRuntimeVersion to "No Managed Code" + + +v1.6.0 +------ +### Improvement +- **[COOK-3922](https://tickets.chef.io/browse/COOK-3922)** - refactor IIS cookbook to not require WebPI + + +v1.5.6 +------ +### Improvement +- **[COOK-3770](https://tickets.chef.io/browse/COOK-3770)** - Add Enabled Protocols to IIS App Recipe + + +v1.5.4 +------ +### New Feature +- **[COOK-3675](https://tickets.chef.io/browse/COOK-3675)** - Add recipe for CGI module + +v1.5.2 +------ +### Bug +- **[COOK-3232](https://tickets.chef.io/browse/COOK-3232)** - Allow `iis_app` resource `:config` action with a virtual path + +v1.5.0 +------ +### Improvement + +- [COOK-2370]: add MVC2, escape `application_pool` and add options for + recycling +- [COOK-2694]: update iis documentation to show that Windows 2012 and + Windows 8 are supported + +### Bug + +- [COOK-2325]: `load_current_resource` does not load state of pool + correctly, always sets running to false +- [COOK-2526]: Installing IIS after .NET framework will leave + installation in non-working state +- [COOK-2596]: iis cookbook fails with indecipherable error if EULA + not accepted + +v1.4.0 +------ +* [COOK-2181] -Adding full module support to iis cookbook + +v1.3.6 +------ +* [COOK-2084] - Add support for additional options during site creation +* [COOK-2152] - Add recipe for IIS6 metabase compatibility + +v1.3.4 +------ +* [COOK-2050] - IIS cookbook does not have returns resource defined + +v1.3.2 +------ +* [COOK-1251] - Fix LWRP "NotImplementedError" + +v1.3.0 +------ +* [COOK-1301] - Add a recycle action to the iis_pool resource +* [COOK-1665] - app pool identity and new node[iis][component] attribute +* [COOK-1666] - Recipe to remove default site and app pool +* [COOK-1858] - Recipe misspelled + +v1.2.0 +------ +* [COOK-1061] - `iis_site` doesn't allow setting the pool +* [COOK-1078] - handle advanced bindings +* [COOK-1283] - typo on pool +* [COOK-1284] - install iis application initialization +* [COOK-1285] - allow multiple host_header, port and protocol +* [COOK-1286] - allow directly setting which app pool on site creation +* [COOK-1449] - iis pool regex returns true if similar site exists +* [COOK-1647] - mod_ApplicationInitialization isn't RC + +v1.1.0 +------ +* [COOK-1012] - support adding apps +* [COOK-1028] - support for config command +* [COOK-1041] - fix removal in app pools +* [COOK-835] - add app pool management +* [COOK-950] - documentation correction for version of IIS/OS + +v1.0.2 +------ +* Ruby 1.9 compat fixes +* ensure carriage returns are removed before applying regex + +v1.0.0 +------ +* [COOK-718] initial release diff --git a/cookbooks/iis/README.md b/cookbooks/iis/README.md new file mode 100644 index 0000000..36fcddf --- /dev/null +++ b/cookbooks/iis/README.md @@ -0,0 +1,458 @@ +Description +=========== + +Installs and configures Microsoft Internet Information Services (IIS) 7.0/7.5/8.0 + +Requirements +============ + +Platform +-------- + +* Windows Vista +* Windows 7 +* Windows 8 +* Windows Server 2008 (R1, R2) +* Windows Server 2012 +* Windows Server 2012R2 + +Windows 2003R2 is *not* supported because it lacks Add/Remove Features. + +Cookbooks +--------- + +* windows + +Attributes +========== + +* `node['iis']['home']` - IIS main home directory. default is `%WINDIR%\System32\inetsrv` +* `node['iis']['conf_dir']` - location where main IIS configs lives. default is `%WINDIR%\System32\inetsrv\config` +* `node['iis']['pubroot']` - . default is `%SYSTEMDRIVE%\inetpub` +* `node['iis']['docroot']` - IIS web site home directory. default is `%SYSTEMDRIVE%\inetpub\wwwroot` +* `node['iis']['log_dir']` - location of IIS logs. default is `%SYSTEMDRIVE%\inetpub\logs\LogFiles` +* `node['iis']['cache_dir']` - location of cached data. default is `%SYSTEMDRIVE%\inetpub\temp` + +Resource/Provider +================= + +iis_site +--------- + +Allows easy management of IIS virtual sites (ie vhosts). + +### Actions + +- `:add` - add a new virtual site +- `:config` - apply configuration to an existing virtual site +- `:delete` - delete an existing virtual site +- `:start` - start a virtual site +- `:stop` - stop a virtual site +- `:restart` - restart a virtual site + +### Attribute Parameters + +- `product_id` - name attribute. Specifies the ID of a product to install. +- `site_name` - name attribute. +- `site_id` - if not given IIS generates a unique ID for the site +- `path` - IIS will create a root application and a root virtual directory mapped to this specified local path +- `protocol` - http protocol type the site should respond to. valid values are :http, :https. default is :http +- `port` - port site will listen on. default is 80 +- `host_header` - host header (also known as domains or host names) the site should map to. default is all host headers +- `options` - additional options to configure the site +- `bindings` - Advanced options to configure the information required for requests to communicate with a Web site. See http://www.iis.net/configreference/system.applicationhost/sites/site/bindings/binding for parameter format. When binding is used, port protocol and host_header should not be used. +- `application_pool` - set the application pool of the site +- `options` - support for additional options -logDir, -limits, -ftpServer, etc... +- `log_directory` - specifies the logging directory, where the log file and logging-related support files are stored. +- `log_period` - specifies how often iis creates a new log file +- `log_truncsize` - specifies the maximum size of the log file (in bytes) after which to create a new log file. + +### Examples + +```ruby +# stop and delete the default site +iis_site 'Default Web Site' do + action [:stop, :delete] +end +``` + +```ruby +# create and start a new site that maps to +# the physical location C:\inetpub\wwwroot\testfu +iis_site 'Testfu Site' do + protocol :http + port 80 + path "#{node['iis']['docroot']}/testfu" + action [:add,:start] +end +``` + +```ruby +# do the same but map to testfu.chef.io domain +iis_site 'Testfu Site' do + protocol :http + port 80 + path "#{node['iis']['docroot']}/testfu" + host_header "testfu.chef.io" + action [:add,:start] +end +``` + +```ruby +# create and start a new site that maps to +# the physical C:\inetpub\wwwroot\testfu +# also adds bindings to http and https +# binding http to the ip address 10.12.0.136, +# the port 80, and the host header www.domain.com +# also binding https to any ip address, +# the port 443, and the host header www.domain.com +iis_site 'FooBar Site' do + bindings "http/10.12.0.136:80:www.domain.com,https/*:443:www.domain.com + path "#{node['iis']['docroot']}/testfu" + action [:add,:start] +end +``` + +iis_config +----------- +Runs a config command on your IIS instance. + +### Actions + +- `:config` - Runs the configuration command + +### Attribute Parameters + +- `cfg_cmd` - name attribute. What ever command you would pass in after "appcmd.exe set config" + +### Example + +```ruby +# Sets up logging +iis_config "/section:system.applicationHost/sites /siteDefaults.logfile.directory:\"D:\\logs\"" do + action :config +end +``` + +```ruby +# Loads an array of commands from the node +cfg_cmds = node['iis']['cfg_cmd'] +cfg_cmds.each do |cmd| + iis_config "#{cmd}" do + action :config + end +end +``` + +iis_pool +--------- +Creates an application pool in IIS. + +### Actions + +- `:add` - add a new application pool +- `:config` - apply configuration to an existing application pool +- `:delete` - delete an existing application pool +- `:start` - start a application pool +- `:stop` - stop a application pool +- `:restart` - restart a application pool +- `:recycle` - recycle an application pool + +### Attribute Parameters + +#### Root Items +- `pool_name` - name attribute. Specifies the name of the pool to create. +- `runtime_version` - specifies what .NET version of the runtime to use. +- `pipeline_mode` - specifies what pipeline mode to create the pool with, valid values are :Integrated or :Classic, the default is :Integrated +- `no_managed_code` - allow Unmanaged Code in setting up IIS app pools is shutting down. - default is true - optional + +#### Add Items +- `start_mode` - Specifies the startup type for the application pool - default :OnDemand (:OnDemand, :AlwaysRunning) - optional +- `auto_start` - When true, indicates to the World Wide Web Publishing Service (W3SVC) that the application pool should be automatically started when it is created or when IIS is started. - boolean: default true - optional +- `queue_length` - Indicates to HTTP.sys how many requests to queue for an application pool before rejecting future requests. - default is 1000 - optional +- `thirty_two_bit` - set the pool to run in 32 bit mode, valid values are true or false, default is false - optional + +#### Process Model Items +- `max_proc` - specifies the number of worker processes associated with the pool. +- `load_user_profile` - This property is used only when a service starts in a named user account. - Default is false - optional +- `pool_identity` - the account identity that they app pool will run as, valid values are :SpecificUser, :NetworkService, :LocalService, :LocalSystem, :ApplicationPoolIdentity +- `pool_username` - username for the identity for the application pool +- `pool_password` password for the identity for the application pool is started. Default is true - optional +- `logon_type` - Specifies the logon type for the process identity. (For additional information about [logon types](http://msdn.microsoft.com/en-us/library/aa378184%28VS.85%29.aspx), see the LogonUser Function topic on Microsoft's MSDN Web site.) - Available [:LogonBatch, :LogonService] - default is :LogonBatch - optional +- `manual_group_membership` - Specifies whether the IIS_IUSRS group Security Identifier (SID) is added to the worker process token. When false, IIS automatically uses an application pool identity as though it were a member of the built-in IIS_IUSRS group, which has access to necessary file and system resources. When true, an application pool identity must be explicitly added to all resources that a worker process requires at runtime. - default is false - optional +- `idle_timeout` - Specifies how long (in minutes) a worker process should run idle if no new requests are received and the worker process is not processing requests. After the allocated time passes, the worker process should request that it be shut down by the WWW service. - default is '00:20:00' - optional +- `shutdown_time_limit` - Specifies the time that the W3SVC service waits after it initiated a recycle. If the worker process does not shut down within the shutdownTimeLimit, it will be terminated by the W3SVC service. - default is '00:01:30' - optional +- `startup_time_limit` - Specifies the time that IIS waits for an application pool to start. If the application pool does not startup within the startupTimeLimit, the worker process is terminated and the rapid-fail protection count is incremented. - default is '00:01:30' - optional +- `pinging_enabled` - Specifies whether pinging is enabled for the worker process. - default is true - optional +- `ping_interval` - Specifies the time between health-monitoring pings that the WWW service sends to a worker process - default is '00:00:30' - optional +- `ping_response_time` - Specifies the time that a worker process is given to respond to a health-monitoring ping. After the time limit is exceeded, the WWW service terminates the worker process - default is '00:01:30' - optional + +#### Recycling Items +- `disallow_rotation_on_config_change` - The DisallowRotationOnConfigChange property specifies whether or not the World Wide Web Publishing Service (WWW Service) should rotate worker processes in an application pool when the configuration has changed. - Default is false - optional +- `disallow_overlapping_rotation` - Specifies whether the WWW Service should start another worker process to replace the existing worker process while that process +- `recycle_after_time` - specifies a pool to recycle at regular time intervals, d.hh:mm:ss, d optional +- `recycle_at_time` - schedule a pool to recycle at a specific time, d.hh:mm:ss, d optional +- `private_mem` - specifies the amount of private memory (in kilobytes) after which you want the pool to recycle + +#### Failure Items +- `load_balancer_capabilities` - Specifies behavior when a worker process cannot be started, such as when the request queue is full or an application pool is in rapid-fail protection. - default is :HttpLevel - optional +- `orphan_worker_process` - Specifies whether to assign a worker process to an orphan state instead of terminating it when an application pool fails. - default is false - optional +- `orphan_action_exe` - Specifies an executable to run when the WWW service orphans a worker process (if the orphanWorkerProcess attribute is set to true). You can use the orphanActionParams attribute to send parameters to the executable. - optional +- `orphan_action_params` - Indicates command-line parameters for the executable named by the orphanActionExe attribute. To specify the process ID of the orphaned process, use %1%. - optional +- `rapid_fail_protection` - Setting to true instructs the WWW service to remove from service all applications that are in an application pool - default is true - optional +- `rapid_fail_protection_interval` - Specifies the number of minutes before the failure count for a process is reset. - default is '00:05:00' - optional +- `rapid_fail_protection_max_crashes` - Specifies the maximum number of failures that are allowed within the number of minutes specified by the rapidFailProtectionInterval attribute. - default is 5 - optional +- `auto_shutdown_exe` - Specifies an executable to run when the WWW service shuts down an application pool. - optional +- `auto_shutdown_params` - Specifies command-line parameters for the executable that is specified in the autoShutdownExe attribute. - optional + +#### CPU Items +- `cpu_action` - Configures the action that IIS takes when a worker process exceeds its configured CPU limit. The action attribute is configured on a per-application pool basis. - Available options [:NoAction, :KillW3wp, :Throttle, :ThrottleUnderLoad] - default is :NoAction - optional +- `cpu_limit` - Configures the maximum percentage of CPU time (in 1/1000ths of one percent) that the worker processes in an application pool are allowed to consume over a period of time as indicated by the resetInterval attribute. If the limit set by the limit attribute is exceeded, an event is written to the event log and an optional set of events can be triggered. These optional events are determined by the action attribute. - default is 0 - optional +- `cpu_reset_interval` - Specifies the reset period (in minutes) for CPU monitoring and throttling limits on an application pool. When the number of minutes elapsed since the last process accounting reset equals the number specified by this property, IIS resets the CPU timers for both the logging and limit intervals. - default is '00:05:00' - optional +- `cpu_smp_affinitized` - Specifies whether a particular worker process assigned to an application pool should also be assigned to a given CPU. - default is false - optional +- `smp_processor_affinity_mask` - Specifies the hexadecimal processor mask for multi-processor computers, which indicates to which CPU the worker processes in an application pool should be bound. Before this property takes effect, the smpAffinitized attribute must be set to true for the application pool. - default is 4294967295 - optional +- `smp_processor_affinity_mask_2` - Specifies the high-order DWORD hexadecimal processor mask for 64-bit multi-processor computers, which indicates to which CPU the worker processes in an application pool should be bound. Before this property takes effect, the smpAffinitized attribute must be set to true for the application pool. - default is 4294967295 - optional + +### Example + +```ruby +# creates a new app pool +iis_pool 'myAppPool_v1_1' do + runtime_version "2.0" + pipeline_mode :Classic + action :add +end +``` + +iis_app +-------- + +Creates an application in IIS. + +### Actions + +- `:add` - add a new application pool +- `:delete` - delete an existing application pool + +### Attribute Parameters + +- `site_name` - name attribute. The name of the site to add this app to +- `path` -The virtual path for this application +- `application_pool` - The pool this application belongs to +- `physical_path` - The physical path where this app resides. +- `enabled_protocols` - The enabled protocols that this app provides (http, https, net.pipe, net.tcp, etc) + +### Example + +```ruby +# creates a new app +iis_app "myApp" do + path "/v1_1" + application_pool "myAppPool_v1_1" + physical_path "#{node['iis']['docroot']}/testfu/v1_1" + enabled_protocols "http,net.pipe" + action :add +end +``` + +iis_vdir +--------- + +Allows easy management of IIS virtual directories (i.e. vdirs). + +### Actions + +- :add: - add a new virtual directory +- :delete: - delete an existing virtual directory +- :config: - configure a virtual directory + +### Attribute Parameters + +- `application_name`: name attribute. Specifies the name of the application attribute. This is the name of the website or application you are adding it to. +- `path`: The virtual directory path on the site. +- `physical_path`: The physical path of the virtual directory on the disk. +- `username`: (optional) The username required to logon to the physical_path. If set to "" will clear username and password. +- `password`: (optional) The password required to logon to the physical_path +- `logon_method`: (optional, default: :ClearText) The method used to logon (:Interactive, :Batch, :Network, :ClearText). For more information on these types, see "LogonUser Function", Read more at [MSDN](http://msdn2.microsoft.com/en-us/library/aa378184.aspx) +- `allow_sub_dir_config`: (optional, default: true) Boolean that specifies whether or not the Web server will look for configuration files located in the subdirectories of this virtual directory. Setting this to false can improve performance on servers with very large numbers of web.config files, but doing so prevents IIS configuration from being read in subdirectories. + +### Examples + +```ruby +# add a virtual directory to default application +iis_vdir 'Default Web Site/' do + action :add + path '/Content/Test' + physical_path 'C:\wwwroot\shared\test' +end +``` + +```ruby +# add a virtual directory to an application under a site +iis_vdir 'Default Web Site/my application' do + action :add + path '/Content/Test' + physical_path 'C:\wwwroot\shared\test' +end +``` + +```ruby +# adds a virtual directory to default application which points to a smb share. (Remember to escape the "\"'s) +iis_vdir 'Default Web Site/' do + action :add + path '/Content/Test' + physical_path '\\\\sharename\\sharefolder\\1' +end +``` + +```ruby +# configure a virtual directory to have a username and password +iis_vdir 'Default Web Site/' do + action :config + path '/Content/Test' + username 'domain\myspecialuser' + password 'myspecialpassword' +end +``` + +```ruby +# delete a virtual directory from the default application +iis_vdir 'Default Web Site/' do + action :delete + path '/Content/Test' +end +``` + +iis_section +--------- + +Allows for the locking/unlocking of sections ([listed here](http://www.iis.net/configreference) or via the command `appcmd list config \"\" /config:* /xml`) + +This is valuable to allow the `web.config` of an individual application/website control it's own settings. + +### Actions + +- `:lock`: - locks the `section` passed +- `:unlock`: - unlocks the `section` passed + +### Attribute Parameters + +- `section`: The name of the section to lock. +- `returns`: The result of the `shell_out` command. + +### Examples + +```ruby +# Sets the IIS global windows authentication to be locked globally +iis_section 'locks global configuration of windows auth' do + section 'system.webServer/security/authentication/windowsAuthentication' + action :lock +end +``` + +```ruby +# Sets the IIS global Basic authentication to be locked globally +iis_section 'locks global configuration of Basic auth' do + section 'system.webServer/security/authentication/basicAuthentication' + action :lock +end +``` + +```ruby +# Sets the IIS global windows authentication to be unlocked globally +iis_section 'unlocked web.config globally for windows auth' do + action :unlock + section 'system.webServer/security/authentication/windowsAuthentication' +end +``` + +```ruby +# Sets the IIS global Basic authentication to be unlocked globally +iis_section 'unlocked web.config globally for Basic auth' do + action :unlock + section 'system.webServer/security/authentication/basicAuthentication' +end +``` + +iis_module +-------- + +Manages modules globally or on a per site basis. + +### Actions + +- `:add` - add a new module +- `:delete` - delete a module + +### Attribute Parameters + +- `module_name` - The name of the module to add or delete +- `type` - The type of module +- `precondition` - precondition for module +- `application` - The application or site to add the module to + +### Example + +```ruby +# Adds a module called "My 3rd Party Module" to mySite/ +iis_module "My 3rd Party Module" do + application "mySite/" + precondition "bitness64" + action :add +end +``` + +```ruby +# Adds a module called "MyModule" to all IIS sites on the server +iis_module "MyModule" +``` + + +Usage +===== + +default +------- + +Installs and configures IIS 7.0/7.5/8.0 using the default configuration. + +mod_* +----- + +This cookbook also contains recipes for installing individual IIS modules (extensions). These recipes can be included in a node's run_list to build the minimal desired custom IIS installation. + +* `mod_aspnet` - installs ASP.NET runtime components +* `mod_aspnet45` - installs ASP.NET 4.5 runtime components +* `mod_auth_basic` - installs Basic Authentication support +* `mod_auth_windows` - installs Windows Authentication (authenticate clients by using NTLM or Kerberos) support +* `mod_compress_dynamic` - installs dynamic content compression support. *PLEASE NOTE* - enabling dynamic compression always gives you more efficient use of bandwidth, but if your server's processor utilization is already very high, the CPU load imposed by dynamic compression might make your site perform more slowly. +* `mod_compress_static` - installs static content compression support +* `mod_iis6_metabase_compat` - installs IIS 6 Metabase Compatibility component. +* `mod_isapi` - installs ISAPI (Internet Server Application Programming Interface) extension and filter support. +* `mod_logging` - installs and enables HTTP Logging (logging of Web site activity), Logging Tools (logging tools and scripts) and Custom Logging (log any of the HTTP request/response headers, IIS server variables, and client-side fields with simple configuration) support +* `mod_management` - installs Web server Management Console which supports management of local and remote Web servers +* `mod_security` - installs URL Authorization (Authorizes client access to the URLs that comprise a Web application), Request Filtering (configures rules to block selected client requests) and IP Security (allows or denies content access based on IP address or domain name) support. +* `mod_tracing` - installs support for tracing ASP.NET applications and failed requests. + +Note: Not every possible IIS module has a corresponding recipe. The foregoing recipes are included for convenience, but users may also place additional IIS modules that are installable as Windows features into the ``node['iis']['components']`` array. + +License and Author +================== + +* Author:: Seth Chisamore () +* Author:: Julian Dunn () +* Author:: Justin Schuhmann () + +Copyright:: 2011-2015, Chef Software, Inc. + +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/cookbooks/iis/attributes/default.rb b/cookbooks/iis/attributes/default.rb new file mode 100644 index 0000000..0635ec4 --- /dev/null +++ b/cookbooks/iis/attributes/default.rb @@ -0,0 +1,27 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Attribute:: default +# +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# +# 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. +# + +default['iis']['home'] = "#{ENV['WINDIR']}\\System32\\inetsrv" +default['iis']['conf_dir'] = "#{ENV['WINDIR']}\\System32\\inetsrv\\config" +default['iis']['pubroot'] = "#{ENV['SYSTEMDRIVE']}\\inetpub" +default['iis']['docroot'] = "#{ENV['SYSTEMDRIVE']}\\inetpub\\wwwroot" +default['iis']['log_dir'] = "#{ENV['SYSTEMDRIVE']}\\inetpub\\logs\\LogFiles" +default['iis']['cache_dir'] = "#{ENV['SYSTEMDRIVE']}\\inetpub\\temp" +default['iis']['components'] = [] diff --git a/cookbooks/iis/libraries/helper.rb b/cookbooks/iis/libraries/helper.rb new file mode 100644 index 0000000..b036774 --- /dev/null +++ b/cookbooks/iis/libraries/helper.rb @@ -0,0 +1,87 @@ +# +# Cookbook Name:: iis +# Library:: helper +# +# Author:: Julian C. Dunn +# +# Copyright 2013, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +module Opscode + module IIS + # Contains functions that are used throughout this cookbook + module Helper + if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + require 'chef/win32/version' + end + + require 'rexml/document' + include REXML + include Windows::Helper + + def self.older_than_windows2008r2? + if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + win_version = Chef::ReservedNames::Win32::Version.new + win_version.windows_server_2008? || + win_version.windows_vista? || + win_version.windows_server_2003_r2? || + win_version.windows_home_server? || + win_version.windows_server_2003? || + win_version.windows_xp? || + win_version.windows_2000? + end + end + + def self.older_than_windows2012? + if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + win_version = Chef::ReservedNames::Win32::Version.new + win_version.windows_7? || + win_version.windows_server_2008_r2? || + win_version.windows_server_2008? || + win_version.windows_vista? || + win_version.windows_server_2003_r2? || + win_version.windows_home_server? || + win_version.windows_server_2003? || + win_version.windows_xp? || + win_version.windows_2000? + end + end + + def windows_cleanpath(path) + if !defined?(Chef::Util::PathHelper.cleanpath).nil? + path = Chef::Util::PathHelper.cleanpath(path) + else + path = win_friendly_path(path) + end + # Remove any trailing slashes to prevent them from accidentally escaping any quotes. + path.chomp('/').chomp('\\') + end + + def new_value?(document, xpath, value_to_check) + XPath.first(document, xpath).to_s != value_to_check.to_s + end + + def new_or_empty_value?(document, xpath, value_to_check) + value_to_check.to_s != '' && new_value?(document, xpath, value_to_check) + end + + def appcmd(node) + @appcmd ||= begin + "#{node['iis']['home']}\\appcmd.exe" + end + end + end + end +end diff --git a/cookbooks/iis/libraries/matcher.rb b/cookbooks/iis/libraries/matcher.rb new file mode 100644 index 0000000..d146d21 --- /dev/null +++ b/cookbooks/iis/libraries/matcher.rb @@ -0,0 +1,68 @@ +if defined?(ChefSpec) + + def config_iis_config(command) + ChefSpec::Matchers::ResourceMatcher.new(:iis_config, :config, command) + end + + [:config, :add, :delete].each do |action| + self.class.send(:define_method, "#{action}_iis_app", proc do |app_name| + ChefSpec::Matchers::ResourceMatcher.new(:iis_app, action, app_name) + end + ) + end + + [:config].each do |action| + self.class.send(:define_method, "#{action}_iis_lock", proc do |section| + ChefSpec::Matchers::ResourceMatcher.new(:iis_lock, action, section) + end + ) + end + + [:add, :delete].each do |action| + self.class.send(:define_method, "#{action}_iis_module", proc do |module_name| + ChefSpec::Matchers::ResourceMatcher.new(:iis_module, action, module_name) + end + ) + end + + [:add, :config, :delete, :start, :stop, :restart, :recycle].each do |action| + self.class.send(:define_method, "#{action}_iis_pool", proc do |pool_name| + ChefSpec::Matchers::ResourceMatcher.new(:iis_pool, action, pool_name) + end + ) + end + + [:add, :delete, :start, :stop, :restart, :config].each do |action| + self.class.send(:define_method, "#{action}_iis_site", proc do |site_name| + ChefSpec::Matchers::ResourceMatcher.new(:iis_site, action, site_name) + end + ) + end + + [:config].each do |action| + self.class.send(:define_method, "#{action}_iis_unlock", proc do |section| + ChefSpec::Matchers::ResourceMatcher.new(:iis_unlock, action, section) + end + ) + end + + [:add, :config, :delete].each do |action| + self.class.send(:define_method, "#{action}_iis_vdir", proc do |section| + ChefSpec::Matchers::ResourceMatcher.new(:iis_vdir, action, section) + end + ) + end + + define_method = (Gem.loaded_specs['chefspec'].version < Gem::Version.new('4.1.0')) ? + ChefSpec::Runner.method(:define_runner_method) : + ChefSpec.method(:define_matcher) + + define_method.call :iis_app + define_method.call :iis_config + define_method.call :iis_lock + define_method.call :iis_module + define_method.call :iis_pool + define_method.call :iis_site + define_method.call :iis_unlock + define_method.call :iis_vdir +end diff --git a/cookbooks/iis/metadata.json b/cookbooks/iis/metadata.json new file mode 100644 index 0000000..10fc6e8 --- /dev/null +++ b/cookbooks/iis/metadata.json @@ -0,0 +1 @@ +{"name":"iis","version":"4.1.1","description":"Installs/Configures Microsoft Internet Information Services","long_description":"Description\n===========\n\nInstalls and configures Microsoft Internet Information Services (IIS) 7.0/7.5/8.0\n\nRequirements\n============\n\nPlatform\n--------\n\n* Windows Vista\n* Windows 7\n* Windows 8\n* Windows Server 2008 (R1, R2)\n* Windows Server 2012\n* Windows Server 2012R2\n\nWindows 2003R2 is *not* supported because it lacks Add/Remove Features.\n\nCookbooks\n---------\n\n* windows\n\nAttributes\n==========\n\n* `node['iis']['home']` - IIS main home directory. default is `%WINDIR%\\System32\\inetsrv`\n* `node['iis']['conf_dir']` - location where main IIS configs lives. default is `%WINDIR%\\System32\\inetsrv\\config`\n* `node['iis']['pubroot']` - . default is `%SYSTEMDRIVE%\\inetpub`\n* `node['iis']['docroot']` - IIS web site home directory. default is `%SYSTEMDRIVE%\\inetpub\\wwwroot`\n* `node['iis']['log_dir']` - location of IIS logs. default is `%SYSTEMDRIVE%\\inetpub\\logs\\LogFiles`\n* `node['iis']['cache_dir']` - location of cached data. default is `%SYSTEMDRIVE%\\inetpub\\temp`\n\nResource/Provider\n=================\n\niis_site\n---------\n\nAllows easy management of IIS virtual sites (ie vhosts).\n\n### Actions\n\n- `:add` - add a new virtual site\n- `:config` - apply configuration to an existing virtual site\n- `:delete` - delete an existing virtual site\n- `:start` - start a virtual site\n- `:stop` - stop a virtual site\n- `:restart` - restart a virtual site\n\n### Attribute Parameters\n\n- `product_id` - name attribute. Specifies the ID of a product to install.\n- `site_name` - name attribute.\n- `site_id` - if not given IIS generates a unique ID for the site\n- `path` - IIS will create a root application and a root virtual directory mapped to this specified local path\n- `protocol` - http protocol type the site should respond to. valid values are :http, :https. default is :http\n- `port` - port site will listen on. default is 80\n- `host_header` - host header (also known as domains or host names) the site should map to. default is all host headers\n- `options` - additional options to configure the site\n- `bindings` - Advanced options to configure the information required for requests to communicate with a Web site. See http://www.iis.net/configreference/system.applicationhost/sites/site/bindings/binding for parameter format. When binding is used, port protocol and host_header should not be used.\n- `application_pool` - set the application pool of the site\n- `options` - support for additional options -logDir, -limits, -ftpServer, etc...\n- `log_directory` - specifies the logging directory, where the log file and logging-related support files are stored.\n- `log_period` - specifies how often iis creates a new log file\n- `log_truncsize` - specifies the maximum size of the log file (in bytes) after which to create a new log file.\n\n### Examples\n\n```ruby\n# stop and delete the default site\niis_site 'Default Web Site' do\n action [:stop, :delete]\nend\n```\n\n```ruby\n# create and start a new site that maps to\n# the physical location C:\\inetpub\\wwwroot\\testfu\niis_site 'Testfu Site' do\n protocol :http\n port 80\n path \"#{node['iis']['docroot']}/testfu\"\n action [:add,:start]\nend\n```\n\n```ruby\n# do the same but map to testfu.chef.io domain\niis_site 'Testfu Site' do\n protocol :http\n port 80\n path \"#{node['iis']['docroot']}/testfu\"\n host_header \"testfu.chef.io\"\n action [:add,:start]\nend\n```\n\n```ruby\n# create and start a new site that maps to\n# the physical C:\\inetpub\\wwwroot\\testfu\n# also adds bindings to http and https\n# binding http to the ip address 10.12.0.136,\n# the port 80, and the host header www.domain.com\n# also binding https to any ip address,\n# the port 443, and the host header www.domain.com\niis_site 'FooBar Site' do\n bindings \"http/10.12.0.136:80:www.domain.com,https/*:443:www.domain.com\n path \"#{node['iis']['docroot']}/testfu\"\n action [:add,:start]\nend\n```\n\niis_config\n-----------\nRuns a config command on your IIS instance.\n\n### Actions\n\n- `:config` - Runs the configuration command\n\n### Attribute Parameters\n\n- `cfg_cmd` - name attribute. What ever command you would pass in after \"appcmd.exe set config\"\n\n### Example\n\n```ruby\n# Sets up logging\niis_config \"/section:system.applicationHost/sites /siteDefaults.logfile.directory:\\\"D:\\\\logs\\\"\" do\n action :config\nend\n```\n\n```ruby\n# Loads an array of commands from the node\ncfg_cmds = node['iis']['cfg_cmd']\ncfg_cmds.each do |cmd|\n iis_config \"#{cmd}\" do\n action :config\n end\nend\n```\n\niis_pool\n---------\nCreates an application pool in IIS.\n\n### Actions\n\n- `:add` - add a new application pool\n- `:config` - apply configuration to an existing application pool\n- `:delete` - delete an existing application pool\n- `:start` - start a application pool\n- `:stop` - stop a application pool\n- `:restart` - restart a application pool\n- `:recycle` - recycle an application pool\n\n### Attribute Parameters\n\n#### Root Items\n- `pool_name` - name attribute. Specifies the name of the pool to create.\n- `runtime_version` - specifies what .NET version of the runtime to use.\n- `pipeline_mode` - specifies what pipeline mode to create the pool with, valid values are :Integrated or :Classic, the default is :Integrated\n- `no_managed_code` - allow Unmanaged Code in setting up IIS app pools is shutting down. - default is true - optional\n\n#### Add Items\n- `start_mode` - Specifies the startup type for the application pool - default :OnDemand (:OnDemand, :AlwaysRunning) - optional\n- `auto_start` - When true, indicates to the World Wide Web Publishing Service (W3SVC) that the application pool should be automatically started when it is created or when IIS is started. - boolean: default true - optional\n- `queue_length` - Indicates to HTTP.sys how many requests to queue for an application pool before rejecting future requests. - default is 1000 - optional\n- `thirty_two_bit` - set the pool to run in 32 bit mode, valid values are true or false, default is false - optional\n\n#### Process Model Items\n- `max_proc` - specifies the number of worker processes associated with the pool.\n- `load_user_profile` - This property is used only when a service starts in a named user account. - Default is false - optional\n- `pool_identity` - the account identity that they app pool will run as, valid values are :SpecificUser, :NetworkService, :LocalService, :LocalSystem, :ApplicationPoolIdentity\n- `pool_username` - username for the identity for the application pool\n- `pool_password` password for the identity for the application pool is started. Default is true - optional\n- `logon_type` - Specifies the logon type for the process identity. (For additional information about [logon types](http://msdn.microsoft.com/en-us/library/aa378184%28VS.85%29.aspx), see the LogonUser Function topic on Microsoft's MSDN Web site.) - Available [:LogonBatch, :LogonService] - default is :LogonBatch - optional\n- `manual_group_membership` - Specifies whether the IIS_IUSRS group Security Identifier (SID) is added to the worker process token. When false, IIS automatically uses an application pool identity as though it were a member of the built-in IIS_IUSRS group, which has access to necessary file and system resources. When true, an application pool identity must be explicitly added to all resources that a worker process requires at runtime. - default is false - optional\n- `idle_timeout` - Specifies how long (in minutes) a worker process should run idle if no new requests are received and the worker process is not processing requests. After the allocated time passes, the worker process should request that it be shut down by the WWW service. - default is '00:20:00' - optional\n- `shutdown_time_limit` - Specifies the time that the W3SVC service waits after it initiated a recycle. If the worker process does not shut down within the shutdownTimeLimit, it will be terminated by the W3SVC service. - default is '00:01:30' - optional\n- `startup_time_limit` - Specifies the time that IIS waits for an application pool to start. If the application pool does not startup within the startupTimeLimit, the worker process is terminated and the rapid-fail protection count is incremented. - default is '00:01:30' - optional\n- `pinging_enabled` - Specifies whether pinging is enabled for the worker process. - default is true - optional\n- `ping_interval` - Specifies the time between health-monitoring pings that the WWW service sends to a worker process - default is '00:00:30' - optional\n- `ping_response_time` - Specifies the time that a worker process is given to respond to a health-monitoring ping. After the time limit is exceeded, the WWW service terminates the worker process - default is '00:01:30' - optional\n\n#### Recycling Items\n- `disallow_rotation_on_config_change` - The DisallowRotationOnConfigChange property specifies whether or not the World Wide Web Publishing Service (WWW Service) should rotate worker processes in an application pool when the configuration has changed. - Default is false - optional\n- `disallow_overlapping_rotation` - Specifies whether the WWW Service should start another worker process to replace the existing worker process while that process\n- `recycle_after_time` - specifies a pool to recycle at regular time intervals, d.hh:mm:ss, d optional\n- `recycle_at_time` - schedule a pool to recycle at a specific time, d.hh:mm:ss, d optional\n- `private_mem` - specifies the amount of private memory (in kilobytes) after which you want the pool to recycle\n\n#### Failure Items\n- `load_balancer_capabilities` - Specifies behavior when a worker process cannot be started, such as when the request queue is full or an application pool is in rapid-fail protection. - default is :HttpLevel - optional\n- `orphan_worker_process` - Specifies whether to assign a worker process to an orphan state instead of terminating it when an application pool fails. - default is false - optional\n- `orphan_action_exe` - Specifies an executable to run when the WWW service orphans a worker process (if the orphanWorkerProcess attribute is set to true). You can use the orphanActionParams attribute to send parameters to the executable. - optional\n- `orphan_action_params` - Indicates command-line parameters for the executable named by the orphanActionExe attribute. To specify the process ID of the orphaned process, use %1%. - optional\n- `rapid_fail_protection` - Setting to true instructs the WWW service to remove from service all applications that are in an application pool - default is true - optional\n- `rapid_fail_protection_interval` - Specifies the number of minutes before the failure count for a process is reset. - default is '00:05:00' - optional\n- `rapid_fail_protection_max_crashes` - Specifies the maximum number of failures that are allowed within the number of minutes specified by the rapidFailProtectionInterval attribute. - default is 5 - optional\n- `auto_shutdown_exe` - Specifies an executable to run when the WWW service shuts down an application pool. - optional\n- `auto_shutdown_params` - Specifies command-line parameters for the executable that is specified in the autoShutdownExe attribute. - optional\n\n#### CPU Items\n- `cpu_action` - Configures the action that IIS takes when a worker process exceeds its configured CPU limit. The action attribute is configured on a per-application pool basis. - Available options [:NoAction, :KillW3wp, :Throttle, :ThrottleUnderLoad] - default is :NoAction - optional\n- `cpu_limit` - Configures the maximum percentage of CPU time (in 1/1000ths of one percent) that the worker processes in an application pool are allowed to consume over a period of time as indicated by the resetInterval attribute. If the limit set by the limit attribute is exceeded, an event is written to the event log and an optional set of events can be triggered. These optional events are determined by the action attribute. - default is 0 - optional\n- `cpu_reset_interval` - Specifies the reset period (in minutes) for CPU monitoring and throttling limits on an application pool. When the number of minutes elapsed since the last process accounting reset equals the number specified by this property, IIS resets the CPU timers for both the logging and limit intervals. - default is '00:05:00' - optional\n- `cpu_smp_affinitized` - Specifies whether a particular worker process assigned to an application pool should also be assigned to a given CPU. - default is false - optional\n- `smp_processor_affinity_mask` - Specifies the hexadecimal processor mask for multi-processor computers, which indicates to which CPU the worker processes in an application pool should be bound. Before this property takes effect, the smpAffinitized attribute must be set to true for the application pool. - default is 4294967295 - optional\n- `smp_processor_affinity_mask_2` - Specifies the high-order DWORD hexadecimal processor mask for 64-bit multi-processor computers, which indicates to which CPU the worker processes in an application pool should be bound. Before this property takes effect, the smpAffinitized attribute must be set to true for the application pool. - default is 4294967295 - optional\n\n### Example\n\n```ruby\n# creates a new app pool\niis_pool 'myAppPool_v1_1' do\n runtime_version \"2.0\"\n pipeline_mode :Classic\n action :add\nend\n```\n\niis_app\n--------\n\nCreates an application in IIS.\n\n### Actions\n\n- `:add` - add a new application pool\n- `:delete` - delete an existing application pool\n\n### Attribute Parameters\n\n- `site_name` - name attribute. The name of the site to add this app to\n- `path` -The virtual path for this application\n- `application_pool` - The pool this application belongs to\n- `physical_path` - The physical path where this app resides.\n- `enabled_protocols` - The enabled protocols that this app provides (http, https, net.pipe, net.tcp, etc)\n\n### Example\n\n```ruby\n# creates a new app\niis_app \"myApp\" do\n path \"/v1_1\"\n application_pool \"myAppPool_v1_1\"\n physical_path \"#{node['iis']['docroot']}/testfu/v1_1\"\n enabled_protocols \"http,net.pipe\"\n action :add\nend\n```\n\niis_vdir\n---------\n\nAllows easy management of IIS virtual directories (i.e. vdirs).\n\n### Actions\n\n- :add: - add a new virtual directory\n- :delete: - delete an existing virtual directory\n- :config: - configure a virtual directory\n\n### Attribute Parameters\n\n- `application_name`: name attribute. Specifies the name of the application attribute. This is the name of the website or application you are adding it to.\n- `path`: The virtual directory path on the site.\n- `physical_path`: The physical path of the virtual directory on the disk.\n- `username`: (optional) The username required to logon to the physical_path. If set to \"\" will clear username and password.\n- `password`: (optional) The password required to logon to the physical_path\n- `logon_method`: (optional, default: :ClearText) The method used to logon (:Interactive, :Batch, :Network, :ClearText). For more information on these types, see \"LogonUser Function\", Read more at [MSDN](http://msdn2.microsoft.com/en-us/library/aa378184.aspx)\n- `allow_sub_dir_config`: (optional, default: true) Boolean that specifies whether or not the Web server will look for configuration files located in the subdirectories of this virtual directory. Setting this to false can improve performance on servers with very large numbers of web.config files, but doing so prevents IIS configuration from being read in subdirectories.\n\n### Examples\n\n```ruby\n# add a virtual directory to default application\niis_vdir 'Default Web Site/' do\n action :add\n path '/Content/Test'\n physical_path 'C:\\wwwroot\\shared\\test'\nend\n```\n\n```ruby\n# add a virtual directory to an application under a site\niis_vdir 'Default Web Site/my application' do\n action :add\n path '/Content/Test'\n physical_path 'C:\\wwwroot\\shared\\test'\nend\n```\n\n```ruby\n# adds a virtual directory to default application which points to a smb share. (Remember to escape the \"\\\"'s)\niis_vdir 'Default Web Site/' do\n action :add\n path '/Content/Test'\n physical_path '\\\\\\\\sharename\\\\sharefolder\\\\1'\nend\n```\n\n```ruby\n# configure a virtual directory to have a username and password\niis_vdir 'Default Web Site/' do\n action :config\n path '/Content/Test'\n username 'domain\\myspecialuser'\n password 'myspecialpassword'\nend\n```\n\n```ruby\n# delete a virtual directory from the default application\niis_vdir 'Default Web Site/' do\n action :delete\n path '/Content/Test'\nend\n```\n\niis_section\n---------\n\nAllows for the locking/unlocking of sections ([listed here](http://www.iis.net/configreference) or via the command `appcmd list config \\\"\\\" /config:* /xml`)\n\nThis is valuable to allow the `web.config` of an individual application/website control it's own settings.\n\n### Actions\n\n- `:lock`: - locks the `section` passed\n- `:unlock`: - unlocks the `section` passed\n\n### Attribute Parameters\n\n- `section`: The name of the section to lock.\n- `returns`: The result of the `shell_out` command.\n\n### Examples\n\n```ruby\n# Sets the IIS global windows authentication to be locked globally\niis_section 'locks global configuration of windows auth' do\n section 'system.webServer/security/authentication/windowsAuthentication'\n action :lock\nend\n```\n\n```ruby\n# Sets the IIS global Basic authentication to be locked globally\niis_section 'locks global configuration of Basic auth' do\n section 'system.webServer/security/authentication/basicAuthentication'\n action :lock\nend\n```\n\n```ruby\n# Sets the IIS global windows authentication to be unlocked globally\niis_section 'unlocked web.config globally for windows auth' do\n action :unlock\n section 'system.webServer/security/authentication/windowsAuthentication'\nend\n```\n\n```ruby\n# Sets the IIS global Basic authentication to be unlocked globally\niis_section 'unlocked web.config globally for Basic auth' do\n action :unlock\n section 'system.webServer/security/authentication/basicAuthentication'\nend\n```\n\niis_module\n--------\n\nManages modules globally or on a per site basis.\n\n### Actions\n\n- `:add` - add a new module\n- `:delete` - delete a module\n\n### Attribute Parameters\n\n- `module_name` - The name of the module to add or delete\n- `type` - The type of module\n- `precondition` - precondition for module\n- `application` - The application or site to add the module to\n\n### Example\n\n```ruby\n# Adds a module called \"My 3rd Party Module\" to mySite/\niis_module \"My 3rd Party Module\" do\n application \"mySite/\"\n precondition \"bitness64\"\n action :add\nend\n```\n\n```ruby\n# Adds a module called \"MyModule\" to all IIS sites on the server\niis_module \"MyModule\"\n```\n\n\nUsage\n=====\n\ndefault\n-------\n\nInstalls and configures IIS 7.0/7.5/8.0 using the default configuration.\n\nmod_*\n-----\n\nThis cookbook also contains recipes for installing individual IIS modules (extensions). These recipes can be included in a node's run_list to build the minimal desired custom IIS installation.\n\n* `mod_aspnet` - installs ASP.NET runtime components\n* `mod_aspnet45` - installs ASP.NET 4.5 runtime components\n* `mod_auth_basic` - installs Basic Authentication support\n* `mod_auth_windows` - installs Windows Authentication (authenticate clients by using NTLM or Kerberos) support\n* `mod_compress_dynamic` - installs dynamic content compression support. *PLEASE NOTE* - enabling dynamic compression always gives you more efficient use of bandwidth, but if your server's processor utilization is already very high, the CPU load imposed by dynamic compression might make your site perform more slowly.\n* `mod_compress_static` - installs static content compression support\n* `mod_iis6_metabase_compat` - installs IIS 6 Metabase Compatibility component.\n* `mod_isapi` - installs ISAPI (Internet Server Application Programming Interface) extension and filter support.\n* `mod_logging` - installs and enables HTTP Logging (logging of Web site activity), Logging Tools (logging tools and scripts) and Custom Logging (log any of the HTTP request/response headers, IIS server variables, and client-side fields with simple configuration) support\n* `mod_management` - installs Web server Management Console which supports management of local and remote Web servers\n* `mod_security` - installs URL Authorization (Authorizes client access to the URLs that comprise a Web application), Request Filtering (configures rules to block selected client requests) and IP Security (allows or denies content access based on IP address or domain name) support.\n* `mod_tracing` - installs support for tracing ASP.NET applications and failed requests.\n\nNote: Not every possible IIS module has a corresponding recipe. The foregoing recipes are included for convenience, but users may also place additional IIS modules that are installable as Windows features into the ``node['iis']['components']`` array.\n\nLicense and Author\n==================\n\n* Author:: Seth Chisamore ()\n* Author:: Julian Dunn ()\n* Author:: Justin Schuhmann ()\n\nCopyright:: 2011-2015, Chef Software, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","maintainer":"Chef Software, Inc.","maintainer_email":"cookbooks@chef.io","license":"Apache 2.0","platforms":{"windows":">= 0.0.0"},"dependencies":{"windows":">= 1.34.6"},"recommendations":{},"suggestions":{},"conflicting":{},"providing":{},"replacing":{},"attributes":{},"groupings":{},"recipes":{}} \ No newline at end of file diff --git a/cookbooks/iis/providers/app.rb b/cookbooks/iis/providers/app.rb new file mode 100644 index 0000000..0e61a4d --- /dev/null +++ b/cookbooks/iis/providers/app.rb @@ -0,0 +1,146 @@ +# +# Author:: Kendrick Martin (kendrick.martin@webtrends.com) +# Contributor:: Adam Wayne (awayne@waynedigital.com) +# Cookbook Name:: iis +# Provider:: app +# +# Copyright:: 2011, Webtrends Inc. +# +# 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. +# + +require 'chef/mixin/shell_out' +require 'rexml/document' + +include Chef::Mixin::ShellOut +include REXML +include Opscode::IIS::Helper + +action :add do + if !@current_resource.exists + cmd = "#{appcmd(node)} add app /site.name:\"#{new_resource.site_name}\"" + cmd << " /path:\"#{new_resource.path}\"" + cmd << " /applicationPool:\"#{new_resource.application_pool}\"" if new_resource.application_pool + cmd << " /physicalPath:\"#{windows_cleanpath(new_resource.physical_path)}\"" if new_resource.physical_path + cmd << " /enabledProtocols:\"#{new_resource.enabled_protocols}\"" if new_resource.enabled_protocols + Chef::Log.debug(cmd) + shell_out!(cmd) + new_resource.updated_by_last_action(true) + Chef::Log.info('App created') + else + Chef::Log.debug("#{new_resource} app already exists - nothing to do") + end +end + +action :config do + was_updated = false + cmd_current_values = "#{appcmd(node)} list app \"#{site_identifier}\" /config:* /xml" + Chef::Log.debug(cmd_current_values) + cmd_current_values = shell_out(cmd_current_values) + if cmd_current_values.stderr.empty? + xml = cmd_current_values.stdout + doc = Document.new(xml) + is_new_path = new_or_empty_value?(doc.root, 'APP/application/@path', new_resource.path.to_s) + is_new_application_pool = new_or_empty_value?(doc.root, 'APP/application/@applicationPool', new_resource.application_pool.to_s) + is_new_enabled_protocols = new_or_empty_value?(doc.root, 'APP/application/@enabledProtocols', new_resource.enabled_protocols.to_s) + is_new_physical_path = new_or_empty_value?(doc.root, 'APP/application/virtualDirectory/@physicalPath', new_resource.physical_path.to_s) + + # only get the beginning of the command if there is something that changeds + cmd = "#{appcmd(node)} set app \"#{site_identifier}\"" if ((new_resource.path && is_new_path) || + (new_resource.application_pool && is_new_application_pool) || + (new_resource.enabled_protocols && is_new_enabled_protocols)) + # adds path to the cmd + cmd << " /path:\"#{new_resource.path}\"" if new_resource.path && is_new_path + # adds applicationPool to the cmd + cmd << " /applicationPool:\"#{new_resource.application_pool}\"" if new_resource.application_pool && is_new_application_pool + # adds enabledProtocols to the cmd + cmd << " /enabledProtocols:\"#{new_resource.enabled_protocols}\"" if new_resource.enabled_protocols && is_new_enabled_protocols + Chef::Log.debug(cmd) + + if (cmd.nil?) + Chef::Log.debug("#{new_resource} application - nothing to do") + else + shell_out!(cmd) + was_updated = true + end + + if ((new_resource.path && is_new_path) || + (new_resource.application_pool && is_new_application_pool) || + (new_resource.enabled_protocols && is_new_enabled_protocols)) + was_updated = true + end + + if new_resource.physical_path && is_new_physical_path + was_updated = true + cmd = "#{appcmd(node)} set vdir /vdir.name:\"#{vdir_identifier}\"" + cmd << " /physicalPath:\"#{windows_cleanpath(new_resource.physical_path)}\"" + Chef::Log.debug(cmd) + shell_out!(cmd) + end + if was_updated + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} configured application") + else + Chef::Log.debug("#{new_resource} application - nothing to do") + end + else + log "Failed to run iis_app action :config, #{cmd_current_values.stderr}" do + level :warn + end + end +end + +action :delete do + if @current_resource.exists + shell_out!("#{appcmd(node)} delete app \"#{site_identifier}\"") + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} deleted") + else + Chef::Log.debug("#{new_resource} app does not exist - nothing to do") + end +end + +def load_current_resource + @current_resource = Chef::Resource::IisApp.new(new_resource.name) + @current_resource.site_name(new_resource.site_name) + @current_resource.path(new_resource.path) + @current_resource.application_pool(new_resource.application_pool) + cmd = shell_out("#{appcmd(node)} list app") + Chef::Log.debug("#{new_resource} list app command output: #{cmd.stdout}") + regex = /^APP\s\"#{new_resource.site_name}#{new_resource.path}\"/ + Chef::Log.debug('Running regex') + if cmd.stderr.empty? + result = cmd.stdout.match(regex) + Chef::Log.debug("#{new_resource} current_resource match output:#{result}") + if result + @current_resource.exists = true + else + @current_resource.exists = false + end + else + log "Failed to run iis_app action :load_current_resource, #{cmd_current_values.stderr}" do + level :warn + end + end +end + +private + +def site_identifier + "#{new_resource.site_name}#{new_resource.path}" +end + +# Ensure VDIR identifier has a trailing slash +def vdir_identifier + site_identifier.end_with?('/') ? site_identifier : site_identifier + '/' +end diff --git a/cookbooks/iis/providers/config.rb b/cookbooks/iis/providers/config.rb new file mode 100644 index 0000000..37a3787 --- /dev/null +++ b/cookbooks/iis/providers/config.rb @@ -0,0 +1,33 @@ +# +# Author:: Kendrick Martin (kendrick.martin@webtrends.com) +# Contributor:: David Dvorak (david.dvorak@webtrends.com) +# Cookbook Name:: iis +# Resource:: config +# +# Copyright:: 2011, Webtrends Inc. +# +# 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. +# + +require 'chef/mixin/shell_out' + +include Chef::Mixin::ShellOut +include Opscode::IIS::Helper + +action :config do + cmd = "#{appcmd(node)} set config #{new_resource.cfg_cmd}" + Chef::Log.debug(cmd) + shell_out!(cmd, returns: new_resource.returns) + Chef::Log.info('IIS Config command run') + new_resource.updated_by_last_action(true) +end diff --git a/cookbooks/iis/providers/module.rb b/cookbooks/iis/providers/module.rb new file mode 100644 index 0000000..8315107 --- /dev/null +++ b/cookbooks/iis/providers/module.rb @@ -0,0 +1,93 @@ +# +# Author:: Jon DeCamp () +# Cookbook Name:: iis +# Provider:: site +# +# Copyright:: 2013, Nordstrom, Inc. +# +# 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. +# + +require 'chef/mixin/shell_out' +include Chef::Mixin::ShellOut +include Opscode::IIS::Helper + +# Support whyrun +def whyrun_supported? + true +end + +# appcmd syntax for adding modules +# appcmd add module /name:string /type:string /preCondition:string +action :add do + if !@current_resource.exists + converge_by("add IIS module #{new_resource.module_name}") do + cmd = "#{appcmd(node)} add module /module.name:\"#{new_resource.module_name}\"" + + if new_resource.application + cmd << " /app.name:\"#{new_resource.application}\"" + end + + if new_resource.type + cmd << " /type:\"#{new_resource.type}\"" + end + + if new_resource.precondition + cmd << " /preCondition:\"#{new_resource.precondition}\"" + end + + shell_out!(cmd, returns: [0, 42]) + + Chef::Log.info("#{new_resource} added module '#{new_resource.module_name}'") + end + else + Chef::Log.debug("#{new_resource} module already exists - nothing to do") + end +end + +action :delete do + if @current_resource.exists + converge_by("delete IIS module #{new_resource.module_name}") do + cmd = "#{appcmd(node)} delete module /module.name:\"#{new_resource.module_name}\"" + if new_resource.application + cmd << " /app.name:\"#{new_resource.application}\"" + end + + shell_out!(cmd, returns: [0, 42]) + end + + Chef::Log.info("#{new_resource} deleted") + else + Chef::Log.debug("#{new_resource} module does not exist - nothing to do") + end +end + +def load_current_resource + @current_resource = Chef::Resource::IisModule.new(new_resource.name) + @current_resource.module_name(new_resource.module_name) + if new_resource.application + cmd = shell_out("#{appcmd(node)} list module /module.name:\"#{new_resource.module_name}\" /app.name:\"#{new_resource.application}\"") + else + cmd = shell_out("#{appcmd(node)} list module /module.name:\"#{new_resource.module_name}\"") + end + + # 'MODULE "Module Name" ( type:module.type, preCondition:condition )' + # 'MODULE "Module Name" ( native, preCondition:condition )' + + Chef::Log.debug("#{new_resource} list module command output: #{cmd.stdout}") + if cmd.stdout.empty? + @current_resource.exists = false + else + @current_resource.exists = true + end +end diff --git a/cookbooks/iis/providers/pool.rb b/cookbooks/iis/providers/pool.rb new file mode 100644 index 0000000..e3f2373 --- /dev/null +++ b/cookbooks/iis/providers/pool.rb @@ -0,0 +1,287 @@ +# +# Author:: Kendrick Martin (kendrick.martin@webtrends.com) +# Contributor:: David Dvorak (david.dvorak@webtrends.com) +# Cookbook Name:: iis +# Provider:: pool +# +# Copyright:: 2011, Webtrends Inc. +# +# 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. +# + +require 'chef/mixin/shell_out' +require 'rexml/document' + +include Chef::Mixin::ShellOut +include REXML +include Opscode::IIS::Helper + +action :add do + if !@current_resource.exists + cmd = "#{appcmd(node)} add apppool /name:\"#{new_resource.pool_name}\"" + cmd << ' /managedRuntimeVersion:' if new_resource.runtime_version || new_resource.no_managed_code + cmd << "v#{new_resource.runtime_version}" if new_resource.runtime_version && !new_resource.no_managed_code + cmd << " /managedPipelineMode:#{new_resource.pipeline_mode.capitalize}" if new_resource.pipeline_mode + Chef::Log.debug(cmd) + shell_out!(cmd) + configure + new_resource.updated_by_last_action(true) + Chef::Log.info('App pool created') + else + Chef::Log.debug("#{new_resource} pool already exists - nothing to do") + end +end + +action :config do + configure +end + +action :delete do + if @current_resource.exists + shell_out!("#{appcmd(node)} delete apppool \"#{site_identifier}\"") + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} deleted") + else + Chef::Log.debug("#{new_resource} pool does not exist - nothing to do") + end +end + +action :start do + if !@current_resource.running + shell_out!("#{appcmd(node)} start apppool \"#{site_identifier}\"") + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} started") + else + Chef::Log.debug("#{new_resource} already running - nothing to do") + end +end + +action :stop do + if @current_resource.running + shell_out!("#{appcmd(node)} stop apppool \"#{site_identifier}\"") + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} stopped") + else + Chef::Log.debug("#{new_resource} already stopped - nothing to do") + end +end + +action :restart do + shell_out!("#{appcmd(node)} stop APPPOOL \"#{site_identifier}\"") + sleep 2 + shell_out!("#{appcmd(node)} start APPPOOL \"#{site_identifier}\"") + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} restarted") +end + +action :recycle do + shell_out!("#{appcmd(node)} recycle APPPOOL \"#{site_identifier}\"") + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} recycled") +end + +def load_current_resource + @current_resource = Chef::Resource::IisPool.new(new_resource.name) + @current_resource.pool_name(new_resource.pool_name) + cmd = shell_out("#{appcmd(node)} list apppool") + # APPPOOL "DefaultAppPool" (MgdVersion:v2.0,MgdMode:Integrated,state:Started) + Chef::Log.debug("#{new_resource} list apppool command output: #{cmd.stdout}") + if cmd.stderr.empty? + result = cmd.stdout.gsub(/\r\n?/, "\n") # ensure we have no carriage returns + result = result.match(/^APPPOOL\s\"(#{new_resource.pool_name})\"\s\(MgdVersion:(.*),MgdMode:(.*),state:(.*)\)$/) + Chef::Log.debug("#{new_resource} current_resource match output: #{result}") + if result + @current_resource.exists = true + @current_resource.running = (result[4] =~ /Started/) ? true : false + else + @current_resource.exists = false + @current_resource.running = false + end + else + log "Failed to run iis_pool action :load_current_resource, #{cmd_current_values.stderr}" do + level :warn + end + end +end + +private + +def site_identifier + new_resource.pool_name +end + +def configure + @was_updated = false + cmd_current_values = "#{appcmd(node)} list apppool \"#{new_resource.pool_name}\" /config:* /xml" + Chef::Log.debug(cmd_current_values) + cmd_current_values = shell_out(cmd_current_values) + if cmd_current_values.stderr.empty? + xml = cmd_current_values.stdout + doc = Document.new(xml) + + # root items + is_new_managed_runtime_version = new_value?(doc.root, 'APPPOOL/@RuntimeVersion', "v#{new_resource.runtime_version}") + is_new_pipeline_mode = new_value?(doc.root, 'APPPOOL/@PipelineMode'.capitalize, "#{new_resource.pipeline_mode}".to_s.capitalize) + + # add items + is_new_start_mode = new_value?(doc.root, 'APPPOOL/add/@startMode', new_resource.start_mode.to_s) + is_new_auto_start = new_value?(doc.root, 'APPPOOL/add/@autoStart', new_resource.auto_start.to_s) + is_new_queue_length = new_value?(doc.root, 'APPPOOL/add/@queueLength', new_resource.queue_length.to_s) + is_new_enable_32_bit_app_on_win_64 = new_value?(doc.root, 'APPPOOL/add/@enable32BitAppOnWin64', new_resource.thirty_two_bit.to_s) + + # processModel items + is_new_max_processes = new_or_empty_value?(doc.root, 'APPPOOL/add/processModel/@maxProcesses', new_resource.max_proc.to_s) + is_new_load_user_profile = new_value?(doc.root, 'APPPOOL/add/processModel/@loadUserProfile', new_resource.load_user_profile.to_s) + is_new_identity_type = new_value?(doc.root, 'APPPOOL/add/processModel/@identityType', new_resource.pool_identity.to_s) + is_new_user_name = new_or_empty_value?(doc.root, 'APPPOOL/add/processModel/@userName', new_resource.pool_username.to_s) + is_new_password = new_or_empty_value?(doc.root, 'APPPOOL/add/processModel/@password', new_resource.pool_password.to_s) + is_new_logon_type = new_value?(doc.root, 'APPPOOL/add/processModel/@logonType', new_resource.logon_type.to_s) + is_new_manual_group_membership = new_value?(doc.root, 'APPPOOL/add/processModel/@manualGroupMembership', new_resource.manual_group_membership.to_s) + is_new_idle_timeout = new_value?(doc.root, 'APPPOOL/add/processModel/@idleTimeout', new_resource.idle_timeout.to_s) + is_new_shutdown_time_limit = new_value?(doc.root, 'APPPOOL/add/processModel/@shutdownTimeLimit', new_resource.shutdown_time_limit.to_s) + is_new_startup_time_limit = new_value?(doc.root, 'APPPOOL/add/processModel/@startupTimeLimit', new_resource.startup_time_limit.to_s) + is_new_pinging_enabled = new_value?(doc.root, 'APPPOOL/add/processModel/@pingingEnabled', new_resource.pinging_enabled.to_s) + is_new_ping_interval = new_value?(doc.root, 'APPPOOL/add/processModel/@pingInterval', new_resource.ping_interval.to_s) + is_new_ping_response_time = new_value?(doc.root, 'APPPOOL/add/processModel/@pingResponseTime', new_resource.ping_response_time.to_s) + + # failure items + is_new_load_balancer_capabilities = new_value?(doc.root, 'APPPOOL/add/failure/@loadBalancerCapabilities', new_resource.load_balancer_capabilities.to_s) + is_new_orphan_worker_process = new_value?(doc.root, 'APPPOOL/add/failure/@orphanWorkerProcess', new_resource.orphan_worker_process.to_s) + is_new_orphan_action_exe = new_or_empty_value?(doc.root, 'APPPOOL/add/failure/@orphanActionExe', new_resource.orphan_action_exe.to_s) + is_new_orphan_action_params = new_or_empty_value?(doc.root, 'APPPOOL/add/failure/@orphanActionParams', new_resource.orphan_action_params.to_s) + is_new_rapid_fail_protection = new_value?(doc.root, 'APPPOOL/add/failure/@rapidFailProtection', new_resource.rapid_fail_protection.to_s) + is_new_rapid_fail_protection_interval = new_value?(doc.root, 'APPPOOL/add/failure/@rapidFailProtectionInterval', new_resource.rapid_fail_protection_interval.to_s) + is_new_rapid_fail_protection_max_crashes = new_value?(doc.root, 'APPPOOL/add/failure/@rapidFailProtectionMaxCrashes', new_resource.rapid_fail_protection_max_crashes.to_s) + is_new_auto_shutdown_exe = new_or_empty_value?(doc.root, 'APPPOOL/add/failure/@autoShutdownExe', new_resource.auto_shutdown_exe.to_s) + is_new_auto_shutdown_params = new_or_empty_value?(doc.root, 'APPPOOL/add/failure/@autoShutdownParams', new_resource.auto_shutdown_params.to_s) + + # recycling items + is_new_disallow_overlapping_rotation = new_value?(doc.root, 'APPPOOL/add/recycling/@disallowOverlappingRotation', new_resource.disallow_overlapping_rotation.to_s) + is_new_disallow_rotation_on_config_change = new_value?(doc.root, 'APPPOOL/add/recycling/@disallowRotationOnConfigChange', new_resource.disallow_rotation_on_config_change.to_s) + is_new_recycle_after_time = new_or_empty_value?(doc.root, 'APPPOOL/add/recycling/periodicRestart/@time', new_resource.recycle_after_time.to_s) + is_new_recycle_at_time = new_or_empty_value?(doc.root, 'APPPOOL/add/recycling/periodicRestart/schedule/add/@value', new_resource.recycle_at_time.to_s) + is_new_private_memory = new_or_empty_value?(doc.root, 'APPPOOL/add/recycling/periodicRestart/@privateMemory', new_resource.private_mem.to_s) + is_new_log_event_on_recycle = new_value?(doc.root, 'APPPOOL/add/recycling/@logEventOnRecycle', 'Time, Requests, Schedule, Memory, IsapiUnhealthy, OnDemand, ConfigChange, PrivateMemory') + + # cpu items + is_new_cpu_action = new_value?(doc.root, 'APPPOOL/add/cpu/@action', new_resource.cpu_action.to_s) + is_new_cpu_limit = new_value?(doc.root, 'APPPOOL/add/cpu/@limit', new_resource.cpu_limit.to_s) + is_new_cpu_smp_affinitized = new_value?(doc.root, 'APPPOOL/add/cpu/@smpAffinitized', new_resource.cpu_smp_affinitized.to_s) + is_new_cpu_reset_interval = new_value?(doc.root, 'APPPOOL/add/cpu/@resetInterval', new_resource.cpu_reset_interval.to_s) + is_new_smp_processor_affinity_mask = new_value?(doc.root, 'APPPOOL/add/cpu/@smpProcessorAffinityMask', new_resource.smp_processor_affinity_mask.to_s) + is_new_smp_processor_affinity_mask_2 = new_value?(doc.root, 'APPPOOL/add/cpu/@smpProcessorAffinityMask2', new_resource.smp_processor_affinity_mask_2.to_s) + + # Application Pool Config + @cmd = "#{appcmd(node)} set config /section:applicationPools" + + # root items + configure_application_pool(is_new_auto_start, "autoStart:#{new_resource.auto_start}") + configure_application_pool(is_new_start_mode, "startMode:#{new_resource.start_mode}") + configure_application_pool(new_resource.runtime_version && is_new_managed_runtime_version, "managedRuntimeVersion:v#{new_resource.runtime_version}") + configure_application_pool(new_resource.pipeline_mode && is_new_pipeline_mode, "managedPipelineMode:#{new_resource.pipeline_mode}") + configure_application_pool(new_resource.thirty_two_bit && is_new_enable_32_bit_app_on_win_64, "enable32BitAppOnWin64:#{new_resource.thirty_two_bit}") + configure_application_pool(new_resource.queue_length && is_new_queue_length, "queueLength:#{new_resource.queue_length}") + + # processModel items + configure_application_pool(new_resource.max_proc && is_new_max_processes, "processModel.maxProcesses:#{new_resource.max_proc}") + configure_application_pool(is_new_load_user_profile, "processModel.loadUserProfile:#{new_resource.load_user_profile}") + configure_application_pool(is_new_logon_type, "processModel.logonType:#{new_resource.logon_type}") + configure_application_pool(is_new_manual_group_membership, "processModel.manualGroupMembership:#{new_resource.manual_group_membership}") + configure_application_pool(is_new_idle_timeout, "processModel.idleTimeout:#{new_resource.idle_timeout}") + configure_application_pool(is_new_shutdown_time_limit, "processModel.shutdownTimeLimit:#{new_resource.shutdown_time_limit}") + configure_application_pool(is_new_startup_time_limit, "processModel.startupTimeLimit:#{new_resource.startup_time_limit}") + configure_application_pool(is_new_pinging_enabled, "processModel.pingingEnabled:#{new_resource.pinging_enabled}") + configure_application_pool(is_new_ping_interval, "processModel.pingInterval:#{new_resource.ping_interval}") + configure_application_pool(is_new_ping_response_time, "processModel.pingResponseTime:#{new_resource.ping_response_time}") + + # recycling items + ## Special case this collection removal for now. + if (new_resource.recycle_at_time && is_new_recycle_at_time) + @was_updated = true + cmd = "#{appcmd(node)} set config /section:applicationPools \"/-[name='#{new_resource.pool_name}'].recycling.periodicRestart.schedule\"" + Chef::Log.debug(@cmd) + shell_out!(@cmd) + end + configure_application_pool(new_resource.recycle_after_time && is_new_recycle_after_time, "recycling.periodicRestart.time:#{new_resource.recycle_after_time}") + configure_application_pool(new_resource.recycle_at_time && is_new_recycle_at_time, "recycling.periodicRestart.schedule.[value='#{new_resource.recycle_at_time}']", '+') + configure_application_pool(is_new_log_event_on_recycle, 'recycling.logEventOnRecycle:PrivateMemory,Memory,Schedule,Requests,Time,ConfigChange,OnDemand,IsapiUnhealthy') + configure_application_pool(new_resource.private_mem && is_new_private_memory, "recycling.periodicRestart.privateMemory:#{new_resource.private_mem}") + configure_application_pool(is_new_disallow_rotation_on_config_change, "recycling.disallowRotationOnConfigChange:#{new_resource.disallow_rotation_on_config_change}") + configure_application_pool(is_new_disallow_overlapping_rotation, "recycling.disallowOverlappingRotation:#{new_resource.disallow_overlapping_rotation}") + + # failure items + configure_application_pool(is_new_load_balancer_capabilities, "failure.loadBalancerCapabilities:#{new_resource.load_balancer_capabilities}") + configure_application_pool(is_new_orphan_worker_process, "failure.orphanWorkerProcess:#{new_resource.orphan_worker_process}") + configure_application_pool(new_resource.orphan_action_exe && is_new_orphan_action_exe, "failure.orphanActionExe:#{new_resource.orphan_action_exe}") + configure_application_pool(new_resource.orphan_action_params && is_new_orphan_action_params, "failure.orphanActionParams:#{new_resource.orphan_action_params}") + configure_application_pool(is_new_rapid_fail_protection, "failure.rapidFailProtection:#{new_resource.rapid_fail_protection}") + configure_application_pool(is_new_rapid_fail_protection_interval, "failure.rapidFailProtectionInterval:#{new_resource.rapid_fail_protection_interval}") + configure_application_pool(is_new_rapid_fail_protection_max_crashes, "failure.rapidFailProtectionMaxCrashes:#{new_resource.rapid_fail_protection_max_crashes}") + configure_application_pool(new_resource.auto_shutdown_exe && is_new_auto_shutdown_exe, "failure.autoShutdownExe:#{new_resource.auto_shutdown_exe}") + configure_application_pool(new_resource.auto_shutdown_params && is_new_auto_shutdown_params, "failure.autoShutdownParams:#{new_resource.auto_shutdown_params}") + + # cpu items + configure_application_pool(is_new_cpu_action, "cpu.action:#{new_resource.cpu_action}") + configure_application_pool(is_new_cpu_limit, "cpu.limit:#{new_resource.cpu_limit}") + configure_application_pool(is_new_cpu_reset_interval, "cpu.resetInterval:#{new_resource.cpu_reset_interval}") + configure_application_pool(is_new_cpu_smp_affinitized, "cpu.smpAffinitized:#{new_resource.cpu_smp_affinitized}") + configure_application_pool(is_new_smp_processor_affinity_mask, "cpu.smpProcessorAffinityMask:#{new_resource.smp_processor_affinity_mask}") + configure_application_pool(is_new_smp_processor_affinity_mask_2, "cpu.smpProcessorAffinityMask2:#{new_resource.smp_processor_affinity_mask_2}") + + if (@cmd != "#{appcmd(node)} set config /section:applicationPools") + Chef::Log.debug(@cmd) + shell_out!(@cmd) + end + + # Application Pool Identity Settings + if ((new_resource.pool_username && new_resource.pool_username != '') && (is_new_user_name || is_new_password)) + @was_updated = true + cmd = "#{appcmd(node)} set config /section:applicationPools" + cmd << " \"/[name='#{new_resource.pool_name}'].processModel.identityType:SpecificUser\"" + cmd << " \"/[name='#{new_resource.pool_name}'].processModel.userName:#{new_resource.pool_username}\"" + cmd << " \"/[name='#{new_resource.pool_name}'].processModel.password:#{new_resource.pool_password}\"" if (new_resource.pool_password && new_resource.pool_password != '' && is_new_password) + Chef::Log.debug(cmd) + shell_out!(cmd) + elsif ((new_resource.pool_username.nil? || new_resource.pool_username == '') && + (new_resource.pool_password.nil? || new_resource.pool_username == '') && + (is_new_identity_type && new_resource.pool_identity != 'SpecificUser')) + @was_updated = true + cmd = "#{appcmd(node)} set config /section:applicationPools" + cmd << " \"/[name='#{new_resource.pool_name}'].processModel.identityType:#{new_resource.pool_identity}\"" + Chef::Log.debug(cmd) + shell_out!(cmd) + end + + if @was_updated + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} configured application pool") + else + Chef::Log.debug("#{new_resource} application pool - nothing to do") + end + else + log "Failed to run iis_pool action :config, #{cmd_current_values.stderr}" do + level :warn + end + end +end + +private + +def configure_application_pool(condition, config, add_remove = '') + unless condition + return + end + + @was_updated = true + @cmd << " \"/#{add_remove}[name='#{new_resource.pool_name}'].#{config}\"" +end diff --git a/cookbooks/iis/providers/section.rb b/cookbooks/iis/providers/section.rb new file mode 100644 index 0000000..a96132f --- /dev/null +++ b/cookbooks/iis/providers/section.rb @@ -0,0 +1,71 @@ +# +# Author:: Justin Schuhmann +# Cookbook Name:: iis +# Resource:: lock +# +# Copyright:: Justin Schuhmann +# +# 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. +# + +require 'chef/mixin/shell_out' +require 'rexml/document' + +include Chef::Mixin::ShellOut +include REXML +include Opscode::IIS::Helper + +action :lock do + @current_resource.exists = new_value?(doc.root, 'CONFIG/@overrideMode', 'Deny') + + if !@current_resource.exists + cmd = "#{appcmd(node)} lock config -section:\"#{new_resource.section}\" -commit:apphost" + Chef::Log.debug(cmd) + shell_out!(cmd, returns: new_resource.returns) + new_resource.updated_by_last_action(true) + Chef::Log.info('IIS Config command run') + else + Chef::Log.debug("#{new_resource.section} already locked - nothing to do") + end +end + +action :unlock do + @current_resource.exists = new_value?(doc.root, 'CONFIG/@overrideMode', 'Allow') + + if !@current_resource.exists + cmd = "#{appcmd(node)} unlock config -section:\"#{new_resource.section}\" -commit:apphost" + Chef::Log.debug(cmd) + shell_out!(cmd, returns: new_resource.returns) + new_resource.updated_by_last_action(true) + Chef::Log.info('IIS Config command run') + else + Chef::Log.debug("#{new_resource.section} already unlocked - nothing to do") + end +end + +def load_current_resource + @current_resource = Chef::Resource::IisSection.new(new_resource.section) + @current_resource.section(new_resource.section) +end + +def doc + cmd_current_values = "#{appcmd(node)} list config \"\" -section:#{new_resource.section} /config:* /xml" + Chef::Log.debug(cmd_current_values) + cmd_current_values = shell_out(cmd_current_values) + if cmd_current_values.stderr.empty? + xml = cmd_current_values.stdout + return Document.new(xml) + end + + cmd_current_values.error! +end diff --git a/cookbooks/iis/providers/site.rb b/cookbooks/iis/providers/site.rb new file mode 100644 index 0000000..8d6f9bf --- /dev/null +++ b/cookbooks/iis/providers/site.rb @@ -0,0 +1,221 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Provider:: site +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +require 'chef/mixin/shell_out' +require 'rexml/document' + +include Chef::Mixin::ShellOut +include REXML +include Opscode::IIS::Helper + +action :add do + if !@current_resource.exists + cmd = "#{appcmd(node)} add site /name:\"#{new_resource.site_name}\"" + cmd << " /id:#{new_resource.site_id}" if new_resource.site_id + cmd << " /physicalPath:\"#{windows_cleanpath(new_resource.path)}\"" if new_resource.path + if new_resource.bindings + cmd << " /bindings:\"#{new_resource.bindings}\"" + else + cmd << " /bindings:#{new_resource.protocol}/*" + cmd << ":#{new_resource.port}:" if new_resource.port + cmd << new_resource.host_header if new_resource.host_header + end + + # support for additional options -logDir, -limits, -ftpServer, etc... + if new_resource.options + cmd << " #{new_resource.options}" + end + shell_out!(cmd, returns: [0, 42]) + + configure + + if new_resource.application_pool + shell_out!("#{appcmd(node)} set app \"#{new_resource.site_name}/\" /applicationPool:\"#{new_resource.application_pool}\"", returns: [0, 42]) + end + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} added new site '#{new_resource.site_name}'") + else + Chef::Log.debug("#{new_resource} site already exists - nothing to do") + end +end + +action :config do + configure +end + +action :delete do + if @current_resource.exists + Chef::Log.info("#{appcmd(node)} stop site /site.name:\"#{new_resource.site_name}\"") + shell_out!("#{appcmd(node)} delete site /site.name:\"#{new_resource.site_name}\"", returns: [0, 42]) + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} deleted") + else + Chef::Log.debug("#{new_resource} site does not exist - nothing to do") + end +end + +action :start do + if !@current_resource.running + shell_out!("#{appcmd(node)} start site /site.name:\"#{new_resource.site_name}\"", returns: [0, 42]) + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} started") + else + Chef::Log.debug("#{new_resource} already running - nothing to do") + end +end + +action :stop do + if @current_resource.running + Chef::Log.info("#{appcmd(node)} stop site /site.name:\"#{new_resource.site_name}\"") + shell_out!("#{appcmd(node)} stop site /site.name:\"#{new_resource.site_name}\"", returns: [0, 42]) + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} stopped") + else + Chef::Log.debug("#{new_resource} already stopped - nothing to do") + end +end + +action :restart do + shell_out!("#{appcmd(node)} stop site /site.name:\"#{new_resource.site_name}\"", returns: [0, 42]) + sleep 2 + shell_out!("#{appcmd(node)} start site /site.name:\"#{new_resource.site_name}\"", returns: [0, 42]) + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} restarted") +end + +def load_current_resource + @current_resource = Chef::Resource::IisSite.new(new_resource.name) + @current_resource.site_name(new_resource.site_name) + cmd = shell_out("#{appcmd(node)} list site") + Chef::Log.debug(appcmd(node)) + # 'SITE "Default Web Site" (id:1,bindings:http/*:80:,state:Started)' + Chef::Log.debug("#{new_resource} list site command output: #{cmd.stdout}") + if cmd.stderr.empty? + result = cmd.stdout.gsub(/\r\n?/, "\n") # ensure we have no carriage returns + result = result.match(/^SITE\s\"(#{new_resource.site_name})\"\s\(id:(.*),bindings:(.*),state:(.*)\)$/) + Chef::Log.debug("#{new_resource} current_resource match output: #{result}") + if result + @current_resource.site_id(result[2].to_i) + @current_resource.exists = true + @current_resource.bindings(result[3]) + @current_resource.running = (result[4] =~ /Started/) ? true : false + else + @current_resource.exists = false + @current_resource.running = false + end + else + log "Failed to run iis_site action :config, #{cmd.stderr}" do + level :warn + end + end +end + +private + def configure + was_updated = false + cmd_current_values = "#{appcmd(node)} list site \"#{new_resource.site_name}\" /config:* /xml" + Chef::Log.debug(cmd_current_values) + cmd_current_values = shell_out(cmd_current_values) + if cmd_current_values.stderr.empty? + xml = cmd_current_values.stdout + doc = Document.new(xml) + is_new_bindings = new_value?(doc.root, 'SITE/@bindings', new_resource.bindings.to_s) + is_new_physical_path = new_or_empty_value?(doc.root, 'SITE/site/application/virtualDirectory/@physicalPath', new_resource.path.to_s) + is_new_port_host_provided = !"#{XPath.first(doc.root, 'SITE/@bindings')},".include?("#{new_resource.protocol}/*:#{new_resource.port}:#{new_resource.host_header},") + is_new_site_id = new_value?(doc.root, 'SITE/site/@id', new_resource.site_id.to_s) + is_new_log_directory = new_or_empty_value?(doc.root, 'SITE/logFiles/@directory', new_resource.log_directory.to_s) + is_new_log_period = new_or_empty_value?(doc.root, 'SITE/logFile/@period', new_resource.log_period.to_s) + is_new_log_trunc = new_or_empty_value?(doc.root, 'SITE/logFiles/@truncateSize', new_resource.log_truncsize.to_s) + is_new_application_pool = new_value?(doc.root, 'SITE/site/application/@applicationPool', new_resource.application_pool) + + if (new_resource.bindings && is_new_bindings) + was_updated = true + cmd = "#{appcmd(node)} set site /site.name:\"#{new_resource.site_name}\"" + cmd << " /bindings:\"#{new_resource.bindings}\"" + shell_out!(cmd) + new_resource.updated_by_last_action(true) + elsif (((new_resource.port || new_resource.host_header || new_resource.protocol) && is_new_port_host_provided) && !new_resource.bindings) + was_updated = true + cmd = "#{appcmd(node)} set site \"#{new_resource.site_name}\"" + cmd << " /bindings:#{new_resource.protocol}/*:#{new_resource.port}:#{new_resource.host_header}" + Chef::Log.debug(cmd) + shell_out!(cmd) + new_resource.updated_by_last_action(true) + end + + if new_resource.application_pool && is_new_application_pool + was_updated = true + cmd = "#{appcmd(node)} set app \"#{new_resource.site_name}/\" /applicationPool:\"#{new_resource.application_pool}\"" + Chef::Log.debug(cmd) + shell_out!(cmd, returns: [0, 42]) + end + + if new_resource.path && is_new_physical_path + was_updated = true + cmd = "#{appcmd(node)} set vdir \"#{new_resource.site_name}/\"" + cmd << " /physicalPath:\"#{windows_cleanpath(new_resource.path)}\"" + Chef::Log.debug(cmd) + shell_out!(cmd) + end + + if new_resource.site_id && is_new_site_id + cmd = "#{appcmd(node)} set site \"#{new_resource.site_name}\"" + cmd << " /id:#{new_resource.site_id}" + Chef::Log.debug(cmd) + shell_out!(cmd) + new_resource.updated_by_last_action(true) + end + + if new_resource.log_directory && is_new_log_directory + cmd = "#{appcmd(node)} set site \"#{new_resource.site_name}\"" + cmd << " /logFile.directory:#{windows_cleanpath(new_resource.log_directory)}" + Chef::Log.debug(cmd) + shell_out!(cmd) + new_resource.updated_by_last_action(true) + end + + if new_resource.log_period && is_new_log_period + cmd = "#{appcmd(node)} set site \"#{new_resource.site_name}\"" + cmd << " /logFile.period:#{new_resource.log_period}" + Chef::Log.debug(cmd) + shell_out!(cmd) + new_resource.updated_by_last_action(true) + end + + if new_resource.log_truncsize && is_new_log_trunc + cmd = "#{appcmd(node)} set site \"#{new_resource.site_name}\"" + cmd << " /logFile.truncateSize:#{new_resource.log_truncsize}" + Chef::Log.debug(cmd) + shell_out!(cmd) + new_resource.updated_by_last_action(true) + end + + if was_updated + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} configured site '#{new_resource.site_name}'") + else + Chef::Log.debug("#{new_resource} site - nothing to do") + end + else + log "Failed to run iis_site action :config, #{cmd_current_values.stderr}" do + level :warn + end + end + end diff --git a/cookbooks/iis/providers/vdir.rb b/cookbooks/iis/providers/vdir.rb new file mode 100644 index 0000000..b2d2332 --- /dev/null +++ b/cookbooks/iis/providers/vdir.rb @@ -0,0 +1,155 @@ +# +# Author:: Justin Schuhmann () +# Cookbook Name:: iis +# Provider:: site +# +# Copyright:: Justin Schuhmann +# +# 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. +# + +require 'chef/mixin/shell_out' +require 'rexml/document' + +include Chef::Mixin::ShellOut +include REXML +include Opscode::IIS::Helper + +action :add do + if !@current_resource.exists + cmd = "#{appcmd(node)} add vdir /app.name:\"#{new_resource.application_name}\"" + cmd << " /path:\"#{new_resource.path}\"" + cmd << " /physicalPath:\"#{windows_cleanpath(new_resource.physical_path)}\"" + cmd << " /userName:\"#{new_resource.username}\"" if new_resource.username + cmd << " /password:\"#{new_resource.password}\"" if new_resource.password + cmd << " /logonMethod:#{new_resource.logon_method}" if new_resource.logon_method + cmd << " /allowSubDirConfig:#{new_resource.allow_sub_dir_config}" if new_resource.allow_sub_dir_config + + Chef::Log.info(cmd) + shell_out!(cmd, returns: [0, 42, 183]) + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} added new virtual directory to application: '#{new_resource.application_name}'") + else + Chef::Log.debug("#{new_resource} virtual directory already exists - nothing to do") + end +end + +action :config do + was_updated = false + cmd_current_values = "#{appcmd(node)} list vdir \"#{application_identifier}\" /config:* /xml" + Chef::Log.debug(cmd_current_values) + cmd_current_values = shell_out!(cmd_current_values) + if cmd_current_values.stderr.empty? + xml = cmd_current_values.stdout + doc = Document.new(xml) + is_new_physical_path = new_or_empty_value?(doc.root, 'VDIR/@physicalPath', new_resource.physical_path.to_s) + is_new_user_name = new_or_empty_value?(doc.root, 'VDIR/virtualDirectory/@userName', new_resource.username.to_s) + is_new_password = new_or_empty_value?(doc.root, 'VDIR/virtualDirectory/@password', new_resource.password.to_s) + is_new_logon_method = new_or_empty_value?(doc.root, 'VDIR/virtualDirectory/@logonMethod', new_resource.logon_method.to_s) + is_new_allow_sub_dir_config = new_or_empty_value?(doc.root, 'VDIR/virtualDirectory/@allowSubDirConfig', new_resource.allow_sub_dir_config.to_s) + + if new_resource.physical_path && is_new_physical_path + was_updated = true + cmd = "#{appcmd(node)} set vdir \"#{application_identifier}\" /physicalPath:\"#{new_resource.physical_path}\"" + Chef::Log.debug(cmd) + shell_out!(cmd) + end + + if new_resource.username && is_new_user_name + was_updated = true + cmd = "#{appcmd(node)} set vdir \"#{application_identifier}\" /userName:\"#{new_resource.username}\"" + Chef::Log.debug(cmd) + shell_out!(cmd) + end + + if new_resource.password && is_new_password + was_updated = true + cmd = "#{appcmd(node)} set vdir \"#{application_identifier}\" /password:\"#{new_resource.password}\"" + Chef::Log.debug(cmd) + shell_out!(cmd) + end + + if new_resource.logon_method && is_new_logon_method + was_updated = true + cmd = "#{appcmd(node)} set vdir \"#{application_identifier}\" /logonMethod:#{new_resource.logon_method}" + Chef::Log.debug(cmd) + shell_out!(cmd) + end + + if new_resource.allow_sub_dir_config && is_new_allow_sub_dir_config + was_updated = true + cmd = "#{appcmd(node)} set vdir \"#{application_identifier}\" /allowSubDirConfig:#{new_resource.allow_sub_dir_config}" + Chef::Log.debug(cmd) + shell_out!(cmd) + end + + if was_updated + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} configured virtual directory to application: '#{new_resource.application_name}'") + else + Chef::Log.debug("#{new_resource} virtual directory - nothing to do") + end + else + log "Failed to run iis_vdir action :config, #{cmd_current_values.stderr}" do + level :warn + end + end +end + +action :delete do + if @current_resource.exists + shell_out!("#{appcmd(node)} delete vdir \"#{application_identifier}\"", returns: [0, 42]) + new_resource.updated_by_last_action(true) + Chef::Log.info("#{new_resource} deleted") + else + Chef::Log.debug("#{new_resource} virtual directory does not exist - nothing to do") + end +end + +def load_current_resource + @current_resource = Chef::Resource::IisVdir.new(new_resource.name) + @current_resource.application_name(application_name_check) + @current_resource.path(new_resource.path) + @current_resource.physical_path(new_resource.physical_path) + cmd = shell_out("#{ appcmd(node) } list vdir \"#{application_identifier}\"") + Chef::Log.debug("#{ new_resource } list vdir command output: #{ cmd.stdout }") + + if cmd.stderr.empty? + # VDIR "Testfu Site/Content/Test" + result = cmd.stdout.match(/^VDIR\s\"#{Regexp.escape(application_identifier)}\"/) + Chef::Log.debug("#{ new_resource } current_resource match output: #{ result }") + if result + @current_resource.exists = true + else + @current_resource.exists = false + end + else + log "Failed to run iis_vdir action :load_current_resource, #{cmd_current_values.stderr}" do + level :warn + end + end +end + +private + +def application_identifier + new_resource.application_name.chomp('/') + new_resource.path +end + +def application_name_check + if !new_resource.application_name.include?('/') && !new_resource.application_name.end_with?('/') + new_resource.application_name("#{new_resource.application_name}/") + elsif new_resource.application_name.chomp('/').include?('/') && new_resource.application_name.end_with?('/') + new_resource.application_name(new_resource.application_name.chomp('/')) + end +end diff --git a/cookbooks/iis/recipes/default.rb b/cookbooks/iis/recipes/default.rb new file mode 100644 index 0000000..5c8c6e5 --- /dev/null +++ b/cookbooks/iis/recipes/default.rb @@ -0,0 +1,34 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Recipe:: default +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +# Always add this, so that we don't require this to be added if we want to add other components +default = Opscode::IIS::Helper.older_than_windows2008r2? ? 'Web-Server' : 'IIS-WebServerRole' + +(node['iis']['components'] + [default]).each do |feature| + windows_feature feature do + action :install + all !Opscode::IIS::Helper.older_than_windows2012? + end +end + +service 'iis' do + service_name 'W3SVC' + action [:enable, :start] +end diff --git a/cookbooks/iis/recipes/mod_application_initialization.rb b/cookbooks/iis/recipes/mod_application_initialization.rb new file mode 100644 index 0000000..b9c05cb --- /dev/null +++ b/cookbooks/iis/recipes/mod_application_initialization.rb @@ -0,0 +1,29 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Recipe:: mod_application_initialization +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + log 'Application Initialization module is not supported on Windows 2008 or lower, ignoring' +else + windows_feature 'IIS-ApplicationInit' do + action :install + end +end diff --git a/cookbooks/iis/recipes/mod_aspnet.rb b/cookbooks/iis/recipes/mod_aspnet.rb new file mode 100644 index 0000000..403a16f --- /dev/null +++ b/cookbooks/iis/recipes/mod_aspnet.rb @@ -0,0 +1,34 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Recipe:: mod_aspnet +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +include_recipe 'iis' +include_recipe 'iis::mod_isapi' + +if Opscode::IIS::Helper.older_than_windows2008r2? + features = %w(NET-Framework) +else + features = %w(IIS-NetFxExtensibility IIS-ASPNET) +end + +features.each do |feature| + windows_feature feature do + action :install + end +end diff --git a/cookbooks/iis/recipes/mod_aspnet45.rb b/cookbooks/iis/recipes/mod_aspnet45.rb new file mode 100644 index 0000000..92c0f0d --- /dev/null +++ b/cookbooks/iis/recipes/mod_aspnet45.rb @@ -0,0 +1,34 @@ +# +# Author:: Blair Hamilton () +# Cookbook Name:: iis +# Recipe:: mod_aspnet45 +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +include_recipe 'iis' +include_recipe 'iis::mod_isapi' + +if Opscode::IIS::Helper.older_than_windows2008r2? + features = %w(NET-Framework) +else + features = %w(NetFx4Extended-ASPNET45 IIS-NetFxExtensibility45 IIS-ASPNET45) +end + +features.each do |feature| + windows_feature feature do + action :install + end +end diff --git a/cookbooks/iis/recipes/mod_auth_anonymous.rb b/cookbooks/iis/recipes/mod_auth_anonymous.rb new file mode 100644 index 0000000..cc909c9 --- /dev/null +++ b/cookbooks/iis/recipes/mod_auth_anonymous.rb @@ -0,0 +1,26 @@ +# +# Author:: Justin Schuhmann +# Cookbook Name:: iis +# Recipe:: mod_auth_basic +# +# Copyright:: Justin Schuhmann +# +# 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. +# + +include_recipe 'iis' + +iis_section 'unlocks anonymous authentication control in web.config' do + section 'system.webServer/security/authentication/anonymousAuthentication' + action :unlock +end diff --git a/cookbooks/iis/recipes/mod_auth_basic.rb b/cookbooks/iis/recipes/mod_auth_basic.rb new file mode 100644 index 0000000..68bf16a --- /dev/null +++ b/cookbooks/iis/recipes/mod_auth_basic.rb @@ -0,0 +1,36 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Recipe:: mod_auth_basic +# +# Copyright:: Copyright (c) 2011 Chef Software, Inc. +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + feature = 'Web-Basic-Auth' +else + feature = 'IIS-BasicAuthentication' +end + +windows_feature feature do + action :install +end + +iis_section 'unlocks basic authentication control in web.config' do + section 'system.webServer/security/authentication/basicAuthentication' + action :unlock +end diff --git a/cookbooks/iis/recipes/mod_auth_digest.rb b/cookbooks/iis/recipes/mod_auth_digest.rb new file mode 100644 index 0000000..e7ce189 --- /dev/null +++ b/cookbooks/iis/recipes/mod_auth_digest.rb @@ -0,0 +1,36 @@ +# +# Author:: Justin Schuhmann +# Cookbook Name:: iis +# Recipe:: mod_auth_basic +# +# Copyright:: Justin Schuhmann +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + feature = 'Web-Digest-Auth' +else + feature = 'IIS-DigestAuthentication' +end + +windows_feature feature do + action :install +end + +iis_section 'unlocks digest authentication control in web.config' do + section 'system.webServer/security/authentication/digestAuthentication' + action :unlock +end diff --git a/cookbooks/iis/recipes/mod_auth_windows.rb b/cookbooks/iis/recipes/mod_auth_windows.rb new file mode 100644 index 0000000..bb89d79 --- /dev/null +++ b/cookbooks/iis/recipes/mod_auth_windows.rb @@ -0,0 +1,36 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Recipe:: mod_auth_windows +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + feature = 'Web-Windows-Auth' +else + feature = 'IIS-WindowsAuthentication' +end + +windows_feature feature do + action :install +end + +iis_section 'unlocks windows authentication control in web.config' do + section 'system.webServer/security/authentication/windowsAuthentication' + action :unlock +end diff --git a/cookbooks/iis/recipes/mod_cgi.rb b/cookbooks/iis/recipes/mod_cgi.rb new file mode 100644 index 0000000..f10f300 --- /dev/null +++ b/cookbooks/iis/recipes/mod_cgi.rb @@ -0,0 +1,31 @@ +# +# Author:: Richard Downer () +# Cookbook Name:: iis +# Recipe:: mod_cgi +# +# Copyright 2013, Cloudsoft Corporation +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + feature = 'Web-CGI' +else + feature = 'IIS-CGI' +end + +windows_feature feature do + action :install +end diff --git a/cookbooks/iis/recipes/mod_compress_dynamic.rb b/cookbooks/iis/recipes/mod_compress_dynamic.rb new file mode 100644 index 0000000..4603b79 --- /dev/null +++ b/cookbooks/iis/recipes/mod_compress_dynamic.rb @@ -0,0 +1,31 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Recipe:: mod_compress_dynamic +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + feature = 'Web-Dyn-Compression' +else + feature = 'IIS-HttpCompressionDynamic' +end + +windows_feature feature do + action :install +end diff --git a/cookbooks/iis/recipes/mod_compress_static.rb b/cookbooks/iis/recipes/mod_compress_static.rb new file mode 100644 index 0000000..a5a99a2 --- /dev/null +++ b/cookbooks/iis/recipes/mod_compress_static.rb @@ -0,0 +1,31 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Recipe:: mod_compress_static +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + feature = 'Web-Stat-Compression' +else + feature = 'IIS-HttpCompressionStatic' +end + +windows_feature feature do + action :install +end diff --git a/cookbooks/iis/recipes/mod_ftp.rb b/cookbooks/iis/recipes/mod_ftp.rb new file mode 100644 index 0000000..aa176c8 --- /dev/null +++ b/cookbooks/iis/recipes/mod_ftp.rb @@ -0,0 +1,33 @@ +# +# Author:: Kevin Rivers () +# Cookbook Name:: iis +# Recipe:: mod_ftp +# +# Copyright 2014, Kevin Rivers +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + features = %w(Web-Ftp-Server Web-Ftp-Service Web-Ftp-Ext) +else + features = %w(IIS-FTPServer IIS-FTPSvc IIS-FTPExtensibility) +end + +features.each do |f| + windows_feature f do + action :install + end +end diff --git a/cookbooks/iis/recipes/mod_iis6_metabase_compat.rb b/cookbooks/iis/recipes/mod_iis6_metabase_compat.rb new file mode 100644 index 0000000..698fe8b --- /dev/null +++ b/cookbooks/iis/recipes/mod_iis6_metabase_compat.rb @@ -0,0 +1,33 @@ +# +# Author:: Kristian Vlaardingerbroek () +# Cookbook Name:: iis +# Recipe:: mod_iis6_metabase_compat +# +# Copyright 2013, Schuberg Philis B.V. +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + features = %w(Web-Mgmt-Compat Web-Metabase) +else + features = %w(IIS-IIS6ManagementCompatibility IIS-Metabase) +end + +features.each do |f| + windows_feature f do + action :install + end +end diff --git a/cookbooks/iis/recipes/mod_isapi.rb b/cookbooks/iis/recipes/mod_isapi.rb new file mode 100644 index 0000000..9bb2249 --- /dev/null +++ b/cookbooks/iis/recipes/mod_isapi.rb @@ -0,0 +1,33 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Recipe:: mod_isapi +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + features = %w(Web-ISAPI-Filter Web-ISAPI-Ext) +else + features = %w(IIS-ISAPIFilter IIS-ISAPIExtensions) +end + +features.each do |feature| + windows_feature feature do + action :install + end +end diff --git a/cookbooks/iis/recipes/mod_logging.rb b/cookbooks/iis/recipes/mod_logging.rb new file mode 100644 index 0000000..21a1b92 --- /dev/null +++ b/cookbooks/iis/recipes/mod_logging.rb @@ -0,0 +1,31 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Recipe:: mod_logging +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + feature = 'Web-Http-Logging' +else + feature = 'IIS-CustomLogging' +end + +windows_feature feature do + action :install +end diff --git a/cookbooks/iis/recipes/mod_management.rb b/cookbooks/iis/recipes/mod_management.rb new file mode 100644 index 0000000..ee98a87 --- /dev/null +++ b/cookbooks/iis/recipes/mod_management.rb @@ -0,0 +1,33 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Recipe:: mod_management +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + features = %w(Web-Mgmt-Console Web-Mgmt-Service) +else + features = %w(IIS-ManagementConsole IIS-ManagementService) +end + +features.each do |feature| + windows_feature feature do + action :install + end +end diff --git a/cookbooks/iis/recipes/mod_security.rb b/cookbooks/iis/recipes/mod_security.rb new file mode 100644 index 0000000..37d7b40 --- /dev/null +++ b/cookbooks/iis/recipes/mod_security.rb @@ -0,0 +1,33 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Recipe:: mod_security +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + features = %w(Web-Url-Auth Web-Filtering Web-IP-Security) +else + features = %w(IIS-URLAuthorization IIS-RequestFiltering IIS-IPSecurity) +end + +features.each do |feature| + windows_feature feature do + action :install + end +end diff --git a/cookbooks/iis/recipes/mod_tracing.rb b/cookbooks/iis/recipes/mod_tracing.rb new file mode 100644 index 0000000..54a7168 --- /dev/null +++ b/cookbooks/iis/recipes/mod_tracing.rb @@ -0,0 +1,31 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Recipe:: mod_diagnostics +# +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +include_recipe 'iis' + +if Opscode::IIS::Helper.older_than_windows2008r2? + feature = 'Web-Http-Tracing' +else + feature = 'IIS-HTTPTracing' +end + +windows_feature feature do + action :install +end diff --git a/cookbooks/iis/recipes/remove_default_site.rb b/cookbooks/iis/recipes/remove_default_site.rb new file mode 100644 index 0000000..606f057 --- /dev/null +++ b/cookbooks/iis/recipes/remove_default_site.rb @@ -0,0 +1,27 @@ +# +# Author:: Kendrick Martin () +# Cookbook Name:: iis +# Recipe:: remove_default_site +# +# Copyright 2012, Webtrends, Inc. +# +# 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. +# + +iis_site 'Default Web Site' do + action [:stop, :delete] +end + +iis_pool 'DefaultAppPool' do + action [:stop, :delete] +end diff --git a/cookbooks/iis/resources/app.rb b/cookbooks/iis/resources/app.rb new file mode 100644 index 0000000..b3993e4 --- /dev/null +++ b/cookbooks/iis/resources/app.rb @@ -0,0 +1,29 @@ +# +# Author:: Kendrick Martin (kendrick.martin@webtrends.com>) +# Cookbook Name:: iis +# Resource:: app +# +# Copyright:: 2011, Webtrends Inc. +# +# 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. +# + +actions :add, :delete, :config +default_action :add + +attribute :site_name, kind_of: String, name_attribute: true +attribute :path, kind_of: String, default: '/' +attribute :application_pool, kind_of: String +attribute :physical_path, kind_of: String +attribute :enabled_protocols, kind_of: String +attr_accessor :exists, :running diff --git a/cookbooks/iis/resources/config.rb b/cookbooks/iis/resources/config.rb new file mode 100644 index 0000000..ad65fe8 --- /dev/null +++ b/cookbooks/iis/resources/config.rb @@ -0,0 +1,25 @@ +# +# Author:: Kendrick Martin (kendrick.martin@webtrends.com) +# Cookbook Name:: iis +# Resource:: config +# +# Copyright:: 2011, Webtrends Inc. +# +# 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. +# + +actions :config +default_action :config + +attribute :cfg_cmd, kind_of: String, name_attribute: true +attribute :returns, kind_of: [Integer, Array], default: 0 diff --git a/cookbooks/iis/resources/module.rb b/cookbooks/iis/resources/module.rb new file mode 100644 index 0000000..4ca8360 --- /dev/null +++ b/cookbooks/iis/resources/module.rb @@ -0,0 +1,29 @@ +# +# Author:: Jon DeCamp () +# Cookbook Name:: iis +# Resource:: module +# +# Copyright:: 2012, Nordstrom, Inc. +# +# 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. +# + +actions :add, :delete +default_action :add + +attribute :module_name, kind_of: String, name_attribute: true +attribute :type, kind_of: String, default: nil +attribute :precondition, kind_of: String, default: nil +attribute :application, kind_of: String, default: nil + +attr_accessor :exists diff --git a/cookbooks/iis/resources/pool.rb b/cookbooks/iis/resources/pool.rb new file mode 100644 index 0000000..72a2a16 --- /dev/null +++ b/cookbooks/iis/resources/pool.rb @@ -0,0 +1,78 @@ +# +# Author:: Kendrick Martin (kendrick.martin@webtrends.com>) +# Contributor:: David Dvorak (david.dvorak@webtrends.com) +# Cookbook Name:: iis +# Resource:: pool +# +# Copyright:: 2011, Webtrends Inc. +# +# 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. +# + +actions :add, :config, :delete, :start, :stop, :restart, :recycle +default_action :add + +# root +attribute :pool_name, kind_of: String, name_attribute: true +attribute :no_managed_code, kind_of: [TrueClass, FalseClass], default: false +attribute :pipeline_mode, kind_of: Symbol, equal_to: [:Integrated, :Classic] +attribute :runtime_version, kind_of: String + +# add items +attribute :start_mode, kind_of: Symbol, equal_to: [:AlwaysRunning, :OnDemand], default: :OnDemand +attribute :auto_start, kind_of: [TrueClass, FalseClass], default: true +attribute :queue_length, kind_of: Integer, default: 1000 +attribute :thirty_two_bit, kind_of: [TrueClass, FalseClass], default: false + +# processModel items +attribute :max_proc, kind_of: Integer +attribute :load_user_profile, kind_of: [TrueClass, FalseClass], default: false +attribute :pool_identity, kind_of: Symbol, equal_to: [:SpecificUser, :NetworkService, :LocalService, :LocalSystem, :ApplicationPoolIdentity], default: :ApplicationPoolIdentity +attribute :pool_username, kind_of: String +attribute :pool_password, kind_of: String +attribute :logon_type, kind_of: Symbol, equal_to: [:LogonBatch, :LogonService], default: :LogonBatch +attribute :manual_group_membership, kind_of: [TrueClass, FalseClass], default: false +attribute :idle_timeout, kind_of: String, default: '00:20:00' +attribute :shutdown_time_limit, kind_of: String, default: '00:01:30' +attribute :startup_time_limit, kind_of: String, default: '00:01:30' +attribute :pinging_enabled, kind_of: [TrueClass, FalseClass], default: true +attribute :ping_interval, kind_of: String, default: '00:00:30' +attribute :ping_response_time, kind_of: String, default: '00:01:30' + +# recycling items +attribute :disallow_rotation_on_config_change, kind_of: [TrueClass, FalseClass], default: false +attribute :disallow_overlapping_rotation, kind_of: [TrueClass, FalseClass], default: false +attribute :recycle_after_time, kind_of: String +attribute :recycle_at_time, kind_of: String +attribute :private_mem, kind_of: Integer + +# failure items +attribute :load_balancer_capabilities, kind_of: Symbol, equal_to: [:HttpLevel, :TcpLevel], default: :HttpLevel +attribute :orphan_worker_process, kind_of: [TrueClass, FalseClass], default: false +attribute :orphan_action_exe, kind_of: String +attribute :orphan_action_params, kind_of: String +attribute :rapid_fail_protection, kind_of: [TrueClass, FalseClass], default: true +attribute :rapid_fail_protection_interval, kind_of: String, default: '00:05:00' +attribute :rapid_fail_protection_max_crashes, kind_of: Integer, default: 5 +attribute :auto_shutdown_exe, kind_of: String +attribute :auto_shutdown_params, kind_of: String + +# cpu items +attribute :cpu_action, kind_of: Symbol, equal_to: [:NoAction, :KillW3wp, :Throttle, :ThrottleUnderLoad], default: :NoAction +attribute :cpu_limit, kind_of: Integer, default: 0 +attribute :cpu_reset_interval, kind_of: String, default: '00:05:00' +attribute :cpu_smp_affinitized, kind_of: [TrueClass, FalseClass], default: false +attribute :smp_processor_affinity_mask, kind_of: Bignum, default: 4_294_967_295 +attribute :smp_processor_affinity_mask_2, kind_of: Bignum, default: 4_294_967_295 + +attr_accessor :exists, :running diff --git a/cookbooks/iis/resources/section.rb b/cookbooks/iis/resources/section.rb new file mode 100644 index 0000000..7d57614 --- /dev/null +++ b/cookbooks/iis/resources/section.rb @@ -0,0 +1,27 @@ +# +# Author:: Justin Schuhmann +# Cookbook Name:: iis +# Resource:: lock +# +# Copyright:: Justin Schuhmann +# +# 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. +# + +actions :lock, :unlock +default_action :lock + +attribute :section, kind_of: String +attribute :returns, kind_of: [Integer, Array], default: 0 + +attr_accessor :exists diff --git a/cookbooks/iis/resources/site.rb b/cookbooks/iis/resources/site.rb new file mode 100644 index 0000000..0b95215 --- /dev/null +++ b/cookbooks/iis/resources/site.rb @@ -0,0 +1,37 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: iis +# Resource:: site +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +actions :add, :delete, :start, :stop, :restart, :config +default_action :add + +attribute :site_name, kind_of: String, name_attribute: true +attribute :site_id, kind_of: Integer +attribute :port, kind_of: Integer, default: 80 +attribute :path, kind_of: String +attribute :protocol, kind_of: Symbol, default: :http, equal_to: [:http, :https] +attribute :host_header, kind_of: String, default: nil +attribute :bindings, kind_of: String, default: nil +attribute :application_pool, kind_of: String, default: nil +attribute :options, kind_of: String, default: '' +attribute :log_directory, kind_of: String, default: "#{node['iis']['pubroot']}\\logs\\LogFiles" +attribute :log_period, kind_of: Symbol, default: :Daily, equal_to: [:Daily, :Hourly, :MaxSize, :Monthly, :Weekly] +attribute :log_truncsize, kind_of: Integer, default: 1_048_576 + +attr_accessor :exists, :running diff --git a/cookbooks/iis/resources/vdir.rb b/cookbooks/iis/resources/vdir.rb new file mode 100644 index 0000000..980f55d --- /dev/null +++ b/cookbooks/iis/resources/vdir.rb @@ -0,0 +1,32 @@ +# +# Author:: Justin Schuhmann () +# Cookbook Name:: iis +# Resource:: site +# +# Copyright:: Justin Schuhmann +# +# 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. +# + +actions :add, :delete, :config +default_action :add + +attribute :application_name, kind_of: String, name_attribute: true +attribute :path, kind_of: String +attribute :physical_path, kind_of: String +attribute :username, kind_of: String, default: nil +attribute :password, kind_of: String, default: nil +attribute :logon_method, kind_of: Symbol, default: :ClearText, equal_to: [:Interactive, :Batch, :Network, :ClearText] +attribute :allow_sub_dir_config, kind_of: [TrueClass, FalseClass], default: true + +attr_accessor :exists diff --git a/cookbooks/mariadb/CHANGELOG.md b/cookbooks/mariadb/CHANGELOG.md new file mode 100644 index 0000000..7ca51c6 --- /dev/null +++ b/cookbooks/mariadb/CHANGELOG.md @@ -0,0 +1,120 @@ +mariadb CHANGELOG +================= + +This file is used to list changes made in each version of the mariadb cookbook. + +0.3.0 +------ +- [ENH] - Add support for using operating system shipped mariadb packages + +0.2.12 +------ +- [BUG #39] - Push gpg key adds through http/80 - Helps with firewalled installs +- [ENH #46] - Add cookbook attribute on configuration lwrp +- [ENH #47] - Allow to pass true for unary options +- [BUG #48] - Load the needed plugins at startup + +0.2.11 +------ +- [ENH #38] - Add CentOS support +- [ENH #40] - Add sensitive flag to resource that deal with passwords +- [BUG #43] - Fix convert TypeError in the replication provider + +0.2.10 +------ +- [BUG] - Audit Plugin test and installation - Correct bad notifies, and stdout test + +0.2.9 +----- +- [BUG #36] - Audit plugin installation can crash mariadb server + +0.2.8 +----- +- [BUG #30] - When using galera, nodes were not sorted, applying configuration change too often +- [BUG #31] - ChefSpec coverage was not 100% +- [BUG #28] - Remove the only_if to mysql service +- [BUG #29] - Add a switch to not launch audit plugin install, when already installed +- [ENH] - Add a switch to separate server install and audit install when needed +- [ENH] - Add a rule to authorize line length to be 120 characters long + +0.2.7 +----- +- [BUG #24] - Fix convert TypeError in the replication provider +- [BUG #25] - Data are now moved when default datadir is changed +- [ENH #21] - Add audit_plugin management + +0.2.6 +----- +- [BUG #18] - Fix provider mariadb_replication compilation error +- [DOCS] - Complete Changelog, and correct README + +0.2.5 +----- +- [ENH #16] - Add a LWRP to manage replication slave +- [ENH #17] - Be able to not install development files within client recipe +- [ENH #11] - Fix the galera root password preseed +- [BUG #12] - Fix the debian-sys-maint user creation/password change +- [BUG #6] - Can change the apt repository base_url when the default one fail +- [TEST] - Add new tests for the new features (galera,development files install,replication LWRP) +- [DOCS] - Complete Changelog, and add new features explanations into README + +0.2.4 +----- +- [BUG #10] - Correct a FC004 broken rule +- [BUG #9] - Correct foodcritic tests (add --epic-fail any to be sure it fails when a broken rule is detected) + +0.2.3 +----- +- [BUG #4] - Add a real management of mysql root password +- [ENH #5] - Now restart mysql service when port is changed +- [ENH #7] - Remove or add root remote access via attribute +- [DOCS] - Complete documentations +- [TEST] - Add a lot of chefspec and kitchen/serverspec tests + +0.2.2 +----- +- [sinfomicien] - Correct repository install under debian family +- [sinfomicien] - Correct client install to add dev files +- [sinfomicien] - Correct and add multiples tests + +0.2.1 +----- +- [sinfomicien] - Use stove to package (remove PaxHeaders.*) + +0.2.0 +----- +- [sinfomicien] - Add rpm/yum management +- [sinfomicien] - Refactor the whole recipes list and management to ease it +- [sinfomicien] - Correct the Documentation +- [sinfomicien] - Rename the provider (from extraconf to configuration), and add matchers to it +- [sinfomicien] - Add a recipe to manage client only installation +- [sinfomicien] - Refactor all tests to manage new platform (centos/redhat/fedora) + +0.1.8 +----- +- [sinfomicien] - Add ignore-failure to debian grants correct, as it can break on initial setup + +0.1.7 +----- +- [sinfomicien] - Correct a typo (unnecessary call to run_command) + +0.1.6 +----- +- [sinfomicien] - improve Galera configuration management +- [sinfomicien] - Add new rspec tests +- [sinfomicien] - Create Kitchen test suite + +0.1.5 +----- +- [sinfomicien] - improve attributes management + +0.1.4 +----- +- [sinfomicien] - adapt galera55 recipe to use a generic galera recipe +- [sinfomicien] - use a generic galera recipe to create the galera10 recipe +- [sinfomicien] - Improve documentation + + +0.1.0 +----- +- [sinfomicien] - Initial release of mariadb diff --git a/cookbooks/mariadb/README.md b/cookbooks/mariadb/README.md new file mode 100644 index 0000000..01fdaea --- /dev/null +++ b/cookbooks/mariadb/README.md @@ -0,0 +1,207 @@ +MariaDB Cookbook +================ + +[![Build Status](https://travis-ci.org/sinfomicien/mariadb.png)](https://travis-ci.org/sinfomicien/mariadb) + +Description +----------- + +This cookbook contains all the stuffs to install and configure a mariadb server on a dpkg/apt compliant system (typically debian), or a rpm/yum compliant system (typically centos) + + +Requirements +------------ + +#### repository +- `mariadb` - This cookbook need that you have a valid apt repository installed with the mariadb official packages + +#### packages +- `percona-xtrabackup` - if you want to use the xtrabckup SST Auth for galera cluster. +- `socat` - if you want to use the xtrabckup SST Auth for galera cluster. +- `rsync` - if you want to use the rsync SST Auth for galera cluster. +- `debconf-utils` - if you use debian platform family. + +#### operating system +- `debian` - this cookbook is fully tested on debian +- `ubuntu` - not fully tested on ubuntu, but should work +- `centos` - not fully tested on centos, but should work + +Attributes +---------- + +#### mariadb::default + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyTypeDescriptionDefault
['mariadb']['install']['version']StringVersion to install (currently 10.0 et 5.5)10.0
['mariadb']['use_default_repository']BooleanWether to install MariaDB default repository or not. If you don't have a local repo containing packages, put it to truefalse
['mariadb']['server_root_password']Stringlocal root password
['mariadb']['forbid_remote_root']BooleanWether to activate root remote accesstrue
['mariadb']['allow_root_pass_change']BooleanWether to allow the recipe to change root password after the first installfalse
['mariadb']['client']['development_files']BooleanWether to install development files in client recipetrue
['mariadb']['apt_repository']['base_url']StringThe http base url to use when installing from default repository'ftp.igh.cnrs.fr/pub/mariadb/repo'
['mariadb']['install']['prefer_os_package']BooleanIndicator for preferring use packages shipped by running osfalse
+ +Usage +----- + +To install a default server for mariadb choose the version you want (MariaDB 5.5 or 10, galera or not), then call the recipe accordingly. + +List of availables recipes: + +- mariadb::default (just call server recipe with default options) +- mariadb::server +- mariadb::galera +- mariadb::client + +Please be ware that by default, the root password is empty! If you want have changed it use the `node['mariadb']['server_root_password']` attribute to put a correct value. And by default the remote root access is not activated. Use `node['mariadb']['forbid_remote_root']` attribute to change it. + +Sometimes, the default apt repository used for apt does not work (see issue #6). In this case, you need to choose another mirror which worki (pick it from mariadb website), and put the http base url in the attribute `node['mariadb']['apt_repository']['base_url']`. + +#### mariadb::galera + +When installing the mariadb::galera on debian recipe, You have to take care of one specific attribute: +`node['mariadb']['debian']['password']` which default to 'please-change-me' +As wee need to have the same password for this user on the whole cluster nodes... We will change the default install one by the content of this attribute. + +#### mariadb::client + +By default this recipe install the client, and all needed packages to develop client application. If you do not want to install development files when installing client package, +set the attribute `node['mariadb']['client']['development_files']` to false. + +Providers +---------- + +This recipe define 2 providers: +- `Chef::Provider::Mariadb::Configuration` shortcut resource `mariadb_configuration` +- `Chef::Provider::Mariadb::Replication` shortcut resource `mariadb_replication` + +#### mariadb_configuration + +Mainly use for internal purpose. You can use it to create a new configuration file into configuration dir. You have to define 2 variables `section` and `option`. +Where `section` is the configuration section, and `option` is a hash of key/value. The name of the resource is used as base for the filename. + +Example: +```ruby +mariadb_configuration 'fake' do + section 'mysqld' + option {foo: 'bar'} +end +``` +will become the file fake.cnf in the include dir (depend on your platform), which contain: +``` +[mysqld] +foo=bar +``` + +If the value start with a '#', then it's considered as a comment, and the value is printed as is (without the key) + +Example: +```ruby +mariadb_configuration 'fake' do + section 'mysqld' + option {comment1: '# Here i am', foo: bar} +end +``` +will become the file fake.cnf in the include dir (depend on your platform), which contain: +``` +[mysqld] +# Here i am +foo=bar +``` + +#### mariadb_replication + +This LWRP is used to manage replication setup on a host. To use this LWRP, the node need to have the mysql binary installed (via the mariadb::client or mariadb::server or mariadb::galera recipe). +It have 4 actions: +- add - to add a new replication setup (become a slave) +- stop - to stop the slave replication +- start - to start the slave replication +- remove - to remove the slave replication configuration + +The resource name need to be 'default' if your don't want to use a named connection (multi source replication in MariaDB 10). + +So by default the provider try to use the local instance of mysql, with the current user and no password. If you want to change, you have to define `host`, `port`, `user` or `password` + +```ruby +mariadb_replication 'default' do + user 'root' + password 'fakepass' + host 'fakehost' + action :stop +end +``` +will stop the replication on the host `fakehost` using the user `root` and password `fakepass` to connect to. + +When you add a replication configuration, you have to define at least 4 values `master_host`, `master_user`, `master_password` and `master_use_gtid`. And if you don't want the GTID support, you have to define also `master_log_file` and `master_log_pos` + +Example: +```ruby +mariadb_replication 'usefull_conn_name' do + master_host 'server1' + master_user 'slave_user' + master_password 'slave_password' + master_use_gtid 'current_pos' + action :add +end +``` + +Contributing +------------ + +1. Fork the repository on Github +2. Create a named feature branch (like `add_component_x`) +3. Write your change +4. Write tests for your change (if applicable) +5. Run the tests, ensuring they all pass +6. Submit a Pull Request using Github + +License and Authors +------------------- +Authors: +Nicolas Blanc diff --git a/cookbooks/mariadb/attributes/default.rb b/cookbooks/mariadb/attributes/default.rb new file mode 100644 index 0000000..c33dcae --- /dev/null +++ b/cookbooks/mariadb/attributes/default.rb @@ -0,0 +1,156 @@ +# platform dependent attributes +case node['platform'] +when 'redhat', 'centos', 'fedora', 'scientific', 'amazon' + default['mariadb']['configuration']['path'] = '/etc' + default['mariadb']['configuration']['includedir'] = '/etc/my.cnf.d' + default['mariadb']['mysqld']['socket'] = '/var/lib/mysql/mysql.sock' + default['mariadb']['client']['socket'] = '/var/lib/mysql/mysql.sock' + default['mariadb']['mysqld_safe']['socket'] = '/var/lib/mysql/mysql.sock' +else + default['mariadb']['configuration']['path'] = '/etc/mysql' + default['mariadb']['configuration']['includedir'] = '/etc/mysql/conf.d' + default['mariadb']['mysqld']['socket'] = '/var/run/mysqld/mysqld.sock' + default['mariadb']['mysqld']['pid_file'] = '/var/run/mysqld/mysqld.pid' + default['mariadb']['client']['socket'] = '/var/run/mysqld/mysqld.sock' + default['mariadb']['mysqld_safe']['socket'] = '/var/run/mysqld/mysqld.sock' +end + +# +# mysqld default configuration +# +default['mariadb']['forbid_remote_root'] = true +default['mariadb']['server_root_password'] = '' +default['mariadb']['allow_root_pass_change'] = false +default['mariadb']['mysqld']['service_name'] = 'mysql' +default['mariadb']['mysqld']['user'] = 'mysql' +default['mariadb']['mysqld']['port'] = '3306' +default['mariadb']['mysqld']['basedir'] = '/usr' +default['mariadb']['mysqld']['default_datadir'] = '/var/lib/mysql' +# if different from previous value, datadir will be moved after install +default['mariadb']['mysqld']['datadir'] = '/var/lib/mysql' +default['mariadb']['mysqld']['tmpdir'] = '/var/tmp' +default['mariadb']['mysqld']['lc_messages_dir'] = '/usr/share/mysql' +default['mariadb']['mysqld']['lc_messages'] = 'en_US' +default['mariadb']['mysqld']['skip_external_locking'] = 'true' +default['mariadb']['mysqld']['bind_address'] = '127.0.0.1' +default['mariadb']['mysqld']['max_connections'] = '100' +default['mariadb']['mysqld']['connect_timeout'] = '5' +default['mariadb']['mysqld']['wait_timeout'] = '600' +default['mariadb']['mysqld']['max_allowed_packet'] = '16M' +default['mariadb']['mysqld']['thread_cache_size'] = '128' +default['mariadb']['mysqld']['sort_buffer_size'] = '4M' +default['mariadb']['mysqld']['bulk_insert_buffer_size'] = '16M' +default['mariadb']['mysqld']['tmp_table_size'] = '32M' +default['mariadb']['mysqld']['max_heap_table_size'] = '32M' +default['mariadb']['mysqld']['myisam_recover'] = 'BACKUP' +default['mariadb']['mysqld']['key_buffer_size'] = '128M' +# if not defined default is 2000 +default['mariadb']['mysqld']['open_files_limit'] = '' +default['mariadb']['mysqld']['table_open_cache'] = '400' +default['mariadb']['mysqld']['myisam_sort_buffer_size'] = '512M' +default['mariadb']['mysqld']['concurrent_insert'] = '2' +default['mariadb']['mysqld']['read_buffer_size'] = '2M' +default['mariadb']['mysqld']['read_rnd_buffer_size'] = '1M' +default['mariadb']['mysqld']['query_cache_limit'] = '128K' +default['mariadb']['mysqld']['query_cache_size'] = '64M' +# if not defined default is ON +default['mariadb']['mysqld']['query_cache_type'] = '' +default['mariadb']['mysqld']['default_storage_engine'] = 'InnoDB' +default['mariadb']['mysqld']['options'] = {} + +# +# InnoDB default configuration +# +# if not defined default is 50M +default['mariadb']['innodb']['log_file_size'] = '' +default['mariadb']['innodb']['bps_percentage_memory'] = false +default['mariadb']['innodb']['buffer_pool_size'] = '256M' +default['mariadb']['innodb']['log_buffer_size'] = '8M' +default['mariadb']['innodb']['file_per_table'] = '1' +default['mariadb']['innodb']['open_files'] = '400' +default['mariadb']['innodb']['io_capacity'] = '400' +default['mariadb']['innodb']['flush_method'] = 'O_DIRECT' +default['mariadb']['innodb']['options'] = {} + +# +# Galera default configuration +# +default['mariadb']['galera']['cluster_name'] = 'galera_cluster' +default['mariadb']['galera']['cluster_search_query'] = '' +default['mariadb']['galera']['wsrep_sst_method'] = 'rsync' +default['mariadb']['galera']['wsrep_provider'] = \ + '/usr/lib/galera/libgalera_smm.so' +default['mariadb']['galera']['options'] = {} + +# +# Replication default configuration +# +default['mariadb']['replication']['server_id'] = '' +default['mariadb']['replication']['log_bin'] = \ + '/var/log/mysql/mariadb-bin' +default['mariadb']['replication']['log_bin_index'] = \ + '/var/log/mysql/mariadb-bin.index' +default['mariadb']['replication']['expire_logs_days'] = '10' +default['mariadb']['replication']['max_binlog_size'] = '100M' + +# +# mysqldump default configuration +# +default['mariadb']['mysqldump']['quick'] = 'true' +default['mariadb']['mysqldump']['quote_names'] = 'true' +default['mariadb']['mysqldump']['max_allowed_packet'] = '16M' + +# +# isamchk default configuration +default['mariadb']['isamchk']['key_buffer'] = '16M' + +# +# mysqld_safe default configuration +# +default['mariadb']['mysqld_safe']['options'] = {} + +# +# client default configuration +# +default['mariadb']['client']['port'] = 3306 +default['mariadb']['client']['options'] = {} +default['mariadb']['client']['development_files'] = true + +# +# debian specific configuration +# +default['mariadb']['debian']['user'] = 'debian-sys-maint' +default['mariadb']['debian']['password'] = 'please-change-me' +default['mariadb']['debian']['host'] = 'localhost' + +# +# mariadb default install configuration +# +# install valid value is 'package', +# hope to have 'from_source' in the near future +default['mariadb']['install']['type'] = 'package' +default['mariadb']['install']['version'] = '10.0' +default['mariadb']['install']['prefer_os_package'] = false + +# +# package(apt or yum) default configuration +# +default['mariadb']['use_default_repository'] = false +default['mariadb']['apt_repository']['base_url'] = \ + 'ftp.igh.cnrs.fr/pub/mariadb/repo' + +# +# MariaDB Plugins enabling +# +default['mariadb']['plugins_options']['auto_install'] = true +# Enabling Plugin Installation +default['mariadb']['plugins']['audit'] = false +# Load Plugins in .cnf (plugin-loadi variable) +default['mariadb']['plugins_loading']['audit'] = 'server_audit=server_audit.so' + +# Default Configuration +default['mariadb']['audit_plugin']['server_audit_events'] = '' +default['mariadb']['audit_plugin']['server_audit_output_type'] = 'file' +# Syslog (require server_audit_output_type = syslog) +default['mariadb']['audit_plugin']['server_audit_syslog_facility'] = 'LOG_USER' +default['mariadb']['audit_plugin']['server_audit_syslog_priority'] = 'LOG_INFO' diff --git a/cookbooks/mariadb/libraries/mariadb_helper.rb b/cookbooks/mariadb/libraries/mariadb_helper.rb new file mode 100644 index 0000000..6ce317a --- /dev/null +++ b/cookbooks/mariadb/libraries/mariadb_helper.rb @@ -0,0 +1,78 @@ +# MariaDB is a module containing mariadb cookbook helper +module MariaDB + # Helper module for mariadb cookbook + module Helper + require 'socket' + require 'timeout' + + def do_port_connect(ip, port) + s = TCPSocket.new(ip, port) + s.close + true + rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH + false + end + + def port_open?(ip, port) + begin + Timeout.timeout(5) do + return do_port_connect(ip, port) + end + rescue Timeout::Error + false + end + false + end + + # Trying to determine if we need to restart the mysql service + def mariadb_service_restart_required?(ip, port, _socket) + restart = false + restart = true unless port_open?(ip, port) + restart + end + + # Helper to determine if running operating system shipped a package for + # mariadb server & client. No galera shipped in any os yet. + # @param [String] os_platform Indicate operating system type, e.g. centos + # @param [String] os_version Indicate operating system version, e.g. 7.0 + def os_package_provided?(os_platform, os_version) + package_provided = false + case os_platform + when 'centos', 'redhat' + package_provided = true if os_version.to_i == 7 + when 'fedora' + package_provided = true if os_version.to_i >= 19 + end + package_provided + end + + # Helper to determine mariadb server service name shipped by native package + # If no native package available on this platform, return nil + # @param [String] os_platform Indicate operating system type, e.g. centos + # @param [String] os_version Indicate operating system version, e.g. 7.0 + def os_service_name(os_platform, os_version) + return nil unless os_package_provided?(os_platform, os_version) + service_name = 'mariadb' + if os_platform == 'fedora' && os_version.to_i == 19 + service_name = 'mysqld' + end + service_name + end + + # Helper to determine whether to use os native package + # @param [Boolean] prefer_os Indicate whether to prefer os native package + # @param [String] os_platform Indicate operating system type, e.g. centos + # @param [String] os_version Indicate operating system version, e.g. 7.0 + def use_os_native_package?(prefer_os, os_platform, os_version) + return false unless prefer_os + if os_package_provided?(os_platform, os_version) + true + else + Chef::Log.warn 'prefer_os_package detected, but no native mariadb'\ + " package available on #{os_platform}-#{os_version}."\ + ' Fall back to use community package.' + false + end + end + end +end diff --git a/cookbooks/mariadb/libraries/matchers.rb b/cookbooks/mariadb/libraries/matchers.rb new file mode 100644 index 0000000..91f1cc1 --- /dev/null +++ b/cookbooks/mariadb/libraries/matchers.rb @@ -0,0 +1,21 @@ +if defined?(ChefSpec) + def add_mariadb_configuration(resource_name) + ChefSpec::Matchers::ResourceMatcher + .new(:mariadb_configuration, :add, resource_name) + end + + def remove_mariadb_configuration(resource_name) + ChefSpec::Matchers::ResourceMatcher + .new(:mariadb_configuration, :remove, resource_name) + end + + def add_mariadb_replication(resource_name) + ChefSpec::Matchers::ResourceMatcher + .new(:mariadb_replication, :add, resource_name) + end + + def remove_mariadb_replication(resource_name) + ChefSpec::Matchers::ResourceMatcher + .new(:mariadb_replication, :remove, resource_name) + end +end diff --git a/cookbooks/mariadb/metadata.json b/cookbooks/mariadb/metadata.json new file mode 100644 index 0000000..6ea0725 --- /dev/null +++ b/cookbooks/mariadb/metadata.json @@ -0,0 +1,36 @@ +{ + "name": "mariadb", + "version": "0.3.0", + "description": "Installs/Configures MariaDB", + "long_description": "MariaDB Cookbook\n================\n\n[![Build Status](https://travis-ci.org/sinfomicien/mariadb.png)](https://travis-ci.org/sinfomicien/mariadb)\n\nDescription\n-----------\n\nThis cookbook contains all the stuffs to install and configure a mariadb server on a dpkg/apt compliant system (typically debian), or a rpm/yum compliant system (typically centos)\n\n\nRequirements\n------------\n\n#### repository\n- `mariadb` - This cookbook need that you have a valid apt repository installed with the mariadb official packages\n\n#### packages\n- `percona-xtrabackup` - if you want to use the xtrabckup SST Auth for galera cluster.\n- `socat` - if you want to use the xtrabckup SST Auth for galera cluster.\n- `rsync` - if you want to use the rsync SST Auth for galera cluster.\n- `debconf-utils` - if you use debian platform family.\n\n#### operating system\n- `debian` - this cookbook is fully tested on debian\n- `ubuntu` - not fully tested on ubuntu, but should work\n- `centos` - not fully tested on centos, but should work\n\nAttributes\n----------\n\n#### mariadb::default\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
KeyTypeDescriptionDefault
['mariadb']['install']['version']StringVersion to install (currently 10.0 et 5.5)10.0
['mariadb']['use_default_repository']BooleanWether to install MariaDB default repository or not. If you don't have a local repo containing packages, put it to truefalse
['mariadb']['server_root_password']Stringlocal root password
['mariadb']['forbid_remote_root']BooleanWether to activate root remote accesstrue
['mariadb']['allow_root_pass_change']BooleanWether to allow the recipe to change root password after the first installfalse
['mariadb']['client']['development_files']BooleanWether to install development files in client recipetrue
['mariadb']['apt_repository']['base_url']StringThe http base url to use when installing from default repository'ftp.igh.cnrs.fr/pub/mariadb/repo'
['mariadb']['install']['prefer_os_package']BooleanIndicator for preferring use packages shipped by running osfalse
\n\nUsage\n-----\n\nTo install a default server for mariadb choose the version you want (MariaDB 5.5 or 10, galera or not), then call the recipe accordingly.\n\nList of availables recipes:\n\n- mariadb::default (just call server recipe with default options)\n- mariadb::server\n- mariadb::galera\n- mariadb::client\n\nPlease be ware that by default, the root password is empty! If you want have changed it use the `node['mariadb']['server_root_password']` attribute to put a correct value. And by default the remote root access is not activated. Use `node['mariadb']['forbid_remote_root']` attribute to change it.\n\nSometimes, the default apt repository used for apt does not work (see issue #6). In this case, you need to choose another mirror which worki (pick it from mariadb website), and put the http base url in the attribute `node['mariadb']['apt_repository']['base_url']`.\n\n#### mariadb::galera\n\nWhen installing the mariadb::galera on debian recipe, You have to take care of one specific attribute:\n`node['mariadb']['debian']['password']` which default to 'please-change-me'\nAs wee need to have the same password for this user on the whole cluster nodes... We will change the default install one by the content of this attribute.\n\n#### mariadb::client\n\nBy default this recipe install the client, and all needed packages to develop client application. If you do not want to install development files when installing client package,\nset the attribute `node['mariadb']['client']['development_files']` to false. \n\nProviders\n----------\n\nThis recipe define 2 providers:\n- `Chef::Provider::Mariadb::Configuration` shortcut resource `mariadb_configuration`\n- `Chef::Provider::Mariadb::Replication` shortcut resource `mariadb_replication`\n\n#### mariadb_configuration\n\nMainly use for internal purpose. You can use it to create a new configuration file into configuration dir. You have to define 2 variables `section` and `option`.\nWhere `section` is the configuration section, and `option` is a hash of key/value. The name of the resource is used as base for the filename.\n\nExample:\n```ruby\nmariadb_configuration 'fake' do\n section 'mysqld'\n option {foo: 'bar'}\nend\n```\nwill become the file fake.cnf in the include dir (depend on your platform), which contain:\n```\n[mysqld]\nfoo=bar\n```\n\nIf the value start with a '#', then it's considered as a comment, and the value is printed as is (without the key)\n\nExample:\n```ruby\nmariadb_configuration 'fake' do\n section 'mysqld'\n option {comment1: '# Here i am', foo: bar}\nend\n```\nwill become the file fake.cnf in the include dir (depend on your platform), which contain:\n```\n[mysqld]\n# Here i am\nfoo=bar\n```\n\n#### mariadb_replication\n\nThis LWRP is used to manage replication setup on a host. To use this LWRP, the node need to have the mysql binary installed (via the mariadb::client or mariadb::server or mariadb::galera recipe).\nIt have 4 actions:\n- add - to add a new replication setup (become a slave)\n- stop - to stop the slave replication\n- start - to start the slave replication\n- remove - to remove the slave replication configuration\n\nThe resource name need to be 'default' if your don't want to use a named connection (multi source replication in MariaDB 10).\n\nSo by default the provider try to use the local instance of mysql, with the current user and no password. If you want to change, you have to define `host`, `port`, `user` or `password`\n\n```ruby\nmariadb_replication 'default' do\n user 'root'\n password 'fakepass'\n host 'fakehost'\n action :stop\nend\n```\nwill stop the replication on the host `fakehost` using the user `root` and password `fakepass` to connect to.\n\nWhen you add a replication configuration, you have to define at least 4 values `master_host`, `master_user`, `master_password` and `master_use_gtid`. And if you don't want the GTID support, you have to define also `master_log_file` and `master_log_pos`\n\nExample:\n```ruby\nmariadb_replication 'usefull_conn_name' do\n master_host 'server1'\n master_user 'slave_user'\n master_password 'slave_password'\n master_use_gtid 'current_pos'\n action :add\nend\n```\n\nContributing\n------------\n\n1. Fork the repository on Github\n2. Create a named feature branch (like `add_component_x`)\n3. Write your change\n4. Write tests for your change (if applicable)\n5. Run the tests, ensuring they all pass\n6. Submit a Pull Request using Github\n\nLicense and Authors\n-------------------\nAuthors:\nNicolas Blanc \n", + "maintainer": "Nicolas Blanc", + "maintainer_email": "sinfomicien@gmail.com", + "license": "Apache 2.0", + "platforms": { + "ubuntu": ">= 0.0.0", + "debian": ">= 7.0", + "centos": ">= 6.4", + "redhat": ">= 7.0" + }, + "dependencies": { + "apt": ">= 0.0.0", + "yum": ">= 0.0.0", + "yum-epel": ">= 0.0.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + } +} \ No newline at end of file diff --git a/cookbooks/mariadb/providers/configuration.rb b/cookbooks/mariadb/providers/configuration.rb new file mode 100644 index 0000000..35bf433 --- /dev/null +++ b/cookbooks/mariadb/providers/configuration.rb @@ -0,0 +1,38 @@ +# +# Cookbook Name:: mariadb +# Provider:: configuration +# + +use_inline_resources if defined?(use_inline_resources) + +def whyrun_supported? + true +end + +action :add do + variables_hash = { + section: new_resource.section, + options: new_resource.option + } + template node['mariadb']['configuration']['includedir'] + \ + '/' + new_resource.name + '.cnf' do + source 'conf.d.generic.erb' + owner 'root' + group 'mysql' + mode '0640' + cookbook new_resource.cookbook + variables variables_hash + end +end + +action :remove do + if ::File.exist?(node['mariadb']['configuration']['includedir'] + \ + '/' + new_resource.name + '.cnf') + Chef::Log.info "Removing #{new_resource.name} repository from " + \ + node['mariadb']['configuration']['includedir'] + file node['mariadb']['configuration']['includedir'] + \ + '/' + new_resource.name + '.cnf' do + action :delete + end + end +end diff --git a/cookbooks/mariadb/providers/replication.rb b/cookbooks/mariadb/providers/replication.rb new file mode 100644 index 0000000..6470023 --- /dev/null +++ b/cookbooks/mariadb/providers/replication.rb @@ -0,0 +1,105 @@ +# +# Cookbook Name:: mariadb +# Provider:: replication +# + +use_inline_resources if defined?(use_inline_resources) + +def whyrun_supported? + true +end + +def get_mysql_command(host, port, user, password) + mysql_command = '/usr/bin/mysql' + mysql_command += ' -h ' + host unless host.nil? + mysql_command += ' -P ' + port unless port.nil? + mysql_command += ' -u ' + user unless user.nil? + mysql_command += ' -p' + password unless password.nil? + mysql_command +end + +action :add do + if new_resource.master_host.nil? || new_resource.master_user.nil? || + new_resource.master_password.nil? + fail '[ERROR] When adding a slave, you have to define master_host' \ + ' master_user and master_password !' + end + sql_string = 'CHANGE MASTER ' + sql_string += '\'' + new_resource.name + \ + '\' ' if new_resource.name != 'default' + sql_string += 'TO ' + sql_string += 'MASTER_HOST=\'' + new_resource.master_host + '\', ' + sql_string += 'MASTER_PORT=' + new_resource.master_port.to_s + \ + ', ' unless new_resource.master_port.nil? + sql_string += 'MASTER_USER=\'' + new_resource.master_user + '\', ' + sql_string += 'MASTER_PASSWORD=\'' + new_resource.master_password + '\'' + if new_resource.master_use_gtid == 'no' + # Use non GTID replication setup + if new_resource.master_log_file.nil? || new_resource.master_log_pos.nil? + fail '[ERROR] When adding a slave without GTID, you have to' \ + 'define master_log_file and master_log_pos !' + end + unless new_resource.master_log_file.nil? + sql_string += ', MASTER_LOG_FILE=\'' + \ + new_resource.master_log_file + '\'' + sql_string += ', MASTER_LOG_POS=' + new_resource.master_log_pos.to_s + end + else + # Use GTID replication + sql_string += ', MASTER_USE_GTID=' + new_resource.master_use_gtid + ';' + end + execute 'add_replication_from_master_' + new_resource.name do + # Add sensitive true when foodcritic #233 fixed + command '/bin/echo "' + sql_string + '" | ' + get_mysql_command( + new_resource.host, + new_resource.port, + new_resource.user, + new_resource.password + ) + action :run + end +end + +action :remove do + execute 'remove_replication_from_master_' + new_resource.name do + # Add sensitive true when foodcritic #233 fixed + command '/bin/echo "STOP SLAVE \'' + new_resource.name + '\'; ' \ + 'RESET SLAVE \'' + new_resource.name + '\' ALL' \ + ';" | ' + get_mysql_command( + new_resource.host, + new_resource.port, + new_resource.user, + new_resource.password + ) + end +end + +action :start do + command_master_connection = ' \'' + new_resource.name + \ + '\'' unless new_resource.name == 'default' + execute 'start_replication_from_master_' + new_resource.name do + # Add sensitive true when foodcritic #233 fixed + command '/bin/echo "START SLAVE' + command_master_connection + ';' \ + '" | ' + get_mysql_command( + new_resource.host, + new_resource.port, + new_resource.user, + new_resource.password + ) + end +end + +action :stop do + command_master_connection = ' \'' + new_resource.name + \ + '\'' unless new_resource.name == 'default' + execute 'start_replication_from_master_' + new_resource.name do + # Add sensitive true when foodcritic #233 fixed + command '/bin/echo "STOP SLAVE' + command_master_connection + ';' \ + '" | ' + get_mysql_command( + new_resource.host, + new_resource.port, + new_resource.user, + new_resource.password + ) + end +end diff --git a/cookbooks/mariadb/recipes/_audit_plugin.rb b/cookbooks/mariadb/recipes/_audit_plugin.rb new file mode 100644 index 0000000..9b8d030 --- /dev/null +++ b/cookbooks/mariadb/recipes/_audit_plugin.rb @@ -0,0 +1,53 @@ +# Prepare Configuration File +audit_plugin_options = {} + +audit_plugin_options['comment1'] = '#' +audit_plugin_options['comment2'] = '# * MariaDB Audit Plugin' +audit_plugin_options['comment3'] = '#' + +audit_plugin_options['server_audit_events'] = \ + node['mariadb']['audit_plugin']['server_audit_events'] +audit_plugin_options['server_audit_output_type'] = \ + node['mariadb']['audit_plugin']['server_audit_output_type'] +audit_plugin_options['server_audit_syslog_facility'] = \ + node['mariadb']['audit_plugin']['server_audit_syslog_facility'] +audit_plugin_options['server_audit_syslog_priority'] = \ + node['mariadb']['audit_plugin']['server_audit_syslog_priority'] + +audit_plugin_options['enable'] = '#server_audit_logging = ON' + +# Install the MariaDB Audit Plugin +execute 'install_mariadb_audit_plugin' do + command '/usr/bin/mysql -e "INSTALL PLUGIN server_audit '\ + 'SONAME \'server_audit\';"' + notifies :run, 'execute[configure_mariadb_audit_plugin]', :immediately + not_if do + cmd = Mixlib::ShellOut.new('/usr/bin/mysql -u root -B -N -e "SELECT 1 '\ + 'FROM information_schema.plugins '\ + 'WHERE PLUGIN_NAME = \'SERVER_AUDIT\''\ + 'AND PLUGIN_STATUS = \'ACTIVE\';"') + cmd.run_command + cmd.stdout.to_i == 1 + end +end + +# Configure (Dynamic) +execute 'configure_mariadb_audit_plugin' do + command 'echo "SET GLOBAL server_audit_events=\'' + \ + node['mariadb']['audit_plugin']['server_audit_events'] + '\';' \ + 'SET GLOBAL server_audit_output_type=\'' + \ + node['mariadb']['audit_plugin']['server_audit_output_type'] + '\';' \ + 'SET GLOBAL server_audit_syslog_facility=\'' + \ + node['mariadb']['audit_plugin']['server_audit_syslog_facility'] + '\';' \ + 'SET GLOBAL server_audit_syslog_priority=\'' + \ + node['mariadb']['audit_plugin']['server_audit_syslog_priority'] + '\';"' \ + '| /usr/bin/mysql' + action :nothing +end + +# Create Configuration File +mariadb_configuration 'audit_plugin' do + section 'mysqld' + option audit_plugin_options + action :add +end diff --git a/cookbooks/mariadb/recipes/_debian_galera.rb b/cookbooks/mariadb/recipes/_debian_galera.rb new file mode 100644 index 0000000..63531ea --- /dev/null +++ b/cookbooks/mariadb/recipes/_debian_galera.rb @@ -0,0 +1,55 @@ +# +# Cookbook Name:: mariadb +# Recipe:: _debian_galera +# +# Copyright 2014, blablacar.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. +# + +# To be sure that debconf is installed +package 'debconf-utils' do + action :install +end + +# Preseed Debian Package +# (but test for resource, as it can be declared by apt recipe) +begin + resources(directory: '/var/cache/local/preseeding') +rescue Chef::Exceptions::ResourceNotFound + directory '/var/cache/local/preseeding' do + owner 'root' + group 'root' + mode '0755' + recursive true + end +end + +template '/var/cache/local/preseeding/mariadb-galera-server.seed' do + source 'mariadb-server.seed.erb' + owner 'root' + group 'root' + mode '0600' + variables(package_name: 'mariadb-galera-server') + notifies :run, 'execute[preseed mariadb-galera-server]', :immediately +end + +execute 'preseed mariadb-galera-server' do + command '/usr/bin/debconf-set-selections ' \ + '/var/cache/local/preseeding/mariadb-galera-server.seed' + action :nothing +end + +package "mariadb-galera-server-#{node['mariadb']['install']['version']}" do + action :install +end diff --git a/cookbooks/mariadb/recipes/_debian_server.rb b/cookbooks/mariadb/recipes/_debian_server.rb new file mode 100644 index 0000000..4c17531 --- /dev/null +++ b/cookbooks/mariadb/recipes/_debian_server.rb @@ -0,0 +1,55 @@ +# +# Cookbook Name:: mariadb +# Recipe:: _debian_server +# +# Copyright 2014, blablacar.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. +# + +# To be sure that debconf is installed +package 'debconf-utils' do + action :install +end + +# Preseed Debian Package +# (but test for resource, as it can be declared by apt recipe) +begin + resources(directory: '/var/cache/local/preseeding') +rescue Chef::Exceptions::ResourceNotFound + directory '/var/cache/local/preseeding' do + owner 'root' + group 'root' + mode '0755' + recursive true + end +end + +template '/var/cache/local/preseeding/mariadb-server.seed' do + source 'mariadb-server.seed.erb' + owner 'root' + group 'root' + mode '0600' + variables(package_name: 'mariadb-server') + notifies :run, 'execute[preseed mariadb-server]', :immediately +end + +execute 'preseed mariadb-server' do + command '/usr/bin/debconf-set-selections ' \ + '/var/cache/local/preseeding/mariadb-server.seed' + action :nothing +end + +package "mariadb-server-#{node['mariadb']['install']['version']}" do + action :install +end diff --git a/cookbooks/mariadb/recipes/_redhat_galera.rb b/cookbooks/mariadb/recipes/_redhat_galera.rb new file mode 100644 index 0000000..8d725db --- /dev/null +++ b/cookbooks/mariadb/recipes/_redhat_galera.rb @@ -0,0 +1,49 @@ +# +# Cookbook Name:: mariadb +# Recipe:: _redhat_galera +# +# Copyright 2014, blablacar.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. +# + +# To force removing of mariadb-libs on CentOS >= 7 +package 'MariaDB-shared' do + action :install +end + +package 'MariaDB-Galera-server' do + action :install + notifies :create, 'directory[/var/log/mysql]', :immediately + notifies :start, 'service[mysql]', :immediately + notifies :run, 'execute[change first install root password]', :immediately +end + +directory '/var/log/mysql' do + action :nothing + user 'mysql' + group 'mysql' + mode '0755' +end + +service 'mysql' do + action :nothing +end + +execute 'change first install root password' do + # Add sensitive true when foodcritic #233 fixed + command '/usr/bin/mysqladmin -u root password \'' + \ + node['mariadb']['server_root_password'] + '\'' + action :nothing + not_if { node['mariadb']['server_root_password'].empty? } +end diff --git a/cookbooks/mariadb/recipes/_redhat_server.rb b/cookbooks/mariadb/recipes/_redhat_server.rb new file mode 100644 index 0000000..c279a2b --- /dev/null +++ b/cookbooks/mariadb/recipes/_redhat_server.rb @@ -0,0 +1,45 @@ +# +# Cookbook Name:: mariadb +# Recipe:: _redhat_server +# +# Copyright 2014, blablacar.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. +# + +# To force removing of mariadb-libs on CentOS >= 7 +package 'MariaDB-shared' do + action :install +end + +package 'MariaDB-server' do + action :install + notifies :create, 'directory[/var/log/mysql]', :immediately + notifies :start, 'service[mysql]', :immediately + notifies :run, 'execute[change first install root password]', :immediately +end + +directory '/var/log/mysql' do + action :nothing + user 'mysql' + group 'mysql' + mode '0755' +end + +execute 'change first install root password' do + # Add sensitive true when foodcritic #233 fixed + command '/usr/bin/mysqladmin -u root password \'' + \ + node['mariadb']['server_root_password'] + '\'' + action :nothing + not_if { node['mariadb']['server_root_password'].empty? } +end diff --git a/cookbooks/mariadb/recipes/_redhat_server_native.rb b/cookbooks/mariadb/recipes/_redhat_server_native.rb new file mode 100644 index 0000000..fe2b8da --- /dev/null +++ b/cookbooks/mariadb/recipes/_redhat_server_native.rb @@ -0,0 +1,45 @@ +# +# Cookbook Name:: mariadb +# Recipe:: _redhat_server_native +# +# 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. +# +# This recipe is for install and configure os shipped mariadb package + +Chef::Recipe.send(:include, MariaDB::Helper) + +service_name = os_service_name(node['platform'], node['platform_version']) +node.set['mariadb']['mysqld']['service_name'] = service_name\ + unless service_name.nil? + +directory '/var/log/mysql' do + action :create + user 'mysql' + group 'mysql' + mode '0755' +end + +package 'mariadb-server' do + action :install + notifies :enable, 'service[mysql]' + notifies :start, 'service[mysql]', :immediately + notifies :run, 'execute[change first install root password]', :immediately +end + +execute 'change first install root password' do + # Add sensitive true when foodcritic #233 fixed + command '/usr/bin/mysqladmin -u root password \'' + \ + node['mariadb']['server_root_password'] + '\'' + action :nothing + not_if { node['mariadb']['server_root_password'].empty? } +end diff --git a/cookbooks/mariadb/recipes/client.rb b/cookbooks/mariadb/recipes/client.rb new file mode 100644 index 0000000..a1296d2 --- /dev/null +++ b/cookbooks/mariadb/recipes/client.rb @@ -0,0 +1,89 @@ +# +# Cookbook Name:: mariadb +# Recipe:: client +# +# Copyright 2014, blablacar.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. +# + +Chef::Recipe.send(:include, MariaDB::Helper) +case node['mariadb']['install']['type'] +when 'package' + use_os_package = use_os_native_package?( + node['mariadb']['install']['prefer_os_package'], + node['platform'], + node['platform_version']) + + include_recipe "#{cookbook_name}::repository" unless use_os_package + + case node['platform_family'] + when 'rhel' + # On CentOS at least, there's a conflict between MariaDB and mysql-libs + package 'mysql-libs' do + action :remove + not_if { use_os_package } + end + + # rubocop:disable BlockNesting + if use_os_package + if node['mariadb']['client']['development_files'] + node.default['mariadb']['client']['packages'] = \ + %w(mariadb mariadb-devel) + else + node.default['mariadb']['client']['packages'] = \ + %w(mariadb) + end + else + if node['mariadb']['client']['development_files'] + node.default['mariadb']['client']['packages'] = \ + %w(MariaDB-client MariaDB-devel) + else + node.default['mariadb']['client']['packages'] = \ + %w(MariaDB-client) + end + end + # rubocop:enable BlockNesting + when 'fedora' + if node['mariadb']['client']['development_files'] + node.default['mariadb']['client']['packages'] = \ + %w(mariadb mariadb-devel) + else + node.default['mariadb']['client']['packages'] = \ + %w(mariadb) + end + when 'suse' + if node['mariadb']['client']['development_files'] + node.default['mariadb']['client']['packages'] = \ + %w(mariadb-community-server-client libmariadbclient-devel) + else + node.default['mariadb']['client']['packages'] = \ + %w(mariadb-community-server-client) + end + when 'debian' + if node['mariadb']['client']['development_files'] + node.default['mariadb']['client']['packages'] = \ + %W(mariadb-client-#{node['mariadb']['install']['version']} + libmariadbclient-dev) + else + node.default['mariadb']['client']['packages'] = \ + %W(mariadb-client-#{node['mariadb']['install']['version']}) + end + end + + node['mariadb']['client']['packages'].each do |name| + package name + end +when 'from_source' + # To be filled as soon as possible +end diff --git a/cookbooks/mariadb/recipes/config.rb b/cookbooks/mariadb/recipes/config.rb new file mode 100644 index 0000000..7cab1b3 --- /dev/null +++ b/cookbooks/mariadb/recipes/config.rb @@ -0,0 +1,96 @@ +# +# Cookbook Name:: mariadb +# Recipe:: config +# +# Copyright 2014, blablacar.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. +# + +template node['mariadb']['configuration']['path'] + '/my.cnf' do + source 'my.cnf.erb' + owner 'root' + group 'root' + mode '0644' +end + +innodb_options = {} + +innodb_options['comment1'] = '#' +innodb_options['comment2'] = '# * InnoDB' +innodb_options['comment3'] = '#' +innodb_options['comment4'] = '# InnoDB is enabled by default with a 10MB ' \ + 'datafile in /var/lib/mysql/.' +innodb_options['comment5'] = '# Read the manual for more InnoDB ' \ + 'related options. There are many!' + +innodb_options['innodb_log_file_size_comment1'] = '# you can\'t just ' \ + 'change log file size, ' \ + 'requires special procedure' +if node['mariadb']['innodb']['log_file_size'].empty? + innodb_options['innodb_log_file_size'] = '#innodb_log_file_size = 50M' +else + innodb_options['innodb_log_file_size'] = \ + node['mariadb']['innodb']['log_file_size'] +end +if node['mariadb']['innodb']['bps_percentage_memory'] + innodb_options['innodb_buffer_pool_size'] = ( + ( + node['mariadb']['innodb']['buffer_pool_size'].to_f * + (node['memory']['total'][0..-3].to_i / 1024) + ).round).to_s + 'M' +else + innodb_options['innodb_buffer_pool_size'] = \ + node['mariadb']['innodb']['buffer_pool_size'] +end +innodb_options['innodb_log_buffer_size'] = \ + node['mariadb']['innodb']['log_buffer_size'] +innodb_options['innodb_file_per_table'] = \ + node['mariadb']['innodb']['file_per_table'] +innodb_options['innodb_open_files'] = node['mariadb']['innodb']['open_files'] +innodb_options['innodb_io_capacity'] = \ + node['mariadb']['innodb']['io_capacity'] +innodb_options['innodb_flush_method'] = \ + node['mariadb']['innodb']['flush_method'] +node['mariadb']['innodb']['options'].each do |key, value| + innodb_options[key] = value +end + +mariadb_configuration 'innodb' do + section 'mysqld' + option innodb_options + action :add +end + +replication_opts = {} +replication_opts['log_bin'] = node['mariadb']['replication']['log_bin'] +replication_opts['log_bin_index'] = \ + node['mariadb']['replication']['log_bin_index'] +replication_opts['expire_logs_days'] = \ + node['mariadb']['replication']['expire_logs_days'] +replication_opts['max_binlog_size'] = \ + node['mariadb']['replication']['max_binlog_size'] +unless node['mariadb']['replication']['server_id'].empty? + replication_opts['server-id'] = node['mariadb']['replication']['server_id'] +end +if node['mariadb']['replication'].key?('options') + node['mariadb']['replication']['options'].each do |key, value| + replication_opts[key] = value + end +end + +mariadb_configuration 'replication' do + section 'mysqld' + option replication_opts + action :add +end diff --git a/cookbooks/mariadb/recipes/default.rb b/cookbooks/mariadb/recipes/default.rb new file mode 100644 index 0000000..7edcd87 --- /dev/null +++ b/cookbooks/mariadb/recipes/default.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: mariadb +# Recipe:: default +# +# Copyright 2014, blablacar.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. +# + +include_recipe "#{cookbook_name}::server" diff --git a/cookbooks/mariadb/recipes/galera.rb b/cookbooks/mariadb/recipes/galera.rb new file mode 100644 index 0000000..7b13f8a --- /dev/null +++ b/cookbooks/mariadb/recipes/galera.rb @@ -0,0 +1,141 @@ +# +# Cookbook Name:: mariadb +# Recipe:: galera +# +# Copyright 2014, blablacar.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. +# + +case node['mariadb']['install']['type'] +when 'package' + # include MariaDB repositories + include_recipe "#{cookbook_name}::repository" + + case node['platform'] + when 'debian', 'ubuntu' + include_recipe "#{cookbook_name}::_debian_galera" + when 'redhat', 'centos', 'fedora', 'scientific', 'amazon' + include_recipe "#{cookbook_name}::_redhat_galera" + end +when 'from_source' + # To be filled as soon as possible +end + +if node['mariadb']['galera']['wsrep_sst_method'] == 'rsync' + package 'rsync' do + action :install + end +else + if node['mariadb']['galera']['wsrep_sst_method'] == 'xtrabackup' + package 'percona-xtrabackup' do + action :install + end + + package 'socat' do + action :install + end + end +end + +include_recipe "#{cookbook_name}::config" + +galera_cluster_nodes = [] +if !node['mariadb'].attribute?('rspec') && Chef::Config[:solo] + Chef::Log.warn('This recipe uses search. Chef Solo does not support search.') +else + if node['mariadb']['galera']['cluster_search_query'].empty? + galera_cluster_nodes = search( + :node, \ + "mariadb_galera_cluster_name:#{node['mariadb']['galera']['cluster_name']}" + ) + else + galera_cluster_nodes = search 'node', node['mariadb']['galera']['cluster_search_query'] + log 'Chef search results' do + message "Searching for \"#{node['mariadb']['galera']['cluster_search_query']}\" \ + resulted in \"#{galera_cluster_nodes}\" ..." + end + end + # Sort Nodes by fqdn + galera_cluster_nodes.sort! { |x, y| x[:fqdn] <=> y[:fqdn] } +end + +first = true +gcomm = 'gcomm://' +galera_cluster_nodes.each do |lnode| + next unless lnode.name != node.name + gcomm += ',' unless first + gcomm += lnode['fqdn'] + first = false +end + +galera_options = {} + +galera_options['wsrep_cluster_address'] = gcomm +galera_options['wsrep_cluster_name'] = \ + node['mariadb']['galera']['cluster_name'] +galera_options['wsrep_sst_method'] = \ + node['mariadb']['galera']['wsrep_sst_method'] +if node['mariadb']['galera'].attribute?('wsrep_sst_auth') + galera_options['wsrep_sst_auth'] = \ + node['mariadb']['galera']['wsrep_sst_auth'] +end +galera_options['wsrep_provider'] = \ + node['mariadb']['galera']['wsrep_provider'] +galera_options['wsrep_slave_threads'] = node['cpu']['total'] * 4 +node['mariadb']['galera']['options'].each do |key, value| + galera_options[key] = value +end + +mariadb_configuration 'galera' do + section 'mysqld' + option galera_options + action :add +end + +# +# Under debian system we have to change the debian-sys-maint default password. +# This password is the same for the overall cluster. +# +if platform?('debian', 'ubuntu') + template '/etc/mysql/debian.cnf' do + sensitive true + source 'debian.cnf.erb' + owner 'root' + group 'root' + mode '0600' + end + + execute 'correct-debian-grants' do + # Add sensitive true when foodcritic #233 fixed + command 'mysql -r -B -N -e "GRANT SELECT, INSERT, UPDATE, DELETE, '\ + 'CREATE, DROP, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, '\ + 'ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, '\ + 'LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, '\ + 'CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, '\ + "CREATE USER, EVENT, TRIGGER ON *.* TO '" + \ + node['mariadb']['debian']['user'] + \ + "'@'" + node['mariadb']['debian']['host'] + "' IDENTIFIED BY '" + \ + node['mariadb']['debian']['password'] + "' WITH GRANT OPTION\"" + action :run + only_if do + cmd = Mixlib::ShellOut.new("/usr/bin/mysql --user=\"" + \ + node['mariadb']['debian']['user'] + \ + "\" --password=\"" + node['mariadb']['debian']['password'] + \ + "\" -r -B -N -e \"SELECT 1\"") + cmd.run_command + cmd.error? + end + ignore_failure true + end +end diff --git a/cookbooks/mariadb/recipes/plugins.rb b/cookbooks/mariadb/recipes/plugins.rb new file mode 100644 index 0000000..5a722bc --- /dev/null +++ b/cookbooks/mariadb/recipes/plugins.rb @@ -0,0 +1,3 @@ +node['mariadb']['plugins'].each do |plugin, enable| + include_recipe "#{cookbook_name}::_" + plugin + '_plugin' if enable +end diff --git a/cookbooks/mariadb/recipes/repository.rb b/cookbooks/mariadb/recipes/repository.rb new file mode 100644 index 0000000..4720cbf --- /dev/null +++ b/cookbooks/mariadb/recipes/repository.rb @@ -0,0 +1,42 @@ +case node['platform'] +when 'debian', 'ubuntu' + install_method = 'apt' +when 'redhat', 'centos', 'fedora', 'scientific', 'amazon' + install_method = 'yum' +end + +if node['mariadb']['use_default_repository'] + case install_method + when 'apt' + include_recipe 'apt::default' + + apt_repository "mariadb-#{node['mariadb']['install']['version']}" do + uri 'http://' + node['mariadb']['apt_repository']['base_url'] + '/' + \ + node['mariadb']['install']['version'] + '/' + node['platform'] + distribution node['lsb']['codename'] + components ['main'] + keyserver 'hkp://keyserver.ubuntu.com:80' + key '0xcbcb082a1bb943db' + end + when 'yum' + include_recipe 'yum::default' + + if node['platform'] == 'redhat' + target_platform = "rhel#{node['platform_version'].to_i}" + else + target_platform = "#{node['platform']}#{node['platform_version'].to_i}" + end + yum_repository "mariadb-#{node['mariadb']['install']['version']}" do + description 'MariaDB Official Repository' + baseurl 'http://yum.mariadb.org/' + \ + node['mariadb']['install']['version'] + "/#{target_platform}-amd64" + gpgkey 'https://yum.mariadb.org/RPM-GPG-KEY-MariaDB' + action :create + end + + case node['platform'] + when 'redhat', 'centos' + include_recipe 'yum-epel::default' + end + end +end diff --git a/cookbooks/mariadb/recipes/server.rb b/cookbooks/mariadb/recipes/server.rb new file mode 100644 index 0000000..d261ba5 --- /dev/null +++ b/cookbooks/mariadb/recipes/server.rb @@ -0,0 +1,130 @@ +# +# Cookbook Name:: mariadb +# Recipe:: server +# +# Copyright 2014, blablacar.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. +# + +Chef::Recipe.send(:include, MariaDB::Helper) +case node['mariadb']['install']['type'] +when 'package' + if use_os_native_package?(node['mariadb']['install']['prefer_os_package'], + node['platform'], node['platform_version']) + # currently, no releases with apt (e.g. ubuntu) ship mariadb + # only provide one type of server here (with yum support) + include_recipe "#{cookbook_name}::_redhat_server_native" + else + include_recipe "#{cookbook_name}::repository" + + case node['platform'] + when 'debian', 'ubuntu' + include_recipe "#{cookbook_name}::_debian_server" + when 'redhat', 'centos', 'fedora', 'scientific', 'amazon' + include_recipe "#{cookbook_name}::_redhat_server" + end + end +when 'from_source' + # To be filled as soon as possible +end + +include_recipe "#{cookbook_name}::config" + +service 'mysql' do + service_name node['mariadb']['mysqld']['service_name'] + supports restart: true + action :nothing +end + +# move the datadir if needed +if node['mariadb']['mysqld']['datadir'] != + node['mariadb']['mysqld']['default_datadir'] + + bash 'move-datadir' do + user 'root' + code <<-EOH + /bin/cp -a #{node['mariadb']['mysqld']['default_datadir']}/* \ + #{node['mariadb']['mysqld']['datadir']} && + /bin/rm -r #{node['mariadb']['mysqld']['default_datadir']} && + /bin/ln -s #{node['mariadb']['mysqld']['datadir']} \ + #{node['mariadb']['mysqld']['default_datadir']} + EOH + action :nothing + end + + directory node['mariadb']['mysqld']['datadir'] do + owner 'mysql' + group 'mysql' + mode 00750 + action :create + notifies :stop, 'service[mysql]', :immediately + notifies :run, 'bash[move-datadir]', :immediately + notifies :start, 'service[mysql]', :immediately + only_if { !File.symlink?(node['mariadb']['mysqld']['default_datadir']) } + end +end + +# restart the service if needed +# workaround idea from https://github.com/stissot +Chef::Resource::Execute.send(:include, MariaDB::Helper) +execute 'mariadb-service-restart-needed' do + command 'true' + only_if do + mariadb_service_restart_required?( + '127.0.0.1', + node['mariadb']['mysqld']['port'], + node['mariadb']['mysqld']['socket'] + ) + end + notifies :restart, 'service[mysql]', :immediately +end + +if node['mariadb']['allow_root_pass_change'] + # Used to change root password after first install + # Still experimental + if node['mariadb']['server_root_password'].empty? + md5 = Digest::MD5.hexdigest('empty') + else + md5 = Digest::MD5.hexdigest(node['mariadb']['server_root_password']) + end + + file '/etc/mysql_root_change' do + content md5 + action :create + notifies :run, 'execute[install-grants]', :immediately + end +end + +if node['mariadb']['allow_root_pass_change'] || + node['mariadb']['forbid_remote_root'] + execute 'install-grants' do + # Add sensitive true when foodcritic #233 fixed + command '/bin/bash /etc/mariadb_grants \'' + \ + node['mariadb']['server_root_password'] + '\'' + only_if { File.exist?('/etc/mariadb_grants') } + action :nothing + end + + template '/etc/mariadb_grants' do + sensitive true + source 'mariadb_grants.erb' + owner 'root' + group 'root' + mode '0600' + notifies :run, 'execute[install-grants]', :immediately + end +end + +# MariaDB Plugins +include_recipe "#{cookbook_name}::plugins" if node['mariadb']['plugins_options']['auto_install'] diff --git a/cookbooks/mariadb/resources/configuration.rb b/cookbooks/mariadb/resources/configuration.rb new file mode 100644 index 0000000..57e458a --- /dev/null +++ b/cookbooks/mariadb/resources/configuration.rb @@ -0,0 +1,13 @@ +# +# Cookbook Name:: mariadb +# Resource:: configuration +# + +actions :add, :remove +default_action :add + +# name of the extra conf file, used for .cnf filename +attribute :conf_name, kind_of: String, name_attribute: true +attribute :section, kind_of: String +attribute :option, kind_of: Hash, default: {} +attribute :cookbook, kind_of: String, default: nil diff --git a/cookbooks/mariadb/resources/replication.rb b/cookbooks/mariadb/resources/replication.rb new file mode 100644 index 0000000..afb0462 --- /dev/null +++ b/cookbooks/mariadb/resources/replication.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: mariadb +# Resource:: replication +# + +actions :add, :remove, :start, :stop +default_action :add + +# name of the extra conf file, used for .cnf filename +attribute :connection_name, kind_of: String, name_attribute: true +attribute :host, kind_of: [String, NilClass], default: nil +attribute :port, kind_of: [String, NilClass], default: nil +attribute :user, kind_of: [String, NilClass], default: nil +attribute :password, kind_of: [String, NilClass], default: nil +attribute :master_host, kind_of: [String, NilClass], default: nil +attribute :master_user, kind_of: [String, NilClass], default: nil +attribute :master_password, kind_of: [String, NilClass], default: nil +attribute :master_connect_retry, kind_of: [String, NilClass], default: nil +attribute :master_port, kind_of: [Integer, NilClass], default: nil +attribute :master_log_pos, kind_of: [Integer, NilClass], default: nil +attribute :master_log_file, kind_of: [String, NilClass], default: nil +attribute :master_use_gtid, kind_of: String, default: 'no' diff --git a/cookbooks/mariadb/templates/default/conf.d.generic.erb b/cookbooks/mariadb/templates/default/conf.d.generic.erb new file mode 100644 index 0000000..56af4e1 --- /dev/null +++ b/cookbooks/mariadb/templates/default/conf.d.generic.erb @@ -0,0 +1,19 @@ +# DEPLOYED BY CHEF +[<%= @section -%>] +<% @options.each do | option_name, option_value |-%> + <% if option_value.to_s == 'true' -%> +<%= option_name %> + <% else -%> + <% if option_value.kind_of?(String) && option_value.start_with?('#') -%> +<%= option_value %> + <% else -%> + <% if option_value.kind_of?(Array) -%> + <% option_value.each do | option_value_array_value | -%> +<%= option_name -%> = <%= option_value_array_value %> + <% end -%> + <% else -%> +<%= option_name -%> = <%= option_value %> + <% end -%> + <% end -%> + <% end -%> +<% end -%> diff --git a/cookbooks/mariadb/templates/default/debian.cnf.erb b/cookbooks/mariadb/templates/default/debian.cnf.erb new file mode 100644 index 0000000..b7fb3f6 --- /dev/null +++ b/cookbooks/mariadb/templates/default/debian.cnf.erb @@ -0,0 +1,12 @@ +# Automatically generated for Debian scripts (Managed by CHEF). DO NOT TOUCH! +[client] +host = <%= node['mariadb']['debian']['host'] %> +user = <%= node['mariadb']['debian']['user'] %> +password = <%= node['mariadb']['debian']['password'] %> +socket = <%= node['mariadb']['client']['socket'] %> +[mysql_upgrade] +host = <%= node['mariadb']['debian']['host'] %> +user = <%= node['mariadb']['debian']['user'] %> +password = <%= node['mariadb']['debian']['password'] %> +socket = <%= node['mariadb']['mysqld_safe']['socket'] %> +basedir = <%= node['mariadb']['mysqld']['basedir'] %> diff --git a/cookbooks/mariadb/templates/default/mariadb-server.seed.erb b/cookbooks/mariadb/templates/default/mariadb-server.seed.erb new file mode 100644 index 0000000..eddb939 --- /dev/null +++ b/cookbooks/mariadb/templates/default/mariadb-server.seed.erb @@ -0,0 +1,13 @@ +<% +# Value obtained via the debconf-get-selections tool on debian wheezy +pack_w_version = @package_name + '-' + node['mariadb']['install']['version'] +-%> +<%= pack_w_version %> mysql-server/root_password_again select <%= node['mariadb']['server_root_password'] %> +<%= pack_w_version %> mysql-server/root_password select <%= node['mariadb']['server_root_password'] %> +<%= pack_w_version %> mysql-server/error_setting_password boolean false +<%= pack_w_version %> mysql-server-5.1/nis_warning note +<%= pack_w_version %> mysql-server-5.1/start_on_boot boolean true +<%= pack_w_version %> <%= pack_w_version %>/really_downgrade boolean false +<%= pack_w_version %> mysql-server-5.1/postrm_remove_databases boolean false +<%= pack_w_version %> mysql-server/password_mismatch boolean false +<%= pack_w_version %> mysql-server/no_upgrade_when_using_ndb boolean true diff --git a/cookbooks/mariadb/templates/default/mariadb_grants.erb b/cookbooks/mariadb/templates/default/mariadb_grants.erb new file mode 100644 index 0000000..6eda39d --- /dev/null +++ b/cookbooks/mariadb/templates/default/mariadb_grants.erb @@ -0,0 +1,25 @@ +#!/bin/bash +# Generated by CHEF +# Local modification will be overriden + +<% if node['mariadb']['allow_root_pass_change'] -%> +<% if node['mariadb']['server_root_password'].empty? -%> +/usr/bin/mysqladmin -u root password "$1" +<% else -%> +/usr/bin/mysqladmin -u root -p'<%= node['mariadb']['server_root_password'] %>' password "$1" +<% end -%> + +<% end -%> +password_flag="" +if [ "$1" ]; then + password_flag="-p$1" +fi + +<% if node['mariadb']['forbid_remote_root'] -%> +user_exist=`/usr/bin/mysql -u root ${password_flag} -D mysql -r -B -N -e "SELECT user from user where user = 'root' and host = '%'"` +if [ $user_exist == 'root' ]; then + /bin/echo "DROP USER 'root'@'%';" | /usr/bin/mysql -u root ${password_flag} +fi +<% else -%> +/bin/echo "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '<%= node['mariadb']['server_root_password'] %>' WITH GRANT OPTION;" | /usr/bin/mysql -u root ${password_flag} +<% end -%> diff --git a/cookbooks/mariadb/templates/default/my.cnf.erb b/cookbooks/mariadb/templates/default/my.cnf.erb new file mode 100644 index 0000000..4b78c25 --- /dev/null +++ b/cookbooks/mariadb/templates/default/my.cnf.erb @@ -0,0 +1,191 @@ +# DEPLOYED BY CHEF +# MariaDB database server configuration file. +# +# You can copy this file to one of: +# - "/etc/mysql/my.cnf" to set global options, +# - "~/.my.cnf" to set user-specific options. +# +# One can use all long options that the program supports. +# Run program with --help to get a list of available options and with +# --print-defaults to see which it would actually understand and use. +# +# For explanations see +# http://dev.mysql.com/doc/mysql/en/server-system-variables.html + +# This will be passed to all mysql clients +# It has been reported that passwords should be enclosed with ticks/quotes +# escpecially if they contain "#" chars... +# Remember to edit /etc/mysql/debian.cnf when changing the socket location. +[client] +port = <%= node['mariadb']['client']['port'] %> +socket = <%= node['mariadb']['client']['socket'] %> +<% if node['mariadb']['client'].attribute?('host') && !node['mariadb']['client']['host'].nil? %> +host = <%= node['mariadb']['client']['host'] %> +<% end %> + +# Here is entries for some specific programs +# The following values assume you have at least 32M ram + +# This was formally known as [safe_mysqld]. Both versions are currently parsed. +[mysqld_safe] +socket = <%= node['mariadb']['mysqld_safe']['socket'] %> +nice = 0 + +[mysqld] +# +# * Basic Settings +# +user = <%= node['mariadb']['mysqld']['user'] %> +<% if node['mariadb']['mysqld'].attribute?('pid_file') %> +pid-file = <%= node['mariadb']['mysqld']['pid_file'] %> +<% end %> +socket = <%= node['mariadb']['mysqld']['socket'] %> +port = <%= node['mariadb']['mysqld']['port'] %> +basedir = <%= node['mariadb']['mysqld']['basedir'] %> +datadir = <%= node['mariadb']['mysqld']['default_datadir'] %> +tmpdir = <%= node['mariadb']['mysqld']['tmpdir'] %> +lc_messages_dir = <%= node['mariadb']['mysqld']['lc_messages_dir'] %> +lc_messages = <%= node['mariadb']['mysqld']['lc_messages'] %> +<% if node['mariadb']['mysqld']['skip_external_locking'] == 'true' -%> +skip-external-locking +<% end -%> +# +# Instead of skip-networking the default is now to listen only on +# localhost which is more compatible and is not less secure. +<% unless node['mariadb']['mysqld']['bind_address'].nil? or node['mariadb']['mysqld']['bind_address'].empty? -%> +bind-address = <%= node['mariadb']['mysqld']['bind_address'] %> +<% end -%> +# +# * Fine Tuning +# +max_connections = <%= node['mariadb']['mysqld']['max_connections'] %> +connect_timeout = <%= node['mariadb']['mysqld']['connect_timeout'] %> +wait_timeout = <%= node['mariadb']['mysqld']['wait_timeout'] %> +max_allowed_packet = <%= node['mariadb']['mysqld']['max_allowed_packet'] %> +thread_cache_size = <%= node['mariadb']['mysqld']['thread_cache_size'] %> +sort_buffer_size = <%= node['mariadb']['mysqld']['sort_buffer_size'] %> +bulk_insert_buffer_size = <%= node['mariadb']['mysqld']['bulk_insert_buffer_size'] %> +tmp_table_size = <%= node['mariadb']['mysqld']['tmp_table_size'] %> +max_heap_table_size = <%= node['mariadb']['mysqld']['max_heap_table_size'] %> +# +# * MyISAM +# +# This replaces the startup script and checks MyISAM tables if needed +# the first time they are touched. On error, make copy and try a repair. +myisam_recover = <%= node['mariadb']['mysqld']['myisam_recover'] %> +key_buffer_size = <%= node['mariadb']['mysqld']['key_buffer_size'] %> +<% if node['mariadb']['mysqld']['open_files_limit'].empty? -%> +#open-files-limit = 2000 +<% else -%> +open-files-limit = <%= node['mariadb']['mysqld']['open_files_limit'] %> +<% end -%> +table_open_cache = <%= node['mariadb']['mysqld']['table_open_cache'] %> +myisam_sort_buffer_size = <%= node['mariadb']['mysqld']['myisam_sort_buffer_size'] %> +concurrent_insert = <%= node['mariadb']['mysqld']['concurrent_insert'] %> +read_buffer_size = <%= node['mariadb']['mysqld']['read_buffer_size'] %> +read_rnd_buffer_size = <%= node['mariadb']['mysqld']['read_rnd_buffer_size'] %> +# +# * Query Cache Configuration +# +# Cache only tiny result sets, so we can fit more in the query cache. +query_cache_limit = <%= node['mariadb']['mysqld']['query_cache_limit'] %> +query_cache_size = <%= node['mariadb']['mysqld']['query_cache_size'] %> +# for more write intensive setups, set to DEMAND or OFF +<% if node['mariadb']['mysqld']['query_cache_type'].empty? -%> +#query_cache_type = DEMAND +<% else -%> +query_cache_type = <%= node['mariadb']['mysqld']['query_cache_type'] %> +<% end -%> +# +# * Logging and Replication +# +# Both location gets rotated by the cronjob. +# Be aware that this log type is a performance killer. +# As of 5.1 you can enable the log at runtime! +#general_log_file = /var/log/mysql/mysql.log +#general_log = 1 +# +# Error logging goes to syslog due to /etc/mysql/conf.d/mysqld_safe_syslog.cnf. +# +# we do want to know about network errors and such +log_warnings = 2 +# +# Enable the slow query log to see queries with especially long duration +#slow_query_log[={0|1}] +slow_query_log_file = /var/log/mysql/mariadb-slow.log +long_query_time = 10 +#log_slow_rate_limit = 1000 +log_slow_verbosity = query_plan + +#log-queries-not-using-indexes +#log_slow_admin_statements +# +# The following can be used as easy to replay backup logs or for replication. +# note: if you are setting up a replication slave, see README.Debian about +# other settings you may need to change. +#report_host = master1 +#auto_increment_increment = 2 +#auto_increment_offset = 1 +# not fab for performance, but safer +#sync_binlog = 1 +# slaves +#relay_log = /var/log/mysql/relay-bin +#relay_log_index = /var/log/mysql/relay-bin.index +#relay_log_info_file = /var/log/mysql/relay-bin.info +#log_slave_updates +#read_only +# +# If applications support it, this stricter sql_mode prevents some +# mistakes like inserting invalid dates etc. +#sql_mode = NO_ENGINE_SUBSTITUTION,TRADITIONAL + +default_storage_engine = <%= node['mariadb']['mysqld']['default_storage_engine'] %> + +# +# * Security Features +# +# Read the manual, too, if you want chroot! +# chroot = /var/lib/mysql/ +# +# For generating SSL certificates I recommend the OpenSSL GUI "tinyca". +# +# ssl-ca=/etc/mysql/cacert.pem +# ssl-cert=/etc/mysql/server-cert.pem +# ssl-key=/etc/mysql/server-key.pem + +<% if node['mariadb']['mysqld'].key?('options') -%> +<% node['mariadb']['mysqld']['options'].each { |key, value| -%> +<%= key %> = <%= value %> +<% } -%> +<% end -%> + +# +# * Plugins Options +# +<% plugin_load = [] -%> +<% node['mariadb']['plugins_loading'].each { |plugin, loading| -%> + <% plugin_load.push(loading) if node['mariadb']['plugins'][plugin] %> +<% } -%> +plugin-load = <%= plugin_load.join(';') %> + +[mysqldump] +<% if node['mariadb']['mysqldump']['quick'].empty? -%> +quick +<% end -%> +<% if node['mariadb']['mysqldump']['quote_names'].empty? -%> +quote-names +<% end -%> +max_allowed_packet = <%= node['mariadb']['mysqldump']['max_allowed_packet'] %> + +[mysql] +#no-auto-rehash # faster start of mysql but no tab completition + +[isamchk] +key_buffer = <%= node['mariadb']['isamchk']['key_buffer'] %> + +# +# * IMPORTANT: Additional settings that can override those from this file! +# The files must end with '.cnf', otherwise they'll be ignored. +# +!includedir <%= node['mariadb']['configuration']['includedir'] %>/ + diff --git a/cookbooks/mediawiki/.gitignore b/cookbooks/mediawiki/.gitignore new file mode 100644 index 0000000..cbccf1e --- /dev/null +++ b/cookbooks/mediawiki/.gitignore @@ -0,0 +1,20 @@ +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ +pkg/ + +# Berkshelf +.vagrant +/cookbooks +Berksfile.lock + +# Bundler +Gemfile.lock +bin/* +.bundle/* + +.kitchen/ +.kitchen.local.yml diff --git a/cookbooks/mediawiki/.kitchen.yml b/cookbooks/mediawiki/.kitchen.yml new file mode 100644 index 0000000..d3610e7 --- /dev/null +++ b/cookbooks/mediawiki/.kitchen.yml @@ -0,0 +1,22 @@ +--- +driver: + name: vagrant + +provisioner: + name: chef_solo + +platforms: + - name: centos-6.5 + driver: + box: centos-6.5 + box_url: https://vagrantcloud.com/baremettle/centos-6.5/version/1/provider/libvirt.box +# - name: debian-7.5 +# driver: +# box: debian-7.5 +# box_url: https://vagrantcloud.com/baremettle/debian-7.5/version/1/provider/libvirt.box + +suites: + - name: default + run_list: + - "recipe[mediawiki::default]" + attributes: diff --git a/cookbooks/mediawiki/Berksfile b/cookbooks/mediawiki/Berksfile new file mode 100644 index 0000000..04f25b7 --- /dev/null +++ b/cookbooks/mediawiki/Berksfile @@ -0,0 +1,4 @@ +source "https://supermarket.getchef.com" + +metadata + diff --git a/cookbooks/mediawiki/CHANGELOG.md b/cookbooks/mediawiki/CHANGELOG.md new file mode 100644 index 0000000..c66f91a --- /dev/null +++ b/cookbooks/mediawiki/CHANGELOG.md @@ -0,0 +1,9 @@ +# 0.1.0 + +Initial release of mediawiki + +* Enhancements + * an enhancement + +* Bug Fixes + * a bug fix diff --git a/cookbooks/mediawiki/Gemfile b/cookbooks/mediawiki/Gemfile new file mode 100644 index 0000000..55f8096 --- /dev/null +++ b/cookbooks/mediawiki/Gemfile @@ -0,0 +1,18 @@ +source 'https://rubygems.org' + +gem 'berkshelf' + +# Uncomment these lines if you want to live on the Edge: +# +# group :development do +# gem "berkshelf", github: "berkshelf/berkshelf" +# gem "vagrant", github: "mitchellh/vagrant", tag: "v1.5.2" +# end +# +# group :plugins do +# gem "vagrant-berkshelf", github: "berkshelf/vagrant-berkshelf" +# gem "vagrant-omnibus", github: "schisamo/vagrant-omnibus" +# end + +gem 'test-kitchen' +gem 'kitchen-vagrant' diff --git a/cookbooks/mediawiki/LICENSE b/cookbooks/mediawiki/LICENSE new file mode 100644 index 0000000..8ce2314 --- /dev/null +++ b/cookbooks/mediawiki/LICENSE @@ -0,0 +1,3 @@ +Copyright (C) 2014 YOUR_NAME + +All rights reserved - Do Not Redistribute diff --git a/cookbooks/mediawiki/README.md b/cookbooks/mediawiki/README.md new file mode 100644 index 0000000..a0fe2a6 --- /dev/null +++ b/cookbooks/mediawiki/README.md @@ -0,0 +1,88 @@ +Mediawiki Cookbook +================== + +Installs/Configures mediawiki + +Requirements +------------ + +### Platform: + +* Centos +* Debian + +### Cookbooks: + +* apache2 +* php +* mysql +* database + +Attributes +---------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionDefault
node['mediawiki']['version']
node['mediawiki']['database']['name']
node['mediawiki']['database']['user']
node['mediawiki']['database']['password']
node['mediawiki']['server_name']
node['mediawiki']['sciptpath']
node['mediawiki']['admin_user']
node['mediawiki']['admin_password']
+ +Recipes +------- + +### mediawiki::default + +Installs/Configures mediawiki + + +License and Author +------------------ + +Author:: pulsation + +Copyright:: 2014, pulsation + +License:: BSD + diff --git a/cookbooks/mediawiki/Thorfile b/cookbooks/mediawiki/Thorfile new file mode 100644 index 0000000..b23ee16 --- /dev/null +++ b/cookbooks/mediawiki/Thorfile @@ -0,0 +1,12 @@ +# encoding: utf-8 + +require 'bundler' +require 'bundler/setup' +require 'berkshelf/thor' + +begin + require 'kitchen/thor_tasks' + Kitchen::ThorTasks.new +rescue LoadError + puts ">>>>> Kitchen gem not loaded, omitting tasks" unless ENV['CI'] +end diff --git a/cookbooks/mediawiki/Vagrantfile b/cookbooks/mediawiki/Vagrantfile new file mode 100644 index 0000000..4f44318 --- /dev/null +++ b/cookbooks/mediawiki/Vagrantfile @@ -0,0 +1,88 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +Vagrant.require_version ">= 1.5.0" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + # All Vagrant configuration is done here. The most common configuration + # options are documented and commented below. For a complete reference, + # please see the online documentation at vagrantup.com. + + config.vm.hostname = "mediawiki-berkshelf" + + # Set the version of chef to install using the vagrant-omnibus plugin + config.omnibus.chef_version = :latest + + # Every Vagrant virtual environment requires a box to build off of. + # If this value is a shorthand to a box in Vagrant Cloud then + # config.vm.box_url doesn't need to be specified. + config.vm.box = "chef/ubuntu-14.04" + + # The url from where the 'config.vm.box' box will be fetched if it + # is not a Vagrant Cloud box and if it doesn't already exist on the + # user's system. + # config.vm.box_url = "https://vagrantcloud.com/chef/ubuntu-14.04/version/1/provider/virtualbox.box" + + # Assign this VM to a host-only network IP, allowing you to access it + # via the IP. Host-only networks can talk to the host machine as well as + # any other machines on the same network, but cannot be accessed (through this + # network interface) by any external networks. + config.vm.network :private_network, type: "dhcp" + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + # config.vm.provider :virtualbox do |vb| + # # Don't boot with headless mode + # vb.gui = true + # + # # Use VBoxManage to customize the VM. For example to change memory: + # vb.customize ["modifyvm", :id, "--memory", "1024"] + # end + # + # View the documentation for the provider you're using for more + # information on available options. + + # The path to the Berksfile to use with Vagrant Berkshelf + # config.berkshelf.berksfile_path = "./Berksfile" + + # Enabling the Berkshelf plugin. To enable this globally, add this configuration + # option to your ~/.vagrant.d/Vagrantfile file + config.berkshelf.enabled = true + + # An array of symbols representing groups of cookbook described in the Vagrantfile + # to exclusively install and copy to Vagrant's shelf. + # config.berkshelf.only = [] + + # An array of symbols representing groups of cookbook described in the Vagrantfile + # to skip installing and copying to Vagrant's shelf. + # config.berkshelf.except = [] + + config.vm.provision :chef_solo do |chef| + chef.json = { + mysql: { + server_root_password: 'rootpass', + server_debian_password: 'debpass', + server_repl_password: 'replpass' + } + } + + chef.run_list = [ + "recipe[mediawiki::default]" + ] + end +end diff --git a/cookbooks/mediawiki/attributes/default.rb b/cookbooks/mediawiki/attributes/default.rb new file mode 100644 index 0000000..a5c46df --- /dev/null +++ b/cookbooks/mediawiki/attributes/default.rb @@ -0,0 +1,16 @@ +default["mediawiki"]["version"] = "1.23.1" +default["mediawiki"]["webdir"] = node['apache']['docroot_dir'] + "/mediawiki-" + default["mediawiki"]["version"] +default["mediawiki"]["tarball"]["name"] = "mediawiki-" + default["mediawiki"]["version"] + ".tar.gz" +default["mediawiki"]["tarball"]["url"] = "https://releases.wikimedia.org/mediawiki/1.23/" + default["mediawiki"]["tarball"]["name"] +default["mediawiki"]["database"]["name"] = "mediawiki" +default["mediawiki"]["database"]["user"] = "mediawiki" +default["mediawiki"]["database"]["password"] = "Ub3rPa55w0rd" +default["mediawiki"]["server_name"] = "wiki.localhost" +default["mediawiki"]["scriptpath"] = "" +default['mysql']['server_root_password'] = 'Fak3Pa55w0rd' + +default["mediawiki"]["server"] = "http://" + default["mediawiki"]["server_name"] +default["mediawiki"]["site_name"] = "my Wiki" +default["mediawiki"]["language_code"] = "fr" +default["mediawiki"]["admin_user"] = "administrator" +default["mediawiki"]["admin_password"] = "admin" diff --git a/cookbooks/mediawiki/chefignore b/cookbooks/mediawiki/chefignore new file mode 100644 index 0000000..138a808 --- /dev/null +++ b/cookbooks/mediawiki/chefignore @@ -0,0 +1,94 @@ +# Put files/directories that should be ignored in this file when uploading +# or sharing to the community site. +# Lines that start with '# ' are comments. + +# OS generated files # +###################### +.DS_Store +Icon? +nohup.out +ehthumbs.db +Thumbs.db + +# SASS # +######## +.sass-cache + +# EDITORS # +########### +\#* +.#* +*~ +*.sw[a-z] +*.bak +REVISION +TAGS* +tmtags +*_flymake.* +*_flymake +*.tmproj +.project +.settings +mkmf.log + +## COMPILED ## +############## +a.out +*.o +*.pyc +*.so +*.com +*.class +*.dll +*.exe +*/rdoc/ + +# Testing # +########### +.watchr +.rspec +spec/* +spec/fixtures/* +test/* +features/* +Guardfile +Procfile + +# SCM # +####### +.git +*/.git +.gitignore +.gitmodules +.gitconfig +.gitattributes +.svn +*/.bzr/* +*/.hg/* +*/.svn/* + +# Berkshelf # +############# +cookbooks/* +tmp + +# Cookbooks # +############# +CONTRIBUTING +CHANGELOG* + +# Strainer # +############ +Colanderfile +Strainerfile +.colander +.strainer + +# Vagrant # +########### +.vagrant +Vagrantfile + +# Travis # +########## +.travis.yml diff --git a/cookbooks/mediawiki/metadata.rb b/cookbooks/mediawiki/metadata.rb new file mode 100644 index 0000000..8d50ba7 --- /dev/null +++ b/cookbooks/mediawiki/metadata.rb @@ -0,0 +1,56 @@ +name 'mediawiki' +maintainer 'pulsation' +license 'BSD' +description 'Installs/Configures mediawiki' +long_description 'Installs/Configures mediawiki' +version '0.1.0' + +depends 'apache2' +depends 'php' +depends 'mysql' +depends 'database' + +attribute 'mediawiki/version', + :display_name => "Mediawiki version", + :type => "string", + :required => "recommended" + +attribute 'mediawiki/database/name', + :display_name => "Database name", + :type => "string", + :required => "optional" + +attribute 'mediawiki/database/user', + :display_name => "Database user", + :type => "string", + :required => "optional" + +attribute 'mediawiki/database/password', + :display_name => "Database password", + :type => "string", + :required => "optional" + +attribute 'mediawiki/server_name', + :display_name => "Server name", + :type => "string", + :required => "recommended" + +attribute 'mediawiki/sciptpath', + :display_name => "Script path", + :type => "string", + :required => "optional" + +attribute 'mediawiki/admin_user', + :display_name => "Admin user", + :type => "string", + :required => "recommended" + +attribute 'mediawiki/admin_password', + :display_name => "Admin password", + :type => "string", + :required => "recommended" + +recipe "mediawiki::default", "Installs/Configures mediawiki" + +supports "centos" +supports "debian" diff --git a/cookbooks/mediawiki/recipes/default.rb b/cookbooks/mediawiki/recipes/default.rb new file mode 100644 index 0000000..53dd413 --- /dev/null +++ b/cookbooks/mediawiki/recipes/default.rb @@ -0,0 +1,100 @@ +# +# Cookbook Name:: mediawiki +# Recipe:: default +# +# Copyright (C) 2014 YOUR_NAME +# +# All rights reserved - Do Not Redistribute +# + +include_recipe "apt" +include_recipe "apache2" +include_recipe "apache2::mod_php5" +include_recipe "apache2::mod_rewrite" +include_recipe "mysql::server" +include_recipe "database::mysql" + +include_recipe "php::default" +include_recipe "php::module_apc" +include_recipe "php::module_mysql" + +# Download mediawiki tarball +remote_file "#{Chef::Config[:file_cache_path]}/" + node['mediawiki']['tarball']['name'] do + source node['mediawiki']['tarball']['url'] +end + +# Extract mediawiki tarball +bash "extract_mediawkiki" do + user "root" + cwd node['apache']['docroot_dir'] + code "tar -zxf #{Chef::Config[:file_cache_path]}/" + node['mediawiki']['tarball']['name'] + action :run +end + +# Database connection information +mysql_connection_info = { + :host => 'localhost', + :username => 'root', + :password => node['mysql']['server_root_password'] +} + +# Create new database +mysql_database node['mediawiki']['database']['name'] do + connection mysql_connection_info + action :create +end + +# Create new user +mysql_database_user node['mediawiki']['database']['user'] do + connection mysql_connection_info + password node['mediawiki']['database']['password'] + action :create +end + +# Grant privilages to user +mysql_database_user node['mediawiki']['database']['user'] do + connection mysql_connection_info + database_name node["mediawiki"]["database"]["name"] + privileges [:all] + action :grant +end + +# Add virtualhost +web_app "mediawiki" do + server_name node["mediawiki"]["server_name"] + docroot node["mediawiki"]["webdir"] +end + +# Additional packages +case node["platform_family"] +when "rhel" + package "php-xml" + package "libicu-devel" + service "apache2" do + action :restart + end +when "debian" + package "libicu-dev" +end + + +php_pear "intl" do + action :install +end + +# Configure mediawiki database +bash "configure_mediawkiki_database" do + user "root" + cwd node["mediawiki"]["webdir"] + code "php maintenance/install.php" + + " --pass '" + node["mediawiki"]["admin_password"] + + "' --dbname '" + node["mediawiki"]["database"]["name"] + + "' --dbpass '" + node["mediawiki"]["database"]["password"] + + "' --dbuser '" + node["mediawiki"]["database"]["name"] + + "' --server '" + node["mediawiki"]["server"] + + "' --scriptpath '" + node["mediawiki"]["scriptpath"] + + "' --lang '" + node["mediawiki"]["language_code"] + + "' '" + node["mediawiki"]["site_name"] + "' '" + node["mediawiki"]["admin_user"] + "'" + action :run +end + diff --git a/cookbooks/mediawiki/templates/default/web_app.conf.erb b/cookbooks/mediawiki/templates/default/web_app.conf.erb new file mode 100644 index 0000000..ef44d22 --- /dev/null +++ b/cookbooks/mediawiki/templates/default/web_app.conf.erb @@ -0,0 +1,49 @@ +> + ServerName <%= @params[:server_name] %> + <% if @params[:server_aliases] -%> + ServerAlias <%= @params[:server_aliases].join " " %> + <% end -%> + DocumentRoot <%= @params[:docroot] %> + + > + Options <%= [@params[:directory_options] || "FollowSymLinks" ].flatten.join " " %> + AllowOverride <%= [@params[:allow_override] || "None" ].flatten.join " " %> + Order allow,deny + Allow from all + + + + Options FollowSymLinks + AllowOverride None + + + + SetHandler server-status + + Order Deny,Allow + Deny from all + Allow from 127.0.0.1 + + + LogLevel info + ErrorLog <%= node['apache']['log_dir'] %>/<%= @params[:name] %>-error.log + CustomLog <%= node['apache']['log_dir'] %>/<%= @params[:name] %>-access.log combined + + <% if @params[:directory_index] -%> + DirectoryIndex <%= [@params[:directory_index]].flatten.join " " %> + <% end -%> + + RewriteEngine On + RewriteLog <%= node['apache']['log_dir'] %>/<%= @application_name %>-rewrite.log + RewriteLogLevel 0 + + # Canonical host, <%= @params[:server_name] %> + RewriteCond %{HTTP_HOST} !^<%= @params[:server_name] %> [NC] + RewriteCond %{HTTP_HOST} !^$ + RewriteRule ^/(.*)$ http://<%= @params[:server_name] %>/$1 [L,R=301] + + RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f + RewriteCond %{SCRIPT_FILENAME} !maintenance.html + RewriteRule ^.*$ /system/maintenance.html [L] + + diff --git a/cookbooks/mysql/CHANGELOG.md b/cookbooks/mysql/CHANGELOG.md new file mode 100644 index 0000000..a716e4a --- /dev/null +++ b/cookbooks/mysql/CHANGELOG.md @@ -0,0 +1,562 @@ +mysql Cookbook CHANGELOG +======================== + +v6.0.22 (2015-05-07) +-------------------- +- Debian 8 (Jessie) support + +v6.0.21 (2015-04-08) +-------------------- +- Fix to Upstart prestart script when using custom socket +- Adding --explicit_defaults_for_timestamp mysql_install_db_cmd for + 5.6 and above + +v6.0.20 (2015-03-27) +-------------------- +- #318 - Fixing Upstart pre-start script to handle custom socket paths + +v6.0.19 (2015-03-25) +-------------------- +- Adding support for Amazon Linux 2015.03 + +v6.0.18 (2015-03-24) +-------------------- +- Adding support for 5.6 and 5.7 packages from dotdeb repos on Debian 7 + +v6.0.17 (2015-03-13) +-------------------- +- Updated for MySQL 5.7.6. +- Handing removal of mysql_install_db and mysqld_safe + +v6.0.16 (2015-03-10) +-------------------- +- Moved --defaults-file as first option to mysql_install_db_script + +v6.0.15 (2015-02-26) +-------------------- +- Updating docker detection fix to pass specs + +v6.0.14 (2015-02-26) +-------------------- +- Fixed debian system service :disable action. Now survives reboot +- Fixing centos-7 instance :enable action. Now survives +- Not applying Apparmor policy if running in a Docker container + +v6.0.13 (2015-02-15) +-------------------- +- Adding support for special characters in initial_root_password +- Fixing failure status bug in sysvinit script + +v6.0.12 (2015-02-30) +-------------------- +- No changes. Released a 6.0.11 that was identical to 6.0.10. + Git before coffee. + +v6.0.11 (2015-02-30) +-------------------- +- Adding support for configurable socket files + +v6.0.10 (2015-01-19) +------------------ +- Fix #282 - Fixing up data_dir template variable + +v6.0.9 (2015-01-19) +------------------ +- Fix #282 - undefined method `parsed_data_dir' bug + +v6.0.8 (2015-01-19) +------------------ +- Refactoring helper methods out of resource classes + +v6.0.7 (2015-01-14) +------------------ +- Fixing timing issue with Upstart provider :restart and :reload + actions where service returns before being available + +v6.0.6 (2014-12-26) +------------------ +- Fixing subtle bug where MysqlCookbook::Helper methods were polluting Chef::Resource + +v6.0.5 (2014-12-25) +------------------ +- Using 'include_recipe' instead of 'recipe_eval' in LWRP +- Fixing type checking on package_name attribute in mysql_client resource. + +v6.0.4 (2014-12-21) +------------------ +- Suggest available versions if current is not available for current platform. + +v6.0.3 (2014-12-17) +------------------ +- Adding bind_address parameter to mysql_service resource + +v6.0.2 (2014-12-17) +------------------ +- Fixing sysvinit provider to survive reboots + +v6.0.1 (2014-12-16) +------------------ +- Fixing Upstart template to survive reboots + +v6.0.0 (2014-12-15) +------------------ +- Major version update +- Cookbook now provides LWRPs instead of recipes +- Platform providers re-factored into init system providers +- Separated :create and :start actions for use in recipes that build containers +- mysql_service now supports multiple instances on the same machine +- mysql_service no longer attempts to manage user records +- Removal of debian-sys-maint +- Unified Sysvinit script that works on all platforms +- mysql_config resource introduced +- mysql_client fixed up +- Refactored acceptance tests +- Temporarily dropped FreeBSD support + +v5.6.1 (2014-10-29) +------------------ +- Use Gem::Version instead of Chef::Version + +v5.6.0 (2014-10-29) +------------------ +- Changing default charset to utf8 +- Quoting passwords in debian.cnf.erb +- Amazon 2014.09 support +- Ubuntu 14.10 support +- Only hide passwords from STDOUT via "sensitive true" in chef-client higher than 11.14 +- Updating test harness + +v5.5.4 (2014-10-07) +------------------ +- Adding sensitive flag to execute resources to protect passwords from logs + +v5.5.3 (2014-09-24) +------------------ +- Reverting back to Upstart on Ubuntu 14.04 + +v5.5.2 (2014-09-8) +------------------ +- Reverting commit that broke Debian pass_string + +v5.5.1 (2014-09-2) +------------------ +- Switching Ubuntu service provider to use SysVinit instead of Upstart + +v5.5.0 (2014-08-27) +------------------- +- Adding package version and action parameters to mysql_service resource +- Fixing Debian pass_string + +v5.4.4 (2014-08-27) +------------------- +- Changing module namespace to MysqlCookbook + +v5.4.3 (2014-08-25) +------------------- +- More refactoring. Moving helper function bits into resource parsed_parameters + +v5.4.2 (2014-08-25) +------------------- +- Moving provider local variables into definitions for RHEL provider + +v5.4.1 (2014-08-25) +------------------- +- Refactoring resources into the LWRP style with parsed parameters +- Moving provider local variables into definitions + +v5.4.0 (2014-08-25) +------------------- +- #212 - support for centos-7 (mysql55 and mysql56) +- Adding (untested) Debian-6 support +- Adding Suse support to metadata.rb +- Adding ability to change MySQL root password +- Added libmysqlclient-devel package to SuSE client provider +- Appeasing AppArmor +- Reducing duplication in client provider + +v5.3.6 (2014-06-18) +------------------- +- Fixing pid path location. Updating tests to include real RHEL + + +v5.3.4 (2014-06-16) +------------------- +- Fixing specs for Amazon Linux server package names + + +v5.3.2 (2014-06-16) +------------------- +- Fixing Amazon Linux support + + +v5.3.0 (2014-06-11) +------------------- +- #189 - Fix server_repl_password description +- #191 - Adding support for server55 and server56 on el-6 +- #193 - Fix syntax in mysql_service example +- #199 - Adding Suse support + + +v5.2.12 (2014-05-19) +-------------------- +PR #192 - recipes/server.rb should honor parameter node['mysql']['version'] + + +v5.2.10 (2014-05-15) +-------------------- +- COOK-4394 - restore freebsd support + + +v5.2.8 (2014-05-15) +------------------- +- [COOK-4653] - Missing mySQL 5.6 support for Ubuntu 14.04 + + +v5.2.6 (2014-05-07) +------------------- +- [COOK-4625] - Fix password resource parameter consumption on Debian and Ubuntu +- Fix up typos and version numbers in PLATFORMS.md +- Fix up specs from COOK-4613 changes + + +v5.2.4 (2014-05-02) +------------------- +- [COOK-4613] - Fix permissions on mysql data_dir to allow global access to mysql.sock + + +v5.2.2 (2014-04-24) +------------------- +- [COOK-4564] - Using positive tests for datadir move + + +v5.2.0 (2014-04-22) +------------------- +- [COOK-4551] - power grants.sql from resource parameters + + +v5.1.12 (2014-04-21) +-------------------- +- [COOK-4554] - Support for Debian Sid + + +v5.1.10 (2014-04-21) +-------------------- +- [COOK-4565] Support for Ubuntu 14.04 +- [COOK-4565] Adding Specs and TK platform +- Removing non-LTS 13.10 specs and TK platform + + +v5.1.8 (2014-04-12) +------------------- +Adding Ubuntu 13.04 to Platforminfo + + +v5.1.6 (2014-04-11) +------------------- +- [COOK-4548] - Add template[/etc/mysql/debian.cnf] to Ubuntu provider + + +v5.1.4 (2014-04-11) +------------------- +- [COOK-4547] - Shellescape server_root_password + + +v5.1.2 (2014-04-09) +------------------- +- [COOK-4519] - Fix error in run_dir for Ubuntu +- [COOK-4531] - Fix pid and run_dir for Debian + + +v5.1.0 (2014-04-08) +------------------- +[COOK-4523] - Allow for both :restart and :reload + + +v5.0.6 (2014-04-07) +------------------- +- [COOK-4519] - Updating specs to reflect pid file change on Ubuntu + + +v5.0.4 (2014-04-07) +------------------- +- [COOK-4519] - Fix path to pid file on Ubuntu + + +v5.0.2 (2014-04-01) +------------------- +- Moving server_deprecated into recipes directory + + +v5.0.0 (2014-03-31) +------------------- +- Rewriting as a library cookbook +- Exposing mysql_service and mysql_client resources +- User now needs to supply configuration +- Moving attribute driven recipe to server-deprecated + + +v4.1.2 (2014-02-28) +------------------- +- [COOK-4349] - Fix invalid platform check +- [COOK-4184] - Better handling of Ubuntu upstart service +- [COOK-2100] - Changing innodb_log_file_size tunable results in inability to start MySQL + + +v4.1.1 (2014-02-25) +------------------- +- **[COOK-2966] - Address foodcritic failures' +- **[COOK-4182] - Template parse failure in /etc/init/mysql.conf (data_dir)' +- **[COOK-4198] - Added missing tunable' +- **[COOK-4206] - create root@127.0.0.1, as well as root@localhost' + + +v4.0.20 (2014-01-18) +-------------------- +* [COOK-3931] - MySQL Server Recipe Regression for Non-LTS Ubuntu Versions +* [COOK-3945] - MySQL cookbook fails on Ubuntu 13.04/13.10 +* [COOK-3966] - mysql::server recipe can't find a template with debian 7.x +* [COOK-3985] - Missing /etc/mysql/debian.cnf template on mysql::_server_debian.rb recipe (mysql 4.0.4) +* [COOK-3974] - debian.cnf not updated +* [COOK-4001] - Pull request: Fixes for broken mysql::server on Debian +* [COOK-4071] - Mysql cookbook doesn't work on debian 7.2 + + +v4.0.14 +------- +Fixing style cops + + +v4.0.12 +------- +### Bug +- **[COOK-4068](https://tickets.chef.io/browse/COOK-4068)** - rework MySQL Windows recipe + +### Improvement +- **[COOK-3801](https://tickets.chef.io/browse/COOK-3801)** - Add innodb_adaptive_flushing_method and innodb_adaptive_checkpoint + + +v4.0.10 +------- +fixing metadata version error. locking to 3.0 + + +v4.0.8 +------ +Locking yum dependency to '< 3' + + +v4.0.6 +------ +# Bug +- [COOK-3943] Notifying service restart on grants update + + +v4.0.4 +------ +[COOK-3952] - Adding 'recursive true' to directory resources + + +v4.0.2 +------ +### BUGS +- Adding support for Amazon Linux in attributes/server_rhel.rb +- Fixing bug where unprivileged users cannot connect over a local socket. Adding integration test. +- Fixing bug in mysql_grants_cmd generation + + +v4.0.0 +------ +- [COOK-3928] Heavily refactoring for readability. Moving platform implementation into separate recipes +- Moving integration tests from minitest to serverspec, removing "improper" tests +- Moving many attributes into the ['mysql']['server']['whatever'] namespace +- [COOK-3481] - Merged Lucas Welsh's Windows bits and moved into own recipe +- [COOK-3697] - Adding security hardening attributes +- [COOK-3780] - Fixing data_dir on Debian and Ubuntu +- [COOK-3807] - Don't use execute[assign-root-password] on Debian and Ubuntu +- [COOK-3881] - Fixing /etc being owned by mysql user + + +v3.0.12 +------- +### Bug +- **[COOK-3752](https://tickets.chef.io/browse/COOK-3752)** - mysql service fails to start in mysql::server recipe + + +v3.0.10 +------- +- Fix a failed release attempt for v3.0.8 + + +v3.0.8 +------ +### Bug +- **[COOK-3749](https://tickets.chef.io/browse/COOK-3749)** - Fix a regression with Chef 11-specific features + + +v3.0.6 +------ +### Bug +- **[COOK-3674](https://tickets.chef.io/browse/COOK-3674)** - Fix an issue where the MySQL server fails to set the root password correctly when `data_dir` is a non-default value +- **[COOK-3647](https://tickets.chef.io/browse/COOK-3647)** - Fix README typo (databas => database) +- **[COOK-3477](https://tickets.chef.io/browse/COOK-3477)** - Fix log-queries-not-using-indexes not working +- **[COOK-3436](https://tickets.chef.io/browse/COOK-3436)** - Pull percona repo in compilation phase +- **[COOK-3208](https://tickets.chef.io/browse/COOK-3208)** - Fix README typo (LitenPort => ListenPort) +- **[COOK-3149](https://tickets.chef.io/browse/COOK-3149)** - Create my.cnf before installing +- **[COOK-2681](https://tickets.chef.io/browse/COOK-2681)** - Fix log_slow_queries for 5.5+ +- **[COOK-2606](https://tickets.chef.io/browse/COOK-2606)** - Use proper bind address on cloud providers + +### Improvement +- **[COOK-3498](https://tickets.chef.io/browse/COOK-3498)** - Add support for replicate_* variables in my.cnf + + +v3.0.4 +------ +### Bug +- **[COOK-3310](https://tickets.chef.io/browse/COOK-3310)** - Fix missing `GRANT` option +- **[COOK-3233](https://tickets.chef.io/browse/COOK-3233)** - Fix escaping special characters +- **[COOK-3156](https://tickets.chef.io/browse/COOK-3156)** - Fix GRANTS file when `remote_root_acl` is specified +- **[COOK-3134](https://tickets.chef.io/browse/COOK-3134)** - Fix Chef 11 support +- **[COOK-2318](https://tickets.chef.io/browse/COOK-2318)** - Remove redundant `if` block around `node.mysql.tunable.log_bin` + +v3.0.2 +------ +### Bug +- [COOK-2158]: apt-get update is run twice at compile time +- [COOK-2832]: mysql grants.sql file has errors depending on attrs +- [COOK-2995]: server.rb is missing a platform_family comparison value + +### Sub-task +- [COOK-2102]: `innodb_flush_log_at_trx_commit` value is incorrectly set based on CPU count + +v3.0.0 +------ +**Note** This is a backwards incompatible version with previous versions of the cookbook. Tickets that introduce incompatibility are COOK-2615 and COOK-2617. + +- [COOK-2478] - Duplicate 'read_only' server attribute in base and tunable +- [COOK-2471] - Add tunable to set slave_compressed_protocol for reduced network traffic +- [COOK-1059] - Update attributes in mysql cookbook to support missing options for my.cnf usable by Percona +- [COOK-2590] - Typo in server recipe to do with conf_dir and confd_dir +- [COOK-2602] - Add `lower_case_table_names` tunable +- [COOK-2430] - Add a tunable to create a network ACL when allowing `remote_root_access` +- [COOK-2619] - mysql: isamchk deprecated +- [COOK-2515] - Better support for SUSE distribution for mysql cookbook +- [COOK-2557] - mysql::percona_repo attributes missing and key server typo +- [COOK-2614] - Duplicate `innodb_file_per_table` +- [COOK-2145] - MySQL cookbook should remove anonymous and password less accounts +- [COOK-2553] - Enable include directory in my.cnf template for any platform +- [COOK-2615] - Rename `key_buffer` to `key_buffer_size` +- [COOK-2626] - Percona repo URL is being constructed incorrectly +- [COOK-2616] - Unneeded attribute thread_cache +- [COOK-2618] - myisam-recover not using attribute value +- [COOK-2617] - open-files is a duplicate of open-files-limit + +v2.1.2 +------ +- [COOK-2172] - Mysql cookbook duplicates `binlog_format` configuration + +v2.1.0 +------ +- [COOK-1669] - Using platform("ubuntu") in default attributes always returns true +- [COOK-1694] - Added additional my.cnf fields and reorganized cookbook to avoid race conditions with mysql startup and sql script execution +- [COOK-1851] - Support server-id and binlog_format settings +- [COOK-1929] - Update msyql server attributes file because setting attributes without specifying a precedence is deprecated +- [COOK-1999] - Add read_only tunable useful for replication slave servers + +v2.0.2 +------ +- [COOK-1967] - mysql: trailing comma in server.rb platform family + +v2.0.0 +------ +**Important note for this release** + +Under Chef Solo, you must set the node attributes for the root, debian and repl passwords or the run will completely fail. See COOK-1737 for background on this. + +- [COOK-1390] - MySQL service cannot start after reboot +- [COOK-1610] - Set root password outside preseed (blocker for drop-in mysql replacements) +- [COOK-1624] - Mysql cookbook fails to even compile on windows +- [COOK-1669] - Using platform("ubuntu") in default attributes always returns true +- [COOK-1686] - Add mysql service start +- [COOK-1687] - duplicate `innodb_buffer_pool_size` attribute +- [COOK-1704] - mysql cookbook fails spec tests when minitest-handler cookbook enabled +- [COOK-1737] - Fail a chef-solo run when `server_root_password`, `server_debian_password`, and/or `server_repl_password` is not set +- [COOK-1769] - link to database recipe in mysql README goes to old chef/cookbooks repo instead of chef-cookbook organization +- [COOK-1963] - use `platform_family` + +v1.3.0 +------ +**Important note for this release** + +This version no longer installs Ruby bindings in the client recipe by default. Use the ruby recipe if you'd like the RubyGem. If you'd like packages from your distribution, use them in your application's specific cookbook/recipe, or modify the client packages attribute. This resolves the following tickets: + +- COOK-932 +- COOK-1009 +- COOK-1384 + +Additionally, this cookbook now has tests (COOK-1439) for use under test-kitchen. + +The following issues are also addressed in this release. + +- [COOK-1443] - MySQL (>= 5.1.24) does not support `innodb_flush_method` = fdatasync +- [COOK-1175] - Add Mac OS X support +- [COOK-1289] - handle additional tunable attributes +- [COOK-1305] - add auto-increment-increment and auto-increment-offset attributes +- [COOK-1397] - make the port an attribute +- [COOK-1439] - Add MySQL cookbook tests for test-kitchen support +- [COOK-1236] - Move package names into attributes to allow percona to free-ride +- [COOK-934] - remove deprecated mysql/libraries/database.rb, use the database cookbook instead. +- [COOK-1475] - fix restart on config change + +v1.2.6 +------ +- [COOK-1113] - Use an attribute to determine if upstart is used +- [COOK-1121] - Add support for Windows +- [COOK-1140] - Fix conf.d on Debian +- [COOK-1151] - Fix server_ec2 handling /var/lib/mysql bind mount +- [COOK-1321] - Document setting password attributes for solo + +v1.2.4 +------ +- [COOK-992] - fix FATAL nameerror +- [COOK-827] - `mysql:server_ec2` recipe can't mount `data_dir` +- [COOK-945] - FreeBSD support + +v1.2.2 +------ +- [COOK-826] mysql::server recipe doesn't quote password string +- [COOK-834] Add 'scientific' and 'amazon' platforms to mysql cookbook + +v1.2.1 +------ +- [COOK-644] Mysql client cookbook 'package missing' error message is confusing +- [COOK-645] RHEL6/CentOS6 - mysql cookbook contains 'skip-federated' directive which is unsupported on MySQL 5.1 + +v1.2.0 +------ +- [COOK-684] remove mysql_database LWRP + +v1.0.8 +------ +- [COOK-633] ensure "cloud" attribute is available + +v1.0.7 +------ +- [COOK-614] expose all mysql tunable settings in config +- [COOK-617] bind to private IP if available + +v1.0.6 +------ +- [COOK-605] install mysql-client package on ubuntu/debian + +v1.0.5 +------ +- [COOK-465] allow optional remote root connections to mysql +- [COOK-455] improve platform version handling +- externalize conf_dir attribute for easier cross platform support +- change datadir attribute to data_dir for consistency + +v1.0.4 +------ +- fix regressions on debian platform +- [COOK-578] wrap root password in quotes +- [COOK-562] expose all tunables in my.cnf diff --git a/cookbooks/mysql/README.md b/cookbooks/mysql/README.md new file mode 100644 index 0000000..e8dd43f --- /dev/null +++ b/cookbooks/mysql/README.md @@ -0,0 +1,560 @@ +MySQL Cookbook +===================== + +[![Join the chat at https://gitter.im/chef-cookbooks/mysql](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/chef-cookbooks/mysql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +The Mysql Cookbook is a library cookbook that provides resource primitives +(LWRPs) for use in recipes. It is designed to be a reference example for +creating highly reusable cross-platform cookbooks. + +Scope +----- +This cookbook is concerned with the "MySQL Community Server", +particularly those shipped with F/OSS Unix and Linux distributions. It +does not address forks or value-added repackaged MySQL distributions +like Drizzle, MariaDB, or Percona. + +Requirements +------------ +- Chef 11 or higher +- Ruby 1.9 or higher (preferably from the Chef full-stack installer) +- Network accessible package repositories +- 'recipe[selinux::disabled]' on RHEL platforms + +Platform Support +---------------- +The following platforms have been tested with Test Kitchen: + +``` +|----------------+-----+-----+-----+-----+-----| +| | 5.0 | 5.1 | 5.5 | 5.6 | 5.7 | +|----------------+-----+-----+-----+-----+-----| +| debian-7 | | | X | | | +|----------------+-----+-----+-----+-----+-----| +| ubuntu-10.04 | | X | | | | +|----------------+-----+-----+-----+-----+-----| +| ubuntu-12.04 | | | X | | | +|----------------+-----+-----+-----+-----+-----| +| ubuntu-14.04 | | | X | X | | +|----------------+-----+-----+-----+-----+-----| +| centos-5 | X | X | X | X | X | +|----------------+-----+-----+-----+-----+-----| +| centos-6 | | X | X | X | X | +|----------------+-----+-----+-----+-----+-----| +| centos-7 | | | X | X | X | +|----------------+-----+-----+-----+-----+-----| +| amazon | | | X | X | X | +|----------------+-----+-----+-----+-----+-----| +| fedora-20 | | | X | X | X | +|----------------+-----+-----+-----+-----+-----| +| suse-11.3 | | | X | | | +|----------------+-----+-----+-----+-----+-----| +| omnios-151006 | | | X | X | | +|----------------+-----+-----+-----+-----+-----| +| smartos-14.3.0 | | | X | X | | +|----------------+-----+-----+-----+-----+-----| +``` + +Cookbook Dependencies +------------ +- yum-mysql-community +- smf + +Usage +----- +Place a dependency on the mysql cookbook in your cookbook's metadata.rb +```ruby +depends 'mysql', '~> 6.0' +``` + +Then, in a recipe: + +```ruby +mysql_service 'foo' do + port '3306' + version '5.5' + initial_root_password 'change me' + action [:create, :start] +end +``` + +The service name on the OS is `mysql-foo`. You can manually start and +stop it with `service mysql-foo start` and `service mysql-foo stop`. + +The configuration file is at `/etc/mysql-foo/my.cnf`. It contains the +minimum options to get the service running. It looks like this. + +``` +# Chef generated my.cnf for instance mysql-default + +[client] +default-character-set = utf8 +port = 3306 +socket = /var/run/mysql-foo/mysqld.sock + +[mysql] +default-character-set = utf8 + +[mysqld] +user = mysql +pid-file = /var/run/mysql-foo/mysqld.pid +socket = /var/run/mysql-foo/mysqld.sock +port = 3306 +datadir = /var/lib/mysql-foo +tmpdir = /tmp +log-error = /var/log/mysql-foo/error.log +!includedir /etc/mysql-foo/conf.d + +[mysqld_safe] +socket = /var/run/mysql-foo/mysqld.sock +``` + +You can put extra configuration into the conf.d directory by using the +`mysql_config` resource, like this: + +```ruby +mysql_service 'foo' do + port '3306' + version '5.5' + initial_root_password 'change me' + action [:create, :start] +end + +mysql_config 'foo' do + source 'my_extra_settings.erb' + notifies :restart, 'mysql_service[foo]' + action :create +end +``` + +You are responsible for providing `my_extra_settings.erb` in your own +cookbook's templates folder. + +Connecting with the mysql CLI command +------------------------------------- +Logging into the machine and typing `mysql` with no extra arguments +will fail. You need to explicitly connect over the socket with `mysql +-S /var/run/mysql-foo/mysqld.sock`, or over the network with `mysql -h +127.0.0.1` + +Upgrading from older version of the mysql cookbook +-------------------------------------------------- +- It is strongly recommended that you rebuild the machine from + scratch. This is easy if you have your `data_dir` on a dedicated + mount point. If you *must* upgrade in-place, follow the instructions + below. + +- The 6.x series supports multiple service instances on a single + machine. It dynamically names the support directories and service + names. `/etc/mysql becomes /etc/mysql-instance_name`. Other support + directories in `/var` `/run` etc work the same way. Make sure to + specify the `data_dir` property on the `mysql_service` resource to + point to the old `/var/lib/mysql` directory. + +Resources Overview +------------------ +### mysql_service + +The `mysql_service` resource manages the basic plumbing needed to get a +MySQL server instance running with minimal configuration. + +The `:create` action handles package installation, support +directories, socket files, and other operating system level concerns. +The internal configuration file contains just enough to get the +service up and running, then loads extra configuration from a conf.d +directory. Further configurations are managed with the `mysql_config` resource. + +- If the `data_dir` is empty, a database will be initialized, and a +root user will be set up with `initial_root_password`. If this +directory already contains database files, no action will be taken. + +The `:start` action starts the service on the machine using the +appropriate provider for the platform. The `:start` action should be +omitted when used in recipes designed to build containers. + +#### Example +```ruby +mysql_service 'default' do + version '5.7' + bind_address '0.0.0.0' + port '3306' + data_dir '/data' + initial_root_password 'Ch4ng3me' + action [:create, :start] +end +``` + +Please note that when using `notifies` or `subscribes`, the resource +to reference is `mysql_service[name]`, not `service[mysql]`. + +#### Parameters + +- `charset` - specifies the default character set. Defaults to `utf8`. + +- `data_dir` - determines where the actual data files are kept +on the machine. This is useful when mounting external storage. When +omitted, it will default to the platform's native location. + +- `initial_root_password` - allows the user to specify the initial + root password for mysql when initializing new databases. + This can be set explicitly in a recipe, driven from a node + attribute, or from data_bags. When omitted, it defaults to + `ilikerandompasswords`. Please be sure to change it. + +- `instance` - A string to identify the MySQL service. By convention, + to allow for multiple instances of the `mysql_service`, directories + and files on disk are named `mysql-`. Defaults to the + resource name. + +- `package_action` - Defaults to `:install`. + +- `package_name` - Defaults to a value looked up in an internal map. + +- `package_version` - Specific version of the package to install, + passed onto the underlying package manager. Defaults to `nil`. + +- `bind_address` - determines the listen IP address for the mysqld service. When + omitted, it will be determined by MySQL. If the address is "regular" IPv4/IPv6 + address (e.g 127.0.0.1 or ::1), the server accepts TCP/IP connections only for + that particular address. If the address is "0.0.0.0" (IPv4) or "::" (IPv6), the + server accepts TCP/IP connections on all IPv4 or IPv6 interfaces. + +- `port` - determines the listen port for the mysqld service. When + omitted, it will default to '3306'. + +- `run_group` - The name of the system group the `mysql_service` + should run as. Defaults to 'mysql'. + +- `run_user` - The name of the system user the `mysql_service` should + run as. Defaults to 'mysql'. + +- `socket` - determines where to write the socket file for the + `mysql_service` instance. Useful when configuring clients on the + same machine to talk over socket and skip the networking stack. + Defaults to a calculated value based on platform and instance name. + +- `version` - allows the user to select from the versions available + for the platform, where applicable. When omitted, it will install + the default MySQL version for the target platform. Available version + numbers are `5.0`, `5.1`, `5.5`, `5.6`, and `5.7`, depending on platform. + +#### Actions + +- `:create` - Configures everything but the underlying operating system service. +- `:delete` - Removes everything but the package and data_dir. +- `:start` - Starts the underlying operating system service +- `:stop`- Stops the underlying operating system service +- `:restart` - Restarts the underlying operating system service +- `:reload` - Reloads the underlying operating system service + +#### Providers +Chef selects the appropriate provider based on platform and version, +but you can specify one if your platform support it. + +```ruby +mysql_service[instance-1] do + port '1234' + data_dir '/mnt/lottadisk' + provider Chef::Provider::MysqlService::Sysvinit + action [:create, :start] +end +``` + +- `Chef::Provider::MysqlService` - Configures everything needed t run +a MySQL service except the platform service facility. This provider +should never be used directly. The `:start`, `:stop`, `:restart`, and +`:reload` actions are stubs meant to be overridden by the providers +below. + +- `Chef::Provider::MysqlService::Smf` - Starts a `mysql_service` using +the Service Management Facility, used by Solaris and IllumOS. Manages +the FMRI and method script. + +- `Chef::Provider::MysqlService::Systemd` - Starts a `mysql_service` +using SystemD. Manages the unit file and activation state + +- `Chef::Provider::MysqlService::Sysvinit` - Starts a `mysql_service` +using SysVinit. Manages the init script and status. + +- `Chef::Provider::MysqlService::Upstart` - Starts a `mysql_service` +using Upstart. Manages job definitions and status. + +### mysql_config + +The `mysql_config` resource is a wrapper around the core Chef +`template` resource. Instead of a `path` parameter, it uses the +`instance` parameter to calculate the path on the filesystem where +file is rendered. + +#### Example + +```ruby +mysql_config[default] do + source 'site.cnf.erb' + action :create +end +``` + +#### Parameters + +- `config_name` - The base name of the configuration file to be + rendered into the conf.d directory on disk. Defaults to the resource + name. + +- `cookbook` - The name of the cookbook to look for the template + source. Defaults to nil + +- `group` - System group for file ownership. Defaults to 'mysql'. + +- `instance` - Name of the `mysql_service` instance the config is + meant for. Defaults to 'default'. + +- `owner` - System user for file ownership. Defaults to 'mysql'. + +- `source` - Template in cookbook to be rendered. + +- `variables` - Variables to be passed to the underlying `template` + resource. + +- `version` - Version of the `mysql_service` instance the config is + meant for. Used to calculate path. Only necessary when using + packages with unique configuration paths, such as RHEL Software + Collections or OmniOS. Defaults to 'nil' + +#### Actions +- `:create` - Renders the template to disk at a path calculated using + the instance parameter. + +- `:delete` - Deletes the file from the conf.d directory calculated + using the instance parameter. + +#### More Examples +```ruby +mysql_service 'instance-1' do + action [:create, :start] +end + +mysql_service 'instance-2' do + action [:create, :start] +end + +mysql_config 'logging' do + instance 'instance-1' + source 'logging.cnf.erb' + action :create + notifies :restart, 'mysql_service[instance-1]' +end + +mysql_config 'security settings for instance-2' do + config_name 'security' + instance 'instance-2' + source 'security_stuff.cnf.erb' + variables(:foo => 'bar') + action :create + notifies :restart, 'mysql_service[instance-2]' +end +``` + +### mysql_client +The `mysql_client` resource manages the MySQL client binaries and +development libraries. + +It is an example of a "singleton" resource. Declaring two +`mysql_client` resources on a machine usually won't yield two separate +copies of the client binaries, except for platforms that support +multiple versions (RHEL SCL, OmniOS). + +#### Example +```ruby +mysql_client 'default' do + action :create +end +``` + +#### Parameters +- `package_name` - An array of packages to be installed. Defaults to a + value looked up in an internal map. + +- `package_version` - Specific versions of the package to install, + passed onto the underlying package manager. Defaults to `nil`. + +- `version` - Major MySQL version number of client packages. Only + valid on for platforms that support multiple versions, such as RHEL + via Software Collections and OmniOS. + +#### Actions +- `:create` - Installs the client software +- `:delete` - Removes the client software + +Advanced Usage Examples +----------------------- +There are a number of configuration scenarios supported by the use of +resource primitives in recipes. For example, you might want to run +multiple MySQL services, as different users, and mount block devices +that contain pre-existing databases. + +### Multiple Instances as Different Users + +```ruby +# instance-1 +user 'alice' do + action :create +end + +directory '/mnt/data/mysql/instance-1' do + owner 'alice' + action :create +end + +mount '/mnt/data/mysql/instance-1' do + device '/dev/sdb1' + fstype 'ext4' + action [:mount, :enable] +end + +mysql_service 'instance-1' do + port '3307' + run_user 'alice' + data_dir '/mnt/data/mysql/instance-1' + action [:create, :start] +end + +mysql_config 'site config for instance-1' do + instance 'instance-1' + source 'instance-1.cnf.erb' + notifies :restart, 'mysql_service[instance-1]' +end + +# instance-2 +user 'bob' do + action :create +end + +directory '/mnt/data/mysql/instance-2' do + owner 'bob' + action :create +end + +mount '/mnt/data/mysql/instance-2' do + device '/dev/sdc1' + fstype 'ext3' + action [:mount, :enable] +end + +mysql_service 'instance-2' do + port '3308' + run_user 'bob' + data_dir '/mnt/data/mysql/instance-2' + action [:create, :start] +end + +mysql_config 'site config for instance-2' do + instance 'instance-2' + source 'instance-2.cnf.erb' + notifies :restart, 'mysql_service[instance-2]' +end +``` + +### Replication Testing +Use multiple `mysql_service` instances to test a replication setup. +This particular example serves as a smoke test in Test Kitchen because +it exercises different resources and requires service restarts. + +https://github.com/chef-cookbooks/mysql/blob/master/test/fixtures/cookbooks/mysql_replication_test/recipes/default.rb + +Frequently Asked Questions +-------------------------- + +### How do I run this behind my firewall? + +On Linux, the `mysql_service` resource uses the platform's underlying +package manager to install software. For this to work behind +firewalls, you'll need to either: + +- Configure the system yum/apt utilities to use a proxy server that + can reach the Internet +- Host a package repository on a network that the machine can talk to + +On the RHEL platform_family, applying the `yum::default` recipe will +allow you to drive the `yum_globalconfig` resource with attributes to +change the global yum proxy settings. + +If hosting repository mirrors, applying one of the following recipes +and adjust the settings with node attributes. + +- `recipe[yum-centos::default]` from the Supermarket + https://supermarket.chef.io/cookbooks/yum-centos + https://github.com/chef-cookbooks/yum-centos + +- `recipe[yum-mysql-community::default]` from the Supermarket + https://supermarket.chef.io/cookbooks/yum-mysql-community + https://github.com/chef-cookbooks/yum-mysql-community + +### The mysql command line doesn't work + +If you log into the machine and type `mysql`, you may see an error +like this one: + +`Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'` + +This is because MySQL is hardcoded to read the defined default my.cnf +file, typically at /etc/my.cnf, and this LWRP deletes it to prevent +overlap among multiple MySQL configurations. + +To connect to the socket from the command line, check the socket in the relevant my.cnf file and use something like this: + +`mysql -S /var/run/mysql-default/mysqld.sock -Pwhatever` + +Or to connect over the network, use something like this: +connect over the network.. + +`mysql -h 127.0.0.1 -Pwhatever` + +These network or socket ssettings can also be put in you +$HOME/.my.cnf, if preferred. + +### What about MariaDB, Percona, Drizzle, WebScaleSQL, etc. + +MySQL forks are purposefully out of scope for this cookbook. This is +mostly to reduce the testing matrix to a manageable size. Cookbooks +for these technologies can easily be created by copying and adapting +this cookbook. However, there will be differences. + +Package repository locations, package version names, software major +version numbers, supported platform matrices, and the availability of +software such as XtraDB and Galera are the main reasons that creating +multiple cookbooks to make sense. + +Warnings +-------- + +Hacking / Testing / TODO +------------------------- +Please refer to the HACKING.md + +License & Authors +----------------- +- Author:: Joshua Timberman () +- Author:: AJ Christensen () +- Author:: Seth Chisamore () +- Author:: Brian Bianco () +- Author:: Jesse Howarth () +- Author:: Andrew Crump () +- Author:: Christoph Hartmann () +- Author:: Sean OMeara () + +```text +Copyright:: 2009-2014 Chef Software, Inc + +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/cookbooks/mysql/libraries/helpers.rb b/cookbooks/mysql/libraries/helpers.rb new file mode 100644 index 0000000..0e53eeb --- /dev/null +++ b/cookbooks/mysql/libraries/helpers.rb @@ -0,0 +1,416 @@ +require 'shellwords' + +module MysqlCookbook + module Helpers + include Chef::DSL::IncludeRecipe + + def base_dir + prefix_dir || '/usr' + end + + def configure_package_repositories + # we need to enable the yum-mysql-community repository to get packages + return unless %w(rhel fedora).include? node['platform_family'] + case parsed_version + when '5.5' + # Prefer packages from native repos + return if node['platform_family'] == 'rhel' && node['platform_version'].to_i == 5 + return if node['platform_family'] == 'fedora' + include_recipe('yum-mysql-community::mysql55') + when '5.6' + include_recipe('yum-mysql-community::mysql56') + when '5.7' + include_recipe('yum-mysql-community::mysql57') + end + end + + def client_package_name + return new_resource.package_name if new_resource.package_name + client_package + end + + def defaults_file + "#{etc_dir}/my.cnf" + end + + def error_log + "#{log_dir}/error.log" + end + + def etc_dir + return "/opt/mysql#{pkg_ver_string}/etc/#{mysql_name}" if node['platform_family'] == 'omnios' + return "#{prefix_dir}/etc/#{mysql_name}" if node['platform_family'] == 'smartos' + "#{prefix_dir}/etc/#{mysql_name}" + end + + def include_dir + "#{etc_dir}/conf.d" + end + + def lc_messages_dir + end + + def log_dir + return "/var/adm/log/#{mysql_name}" if node['platform_family'] == 'omnios' + "#{prefix_dir}/var/log/#{mysql_name}" + end + + def mysql_name + "mysql-#{new_resource.instance}" + end + + def pkg_ver_string + parsed_version.gsub('.', '') if node['platform_family'] == 'omnios' + end + + def prefix_dir + return "/opt/mysql#{pkg_ver_string}" if node['platform_family'] == 'omnios' + return '/opt/local' if node['platform_family'] == 'smartos' + return "/opt/rh/#{scl_name}/root" if scl_package? + end + + def scl_name + return unless node['platform_family'] == 'rhel' + return 'mysql51' if parsed_version == '5.1' && node['platform_version'].to_i == 5 + return 'mysql55' if parsed_version == '5.5' && node['platform_version'].to_i == 5 + end + + def scl_package? + return unless node['platform_family'] == 'rhel' + return true if parsed_version == '5.1' && node['platform_version'].to_i == 5 + return true if parsed_version == '5.5' && node['platform_version'].to_i == 5 + false + end + + def system_service_name + return 'mysql51-mysqld' if node['platform_family'] == 'rhel' && scl_name == 'mysql51' + return 'mysql55-mysqld' if node['platform_family'] == 'rhel' && scl_name == 'mysql55' + return 'mysqld' if node['platform_family'] == 'rhel' + return 'mysqld' if node['platform_family'] == 'fedora' + return 'mysql' if node['platform_family'] == 'debian' + return 'mysql' if node['platform_family'] == 'suse' + return 'mysql' if node['platform_family'] == 'omnios' + return 'mysql' if node['platform_family'] == 'smartos' + end + + def v56plus + return false if parsed_version.split('.')[0].to_i < 5 + return false if parsed_version.split('.')[1].to_i < 6 + true + end + + def v57plus + return false if parsed_version.split('.')[0].to_i < 5 + return false if parsed_version.split('.')[1].to_i < 7 + true + end + + # database and initial records + # initialization commands + + def mysqld_initialize_cmd + cmd = mysqld_bin + cmd << " --defaults-file=#{etc_dir}/my.cnf" + cmd << ' --initialize' + cmd << ' --explicit_defaults_for_timestamp' if v56plus + return "scl enable #{scl_name} \"#{cmd}\"" if scl_package? + cmd + end + + def mysql_install_db_cmd + cmd = mysql_install_db_bin + cmd << " --defaults-file=#{etc_dir}/my.cnf" + cmd << " --datadir=#{parsed_data_dir}" + cmd << ' --explicit_defaults_for_timestamp' if v56plus + return "scl enable #{scl_name} \"#{cmd}\"" if scl_package? + cmd + end + + def record_init + cmd = v56plus ? mysqld_bin : mysqld_safe_bin + cmd << " --defaults-file=#{etc_dir}/my.cnf" + cmd << " --init-file=/tmp/#{mysql_name}/my.sql" + cmd << ' --explicit_defaults_for_timestamp' if v56plus + cmd << ' &' + return "scl enable #{scl_name} \"#{cmd}\"" if scl_package? + cmd + end + + def db_init + return mysqld_initialize_cmd if v57plus + mysql_install_db_cmd + end + + def init_records_script + <<-EOS + set -e + rm -rf /tmp/#{mysql_name} + mkdir /tmp/#{mysql_name} + + cat > /tmp/#{mysql_name}/my.sql <<-EOSQL +DELETE FROM mysql.user ; +CREATE USER 'root'@'%' IDENTIFIED BY '#{Shellwords.escape(new_resource.initial_root_password)}' ; +GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ; +FLUSH PRIVILEGES; +DROP DATABASE IF EXISTS test ; +EOSQL + + #{db_init} + #{record_init} + + while [ ! -f #{pid_file} ] ; do sleep 1 ; done + kill `cat #{pid_file}` + while [ -f #{pid_file} ] ; do sleep 1 ; done + rm -rf /tmp/#{mysql_name} + EOS + end + + def mysql_bin + return "#{prefix_dir}/bin/mysql" if node['platform_family'] == 'smartos' + return "#{base_dir}/bin/mysql" if node['platform_family'] == 'omnios' + "#{prefix_dir}/usr/bin/mysql" + end + + def mysql_install_db_bin + return "#{base_dir}/scripts/mysql_install_db" if node['platform_family'] == 'omnios' + return "#{prefix_dir}/bin/mysql_install_db" if node['platform_family'] == 'smartos' + 'mysql_install_db' + end + + def mysql_version + new_resource.version + end + + def mysqladmin_bin + return "#{prefix_dir}/bin/mysqladmin" if node['platform_family'] == 'smartos' + return 'mysqladmin' if scl_package? + "#{prefix_dir}/usr/bin/mysqladmin" + end + + def mysqld_bin + return "#{prefix_dir}/libexec/mysqld" if node['platform_family'] == 'smartos' + return "#{base_dir}/bin/mysqld" if node['platform_family'] == 'omnios' + return '/usr/sbin/mysqld' if node['platform_family'] == 'fedora' && v56plus + return '/usr/libexec/mysqld' if node['platform_family'] == 'fedora' + return 'mysqld' if scl_package? + "#{prefix_dir}/usr/sbin/mysqld" + end + + def mysqld_safe_bin + return "#{prefix_dir}/bin/mysqld_safe" if node['platform_family'] == 'smartos' + return "#{base_dir}/bin/mysqld_safe" if node['platform_family'] == 'omnios' + return 'mysqld_safe' if scl_package? + "#{prefix_dir}/usr/bin/mysqld_safe" + end + + def pid_file + "#{run_dir}/mysqld.pid" + end + + def run_dir + return "#{prefix_dir}/var/run/#{mysql_name}" if node['platform_family'] == 'rhel' + return "/run/#{mysql_name}" if node['platform_family'] == 'debian' + "/var/run/#{mysql_name}" + end + + def sensitive_supported? + Gem::Version.new(Chef::VERSION) >= Gem::Version.new('11.14.0') + end + + def socket_file + return new_resource.socket if new_resource.socket + "#{run_dir}/mysqld.sock" + end + + def socket_dir + return File.dirname(new_resource.socket) if new_resource.socket + run_dir + end + + def tmp_dir + '/tmp' + end + + ####### + # FIXME: There is a LOT of duplication here.. + # There has to be a less gnarly way to look up this information. Refactor for great good! + ####### + class Pkginfo + def self.pkginfo + # Autovivification is Perl. + @pkginfo = Chef::Node.new + + @pkginfo.set['debian']['10.04']['5.1']['client_package'] = %w(mysql-client-5.1 libmysqlclient-dev) + @pkginfo.set['debian']['10.04']['5.1']['server_package'] = 'mysql-server-5.1' + @pkginfo.set['debian']['12.04']['5.5']['client_package'] = %w(mysql-client-5.5 libmysqlclient-dev) + @pkginfo.set['debian']['12.04']['5.5']['server_package'] = 'mysql-server-5.5' + @pkginfo.set['debian']['13.04']['5.5']['client_package'] = %w(mysql-client-5.5 libmysqlclient-dev) + @pkginfo.set['debian']['13.04']['5.5']['server_package'] = 'mysql-server-5.5' + @pkginfo.set['debian']['13.10']['5.5']['client_package'] = %w(mysql-client-5.5 libmysqlclient-dev) + @pkginfo.set['debian']['13.10']['5.5']['server_package'] = 'mysql-server-5.5' + @pkginfo.set['debian']['14.04']['5.5']['client_package'] = %w(mysql-client-5.5 libmysqlclient-dev) + @pkginfo.set['debian']['14.04']['5.5']['server_package'] = 'mysql-server-5.5' + @pkginfo.set['debian']['14.04']['5.6']['client_package'] = %w(mysql-client-5.6 libmysqlclient-dev) + @pkginfo.set['debian']['14.04']['5.6']['server_package'] = 'mysql-server-5.6' + @pkginfo.set['debian']['14.10']['5.5']['client_package'] = %w(mysql-client-5.5 libmysqlclient-dev) + @pkginfo.set['debian']['14.10']['5.5']['server_package'] = 'mysql-server-5.5' + @pkginfo.set['debian']['14.10']['5.6']['client_package'] = %w(mysql-client-5.6 libmysqlclient-dev) + @pkginfo.set['debian']['14.10']['5.6']['server_package'] = 'mysql-server-5.6' + @pkginfo.set['debian']['6']['5.1']['client_package'] = %w(mysql-client libmysqlclient-dev) + @pkginfo.set['debian']['6']['5.1']['server_package'] = 'mysql-server-5.1' + @pkginfo.set['debian']['7']['5.5']['client_package'] = %w(mysql-client libmysqlclient-dev) + @pkginfo.set['debian']['7']['5.5']['server_package'] = 'mysql-server-5.5' + @pkginfo.set['debian']['7']['5.6']['client_package'] = %w(mysql-client libmysqlclient-dev) # apt-repo from dotdeb + @pkginfo.set['debian']['7']['5.6']['server_package'] = 'mysql-server-5.6' + @pkginfo.set['debian']['7']['5.7']['client_package'] = %w(mysql-client libmysqlclient-dev) # apt-repo from dotdeb + @pkginfo.set['debian']['7']['5.7']['server_package'] = 'mysql-server-5.7' + @pkginfo.set['debian']['8']['5.5']['client_package'] = %w(mysql-client libmysqlclient-dev) + @pkginfo.set['debian']['8']['5.5']['server_package'] = 'mysql-server-5.5' + @pkginfo.set['fedora']['20']['5.5']['client_package'] = %w(community-mysql community-mysql-devel) + @pkginfo.set['fedora']['20']['5.5']['server_package'] = 'community-mysql-server' + @pkginfo.set['fedora']['20']['5.6']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['fedora']['20']['5.6']['server_package'] = 'mysql-community-server' + @pkginfo.set['fedora']['20']['5.7']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['fedora']['20']['5.7']['server_package'] = 'mysql-community-server' + @pkginfo.set['freebsd']['10']['5.5']['client_package'] = %w(mysql55-client) + @pkginfo.set['freebsd']['10']['5.5']['server_package'] = 'mysql55-server' + @pkginfo.set['freebsd']['9']['5.5']['client_package'] = %w(mysql55-client) + @pkginfo.set['freebsd']['9']['5.5']['server_package'] = 'mysql55-server' + @pkginfo.set['omnios']['151006']['5.5']['client_package'] = %w(database/mysql-55/library) + @pkginfo.set['omnios']['151006']['5.5']['server_package'] = 'database/mysql-55' + @pkginfo.set['omnios']['151006']['5.6']['client_package'] = %w(database/mysql-56) + @pkginfo.set['omnios']['151006']['5.6']['server_package'] = 'database/mysql-56' + @pkginfo.set['rhel']['2014.09']['5.1']['server_package'] = %w(mysql51 mysql51-devel) + @pkginfo.set['rhel']['2014.09']['5.1']['server_package'] = 'mysql51-server' + @pkginfo.set['rhel']['2014.09']['5.5']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['2014.09']['5.5']['server_package'] = 'mysql-community-server' + @pkginfo.set['rhel']['2014.09']['5.6']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['2014.09']['5.6']['server_package'] = 'mysql-community-server' + @pkginfo.set['rhel']['2014.09']['5.7']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['2014.09']['5.7']['server_package'] = 'mysql-community-server' + @pkginfo.set['rhel']['2015.03']['5.1']['server_package'] = %w(mysql51 mysql51-devel) + @pkginfo.set['rhel']['2015.03']['5.1']['server_package'] = 'mysql51-server' + @pkginfo.set['rhel']['2015.03']['5.5']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['2015.03']['5.5']['server_package'] = 'mysql-community-server' + @pkginfo.set['rhel']['2015.03']['5.6']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['2015.03']['5.6']['server_package'] = 'mysql-community-server' + @pkginfo.set['rhel']['2015.03']['5.7']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['2015.03']['5.7']['server_package'] = 'mysql-community-server' + @pkginfo.set['rhel']['5']['5.0']['client_package'] = %w(mysql mysql-devel) + @pkginfo.set['rhel']['5']['5.0']['server_package'] = 'mysql-server' + @pkginfo.set['rhel']['5']['5.1']['client_package'] = %w(mysql51-mysql) + @pkginfo.set['rhel']['5']['5.1']['server_package'] = 'mysql51-mysql-server' + @pkginfo.set['rhel']['5']['5.5']['client_package'] = %w(mysql55-mysql mysql55-mysql-devel) + @pkginfo.set['rhel']['5']['5.5']['server_package'] = 'mysql55-mysql-server' + @pkginfo.set['rhel']['5']['5.6']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['5']['5.6']['server_package'] = 'mysql-community-server' + @pkginfo.set['rhel']['5']['5.7']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['5']['5.7']['server_package'] = 'mysql-community-server' + @pkginfo.set['rhel']['6']['5.1']['client_package'] = %w(mysql mysql-devel) + @pkginfo.set['rhel']['6']['5.1']['server_package'] = 'mysql-server' + @pkginfo.set['rhel']['6']['5.5']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['6']['5.5']['server_package'] = 'mysql-community-server' + @pkginfo.set['rhel']['6']['5.6']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['6']['5.6']['server_package'] = 'mysql-community-server' + @pkginfo.set['rhel']['6']['5.7']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['6']['5.7']['server_package'] = 'mysql-community-server' + @pkginfo.set['rhel']['7']['5.5']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['7']['5.5']['server_package'] = 'mysql-community-server' + @pkginfo.set['rhel']['7']['5.6']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['7']['5.6']['server_package'] = 'mysql-community-server' + @pkginfo.set['rhel']['7']['5.7']['client_package'] = %w(mysql-community-client mysql-community-devel) + @pkginfo.set['rhel']['7']['5.7']['server_package'] = 'mysql-community-server' + @pkginfo.set['smartos']['5.11']['5.5']['client_package'] = %w(mysql-client) + @pkginfo.set['smartos']['5.11']['5.5']['server_package'] = 'mysql-server' + @pkginfo.set['smartos']['5.11']['5.6']['client_package'] = %w(mysql-client) + @pkginfo.set['smartos']['5.11']['5.6']['server_package'] = 'mysql-server' + @pkginfo.set['suse']['11.3']['5.5']['client_package'] = %w(mysql-client) + @pkginfo.set['suse']['11.3']['5.5']['server_package'] = 'mysql' + + @pkginfo + end + end + + def package_name_for(platform, platform_family, platform_version, version, type) + keyname = keyname_for(platform, platform_family, platform_version) + info = Pkginfo.pkginfo[platform_family.to_sym][keyname] + type_label = type.to_s.gsub('_package', '').capitalize + unless info[version] + # Show availabe versions if the requested is not available on the current platform + Chef::Log.error("Unsupported Version: You requested to install a Mysql #{type_label} version that is not supported by your platform") + Chef::Log.error("Platform: #{platform_family} #{platform_version} - Request Mysql #{type_label} version: #{version}") + Chef::Log.error("Availabe versions for your platform are: #{info.map { |k, _v| k }.join(' - ')}") + fail "Unsupported Mysql #{type_label} Version" + end + info[version][type] + end + + def keyname_for(platform, platform_family, platform_version) + return platform_version if platform_family == 'debian' && platform == 'ubuntu' + return platform_version if platform_family == 'fedora' + return platform_version if platform_family == 'omnios' + return platform_version if platform_family == 'rhel' && platform == 'amazon' + return platform_version if platform_family == 'smartos' + return platform_version if platform_family == 'suse' + return platform_version.to_i.to_s if platform_family == 'debian' + return platform_version.to_i.to_s if platform_family == 'rhel' + return platform_version.to_s if platform_family == 'debian' && platform_version =~ /sid$/ + return platform_version.to_s if platform_family == 'freebsd' + end + + def parsed_data_dir + return new_resource.data_dir if new_resource.data_dir + return "/opt/local/lib/#{mysql_name}" if node['os'] == 'solaris2' + return "/var/lib/#{mysql_name}" if node['os'] == 'linux' + return "/var/db/#{mysql_name}" if node['os'] == 'freebsd' + end + + def client_package + package_name_for( + node['platform'], + node['platform_family'], + node['platform_version'], + parsed_version, + :client_package + ) + end + + def server_package + package_name_for( + node['platform'], + node['platform_family'], + node['platform_version'], + parsed_version, + :server_package + ) + end + + def server_package_name + return new_resource.package_name if new_resource.package_name + server_package + end + + def parsed_version + return new_resource.version if new_resource.version + return '5.0' if node['platform_family'] == 'rhel' && node['platform_version'].to_i == 5 + return '5.1' if node['platform_family'] == 'debian' && node['platform_version'] == '10.04' + return '5.1' if node['platform_family'] == 'debian' && node['platform_version'].to_i == 6 + return '5.1' if node['platform_family'] == 'rhel' && node['platform_version'].to_i == 6 + return '5.5' if node['platform_family'] == 'debian' && node['platform_version'] == '12.04' + return '5.5' if node['platform_family'] == 'debian' && node['platform_version'] == '13.04' + return '5.5' if node['platform_family'] == 'debian' && node['platform_version'] == '13.10' + return '5.5' if node['platform_family'] == 'debian' && node['platform_version'] == '14.04' + return '5.5' if node['platform_family'] == 'debian' && node['platform_version'] == '14.10' + return '5.5' if node['platform_family'] == 'debian' && node['platform_version'].to_i == 7 + return '5.5' if node['platform_family'] == 'debian' && node['platform_version'].to_i == 8 + return '5.5' if node['platform_family'] == 'fedora' + return '5.5' if node['platform_family'] == 'freebsd' + return '5.5' if node['platform_family'] == 'omnios' + return '5.5' if node['platform_family'] == 'rhel' && node['platform_version'].to_i == 2014 + return '5.5' if node['platform_family'] == 'rhel' && node['platform_version'].to_i == 2015 + return '5.5' if node['platform_family'] == 'rhel' && node['platform_version'].to_i == 7 + return '5.5' if node['platform_family'] == 'smartos' + return '5.5' if node['platform_family'] == 'suse' + end + end +end diff --git a/cookbooks/mysql/libraries/matchers.rb b/cookbooks/mysql/libraries/matchers.rb new file mode 100644 index 0000000..f806d00 --- /dev/null +++ b/cookbooks/mysql/libraries/matchers.rb @@ -0,0 +1,28 @@ +if defined?(ChefSpec) + # config + def create_mysql_config(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:mysql_config, :create, resource_name) + end + + def delete_mysql_config(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:mysql_config, :delete, resource_name) + end + + # service + def create_mysql_service(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:mysql_service, :create, resource_name) + end + + def delete_mysql_service(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:mysql_service, :delete, resource_name) + end + + # client + def create_mysql_client(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:mysql_client, :create, resource_name) + end + + def delete_mysql_client(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:mysql_client, :delete, resource_name) + end +end diff --git a/cookbooks/mysql/libraries/provider_mysql_client.rb b/cookbooks/mysql/libraries/provider_mysql_client.rb new file mode 100644 index 0000000..0aca4e2 --- /dev/null +++ b/cookbooks/mysql/libraries/provider_mysql_client.rb @@ -0,0 +1,38 @@ +require 'chef/provider/lwrp_base' +require_relative 'helpers' + +class Chef + class Provider + class MysqlClient < Chef::Provider::LWRPBase + include MysqlCookbook::Helpers + + use_inline_resources if defined?(use_inline_resources) + + def whyrun_supported? + true + end + + action :create do + # From helpers.rb + configure_package_repositories + + client_package_name.each do |p| + package "#{new_resource.name} :create #{p}" do + package_name p + version new_resource.version if node['platform'] == 'smartos' + version new_resource.package_version + action :install + end + end + end + + action :delete do + parsed_package_name.each do |p| + package "#{new_resource.name} :delete #{p}" do + action :remove + end + end + end + end + end +end diff --git a/cookbooks/mysql/libraries/provider_mysql_config.rb b/cookbooks/mysql/libraries/provider_mysql_config.rb new file mode 100644 index 0000000..dc277ab --- /dev/null +++ b/cookbooks/mysql/libraries/provider_mysql_config.rb @@ -0,0 +1,58 @@ +require 'chef/provider/lwrp_base' +require_relative 'helpers' + +class Chef + class Provider + class MysqlConfig < Chef::Provider::LWRPBase + include MysqlCookbook::Helpers + + use_inline_resources if defined?(use_inline_resources) + + def whyrun_supported? + true + end + + action :create do + group "#{new_resource.name} :create #{new_resource.group}" do + group_name new_resource.group + system true if new_resource.name == 'mysql' + action :create + end + + user "#{new_resource.name} :create #{new_resource.owner}" do + username new_resource.owner + gid new_resource.owner + system true if new_resource.name == 'mysql' + action :create + end + + directory "#{new_resource.name} :create #{include_dir}" do + path include_dir + owner new_resource.owner + group new_resource.group + mode '0750' + recursive true + action :create + end + + template "#{new_resource.name} :create #{include_dir}/#{new_resource.config_name}.cnf" do + path "#{include_dir}/#{new_resource.config_name}.cnf" + owner new_resource.owner + group new_resource.group + mode '0640' + variables(new_resource.variables) + source new_resource.source + cookbook new_resource.cookbook + action :create + end + end + + action :delete do + file "#{new_resource.name} :delete #{include_dir}/#{new_resource.config_name}.conf" do + path "#{include_dir}/#{new_resource.config_name}.conf" + action :delete + end + end + end + end +end diff --git a/cookbooks/mysql/libraries/provider_mysql_service.rb b/cookbooks/mysql/libraries/provider_mysql_service.rb new file mode 100644 index 0000000..3784fab --- /dev/null +++ b/cookbooks/mysql/libraries/provider_mysql_service.rb @@ -0,0 +1,251 @@ +require 'chef/provider/lwrp_base' +require_relative 'helpers' + +class Chef + class Provider + class MysqlService < Chef::Provider::LWRPBase + # Chef 11 LWRP DSL Methods + use_inline_resources if defined?(use_inline_resources) + + def whyrun_supported? + true + end + + # Mix in helpers from libraries/helpers.rb + include MysqlCookbook::Helpers + + # Service related methods referred to in the :create and :delete + # actions need to be implemented in the init system subclasses. + # + # create_stop_system_service + # delete_stop_service + + # All other methods are found in libraries/helpers.rb + # + # etc_dir, run_dir, log_dir, etc + + action :create do + # Yum, Apt, etc. From helpers.rb + configure_package_repositories + + # Software installation + package "#{new_resource.name} :create #{server_package_name}" do + package_name server_package_name + version parsed_version if node['platform'] == 'smartos' + version new_resource.package_version + action new_resource.package_action + end + + create_stop_system_service + + # Apparmor + configure_apparmor + + # System users + group "#{new_resource.name} :create mysql" do + group_name 'mysql' + action :create + end + + user "#{new_resource.name} :create mysql" do + username 'mysql' + gid 'mysql' + action :create + end + + # Yak shaving secion. Account for random errata. + # + # Turns out that mysqld is hard coded to try and read + # /etc/mysql/my.cnf, and its presence causes problems when + # setting up multiple services. + file "#{new_resource.name} :create #{prefix_dir}/etc/mysql/my.cnf" do + path "#{prefix_dir}/etc/mysql/my.cnf" + action :delete + end + + file "#{new_resource.name} :create #{prefix_dir}/etc/my.cnf" do + path "#{prefix_dir}/etc/my.cnf" + action :delete + end + + # mysql_install_db is broken on 5.6.13 + link "#{new_resource.name} :create #{prefix_dir}/usr/share/my-default.cnf" do + target_file "#{prefix_dir}/usr/share/my-default.cnf" + to "#{etc_dir}/my.cnf" + action :create + end + + # Support directories + directory "#{new_resource.name} :create #{etc_dir}" do + path etc_dir + owner new_resource.run_user + group new_resource.run_group + mode '0750' + recursive true + action :create + end + + directory "#{new_resource.name} :create #{include_dir}" do + path include_dir + owner new_resource.run_user + group new_resource.run_group + mode '0750' + recursive true + action :create + end + + directory "#{new_resource.name} :create #{run_dir}" do + path run_dir + owner new_resource.run_user + group new_resource.run_group + mode '0755' + recursive true + action :create + end + + directory "#{new_resource.name} :create #{log_dir}" do + path log_dir + owner new_resource.run_user + group new_resource.run_group + mode '0750' + recursive true + action :create + end + + directory "#{new_resource.name} :create #{parsed_data_dir}" do + path parsed_data_dir + owner new_resource.run_user + group new_resource.run_group + mode '0750' + recursive true + action :create + end + + # Main configuration file + template "#{new_resource.name} :create #{etc_dir}/my.cnf" do + path "#{etc_dir}/my.cnf" + source 'my.cnf.erb' + cookbook 'mysql' + owner new_resource.run_user + group new_resource.run_group + mode '0600' + variables( + config: new_resource, + error_log: error_log, + include_dir: include_dir, + lc_messages_dir: lc_messages_dir, + pid_file: pid_file, + socket_file: socket_file, + tmp_dir: tmp_dir, + data_dir: parsed_data_dir + ) + action :create + end + + # initialize database and create initial records + bash "#{new_resource.name} :create initial records" do + code init_records_script + returns [0, 1, 2] # facepalm + not_if "/usr/bin/test -f #{parsed_data_dir}/mysql/user.frm" + action :run + end + end + + action :delete do + # Stop the service before removing support directories + delete_stop_service + + directory "#{new_resource.name} :delete #{etc_dir}" do + path etc_dir + recursive true + action :delete + end + + directory "#{new_resource.name} :delete #{run_dir}" do + path run_dir + recursive true + action :delete + end + + directory "#{new_resource.name} :delete #{log_dir}" do + path log_dir + recursive true + action :delete + end + end + + # + # Platform specific bits + # + def configure_apparmor + # Do not add these resource if inside a container + # Only valid on Ubuntu + + unless ::File.exist?('/.dockerenv') || ::File.exist?('/.dockerinit') + if node['platform'] == 'ubuntu' + # Apparmor + package "#{new_resource.name} :create apparmor" do + package_name 'apparmor' + action :install + end + + directory "#{new_resource.name} :create /etc/apparmor.d/local/mysql" do + path '/etc/apparmor.d/local/mysql' + owner 'root' + group 'root' + mode '0755' + recursive true + action :create + end + + template "#{new_resource.name} :create /etc/apparmor.d/local/usr.sbin.mysqld" do + path '/etc/apparmor.d/local/usr.sbin.mysqld' + cookbook 'mysql' + source 'apparmor/usr.sbin.mysqld-local.erb' + owner 'root' + group 'root' + mode '0644' + action :create + notifies :restart, "service[#{new_resource.name} :create apparmor]", :immediately + end + + template "#{new_resource.name} :create /etc/apparmor.d/usr.sbin.mysqld" do + path '/etc/apparmor.d/usr.sbin.mysqld' + cookbook 'mysql' + source 'apparmor/usr.sbin.mysqld.erb' + owner 'root' + group 'root' + mode '0644' + action :create + notifies :restart, "service[#{new_resource.name} :create apparmor]", :immediately + end + + template "#{new_resource.name} :create /etc/apparmor.d/local/mysql/#{new_resource.instance}" do + path "/etc/apparmor.d/local/mysql/#{new_resource.instance}" + cookbook 'mysql' + source 'apparmor/usr.sbin.mysqld-instance.erb' + owner 'root' + group 'root' + mode '0644' + variables( + data_dir: parsed_data_dir, + mysql_name: mysql_name, + log_dir: log_dir, + run_dir: run_dir, + pid_file: pid_file, + socket_file: socket_file + ) + action :create + notifies :restart, "service[#{new_resource.name} :create apparmor]", :immediately + end + + service "#{new_resource.name} :create apparmor" do + service_name 'apparmor' + action :nothing + end + end + end + end + end + end +end diff --git a/cookbooks/mysql/libraries/provider_mysql_service_smf.rb b/cookbooks/mysql/libraries/provider_mysql_service_smf.rb new file mode 100644 index 0000000..3b247ca --- /dev/null +++ b/cookbooks/mysql/libraries/provider_mysql_service_smf.rb @@ -0,0 +1,85 @@ +class Chef + class Provider + class MysqlService + class Smf < Chef::Provider::MysqlService + action :start do + method_script_path = "/lib/svc/method/#{mysql_name}" if node['platform'] == 'omnios' + method_script_path = "/opt/local/lib/svc/method/#{mysql_name}" if node['platform'] == 'smartos' + + template "#{new_resource.name} :start #{method_script_path}" do + path method_script_path + cookbook 'mysql' + source 'smf/svc.method.mysqld.erb' + owner 'root' + group 'root' + mode '0555' + variables( + base_dir: base_dir, + data_dir: parsed_data_dir, + defaults_file: defaults_file, + error_log: error_log, + mysql_name: mysql_name, + mysqld_bin: mysqld_bin, + pid_file: pid_file + ) + action :create + end + + smf "#{new_resource.name} :start #{mysql_name}" do + name mysql_name + user new_resource.run_user + group new_resource.run_group + start_command "#{method_script_path} start" + end + + service "#{new_resource.name} :start #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Solaris + supports restart: true + action [:enable] + end + end + + action :stop do + service "#{new_resource.name} :stop #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Solaris + supports restart: true + action :stop + end + end + + action :restart do + service "#{new_resource.name} :restart #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Solaris + supports restart: true + action :restart + end + end + + action :reload do + service "#{new_resource.name} :reload #{mysql_name}" do + provider Chef::Provider::Service::Solaris + service_name mysql_name + supports reload: true + action :reload + end + end + + def create_stop_system_service + # nothing to do here + end + + def delete_stop_service + service "#{new_resource.name} :delete #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Solaris + supports restart: true + action :stop + end + end + end + end + end +end diff --git a/cookbooks/mysql/libraries/provider_mysql_service_systemd.rb b/cookbooks/mysql/libraries/provider_mysql_service_systemd.rb new file mode 100644 index 0000000..2fccfb4 --- /dev/null +++ b/cookbooks/mysql/libraries/provider_mysql_service_systemd.rb @@ -0,0 +1,121 @@ +class Chef + class Provider + class MysqlService + class Systemd < Chef::Provider::MysqlService + action :start do + # this script is called by the main systemd unit file, and + # spins around until the service is actually up and running. + template "#{new_resource.name} :start /usr/libexec/#{mysql_name}-wait-ready" do + path "/usr/libexec/#{mysql_name}-wait-ready" + source 'systemd/mysqld-wait-ready.erb' + owner 'root' + group 'root' + mode '0755' + variables(socket_file: socket_file) + cookbook 'mysql' + action :create + end + + # this is the main systemd unit file + template "#{new_resource.name} :start /usr/lib/systemd/system/#{mysql_name}.service" do + path "/usr/lib/systemd/system/#{mysql_name}.service" + source 'systemd/mysqld.service.erb' + owner 'root' + group 'root' + mode '0644' + variables( + config: new_resource, + etc_dir: etc_dir, + base_dir: base_dir, + mysqld_bin: mysqld_bin + ) + cookbook 'mysql' + notifies :run, "execute[#{new_resource.name} :start systemctl daemon-reload]", :immediately + action :create + end + + # avoid 'Unit file changed on disk' warning + execute "#{new_resource.name} :start systemctl daemon-reload" do + command '/usr/bin/systemctl daemon-reload' + action :nothing + end + + # tmpfiles.d config so the service survives reboot + template "#{new_resource.name} :start /usr/lib/tmpfiles.d/#{mysql_name}.conf" do + path "/usr/lib/tmpfiles.d/#{mysql_name}.conf" + source 'tmpfiles.d.conf.erb' + owner 'root' + group 'root' + mode '0644' + variables( + run_dir: run_dir, + run_user: new_resource.run_user, + run_group: new_resource.run_group + ) + cookbook 'mysql' + action :create + end + + # service management resource + service "#{new_resource.name} :start #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Systemd + supports restart: true, status: true + action [:enable, :start] + end + end + + action :stop do + # service management resource + service "#{new_resource.name} :stop #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Systemd + supports status: true + action [:disable, :stop] + only_if { ::File.exist?("/usr/lib/systemd/system/#{mysql_name}.service") } + end + end + + action :restart do + # service management resource + service "#{new_resource.name} :restart #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Systemd + supports restart: true + action :restart + end + end + + action :reload do + # service management resource + service "#{new_resource.name} :reload #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Systemd + action :reload + end + end + + def create_stop_system_service + # service management resource + service "#{new_resource.name} :create mysql" do + service_name 'mysqld' + provider Chef::Provider::Service::Systemd + supports status: true + action [:stop, :disable] + end + end + + def delete_stop_service + # service management resource + service "#{new_resource.name} :delete #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Systemd + supports status: true + action [:disable, :stop] + only_if { ::File.exist?("/usr/lib/systemd/system/#{mysql_name}.service") } + end + end + end + end + end +end diff --git a/cookbooks/mysql/libraries/provider_mysql_service_sysvinit.rb b/cookbooks/mysql/libraries/provider_mysql_service_sysvinit.rb new file mode 100644 index 0000000..5b197ac --- /dev/null +++ b/cookbooks/mysql/libraries/provider_mysql_service_sysvinit.rb @@ -0,0 +1,87 @@ +class Chef + class Provider + class MysqlService + class Sysvinit < Chef::Provider::MysqlService + action :start do + template "#{new_resource.name} :start /etc/init.d/#{mysql_name}" do + path "/etc/init.d/#{mysql_name}" + source 'sysvinit/mysqld.erb' + owner 'root' + group 'root' + mode '0755' + variables( + config: new_resource, + defaults_file: defaults_file, + error_log: error_log, + mysql_name: mysql_name, + mysqladmin_bin: mysqladmin_bin, + mysqld_safe_bin: mysqld_safe_bin, + pid_file: pid_file, + scl_name: scl_name + ) + cookbook 'mysql' + action :create + end + + service "#{new_resource.name} :start #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Init::Redhat if node['platform_family'] == 'redhat' + provider Chef::Provider::Service::Init::Insserv if node['platform_family'] == 'debian' + supports restart: true, status: true + action [:enable, :start] + end + end + + action :stop do + service "#{new_resource.name} :stop #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Init::Redhat if node['platform_family'] == 'redhat' + provider Chef::Provider::Service::Init::Insserv if node['platform_family'] == 'debian' + supports restart: true, status: true + action [:stop] + end + end + + action :restart do + service "#{new_resource.name} :restart #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Init::Redhat if node['platform_family'] == 'redhat' + provider Chef::Provider::Service::Init::Insserv if node['platform_family'] == 'debian' + supports restart: true + action :restart + end + end + + action :reload do + service "#{new_resource.name} :reload #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Init::Redhat if node['platform_family'] == 'redhat' + provider Chef::Provider::Service::Init::Insserv if node['platform_family'] == 'debian' + action :reload + end + end + + def create_stop_system_service + service "#{new_resource.name} :create #{system_service_name}" do + service_name system_service_name + provider Chef::Provider::Service::Init::Redhat if node['platform_family'] == 'redhat' + provider Chef::Provider::Service::Init::Insserv if node['platform_family'] == 'debian' + supports status: true + action [:stop, :disable] + end + end + + def delete_stop_service + service "#{new_resource.name} :delete #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Init::Redhat if node['platform_family'] == 'redhat' + provider Chef::Provider::Service::Init::Insserv if node['platform_family'] == 'debian' + supports status: true + action [:disable, :stop] + only_if { ::File.exist?("#{etc_dir}/init.d/#{mysql_name}") } + end + end + end + end + end +end diff --git a/cookbooks/mysql/libraries/provider_mysql_service_upstart.rb b/cookbooks/mysql/libraries/provider_mysql_service_upstart.rb new file mode 100644 index 0000000..2a45166 --- /dev/null +++ b/cookbooks/mysql/libraries/provider_mysql_service_upstart.rb @@ -0,0 +1,107 @@ +class Chef + class Provider + class MysqlService + class Upstart < Chef::Provider::MysqlService + action :start do + template "#{new_resource.name} :start /usr/sbin/#{mysql_name}-wait-ready" do + path "/usr/sbin/#{mysql_name}-wait-ready" + source 'upstart/mysqld-wait-ready.erb' + owner 'root' + group 'root' + mode '0755' + variables(socket_file: socket_file) + cookbook 'mysql' + action :create + end + + template "#{new_resource.name} :start /etc/init/#{mysql_name}.conf" do + path "/etc/init/#{mysql_name}.conf" + source 'upstart/mysqld.erb' + owner 'root' + group 'root' + mode '0644' + variables( + defaults_file: defaults_file, + mysql_name: mysql_name, + run_group: new_resource.run_group, + run_user: new_resource.run_user, + socket_dir: socket_dir + ) + cookbook 'mysql' + action :create + end + + service "#{new_resource.name} :start #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Upstart + supports status: true + action [:start] + end + end + + action :stop do + service "#{new_resource.name} :stop #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Upstart + supports restart: true, status: true + action [:stop] + end + end + + action :restart do + # With Upstart, restarting the service doesn't behave "as expected". + # We want the post-start stanzas, which wait until the + # service is available before returning + # + # http://upstart.ubuntu.com/cookbook/#restart + service "#{new_resource.name} :restart stop #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Upstart + action :stop + end + + service "#{new_resource.name} :restart start #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Upstart + action :start + end + end + + action :reload do + # With Upstart, reload just sends a HUP signal to the process. + # As far as I can tell, this doesn't work the way it's + # supposed to, so we need to actually restart the service. + service "#{new_resource.name} :reload stop #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Upstart + action :stop + end + + service "#{new_resource.name} :reload start #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Upstart + action :start + end + end + + def create_stop_system_service + service "#{new_resource.name} :create #{system_service_name}" do + service_name system_service_name + provider Chef::Provider::Service::Upstart + supports status: true + action [:stop, :disable] + end + end + + def delete_stop_service + service "#{new_resource.name} :delete #{mysql_name}" do + service_name mysql_name + provider Chef::Provider::Service::Upstart + action [:disable, :stop] + only_if { ::File.exist?("#{etc_dir}/init/#{mysql_name}") } + end + end + end + end + end +end diff --git a/cookbooks/mysql/libraries/resource_mysql_client.rb b/cookbooks/mysql/libraries/resource_mysql_client.rb new file mode 100644 index 0000000..5940505 --- /dev/null +++ b/cookbooks/mysql/libraries/resource_mysql_client.rb @@ -0,0 +1,16 @@ +require 'chef/resource/lwrp_base' + +class Chef + class Resource + class MysqlClient < Chef::Resource::LWRPBase + self.resource_name = :mysql_client + actions :create, :delete + default_action :create + + attribute :client_name, kind_of: String, name_attribute: true, required: true + attribute :package_name, kind_of: Array, default: nil + attribute :package_version, kind_of: String, default: nil + attribute :version, kind_of: String, default: nil # mysql_version + end + end +end diff --git a/cookbooks/mysql/libraries/resource_mysql_config.rb b/cookbooks/mysql/libraries/resource_mysql_config.rb new file mode 100644 index 0000000..3a5305f --- /dev/null +++ b/cookbooks/mysql/libraries/resource_mysql_config.rb @@ -0,0 +1,20 @@ +require 'chef/resource/lwrp_base' + +class Chef + class Resource + class MysqlConfig < Chef::Resource::LWRPBase + self.resource_name = :mysql_config + actions :create, :delete + default_action :create + + attribute :config_name, kind_of: String, name_attribute: true, required: true + attribute :cookbook, kind_of: String, default: nil + attribute :group, kind_of: String, default: 'mysql' + attribute :instance, kind_of: String, default: 'default' + attribute :owner, kind_of: String, default: 'mysql' + attribute :source, kind_of: String, default: nil + attribute :variables, kind_of: [Hash], default: nil + attribute :version, kind_of: String, default: nil + end + end +end diff --git a/cookbooks/mysql/libraries/resource_mysql_service.rb b/cookbooks/mysql/libraries/resource_mysql_service.rb new file mode 100644 index 0000000..5c85f2d --- /dev/null +++ b/cookbooks/mysql/libraries/resource_mysql_service.rb @@ -0,0 +1,25 @@ +require 'chef/resource/lwrp_base' + +class Chef + class Resource + class MysqlService < Chef::Resource::LWRPBase + self.resource_name = :mysql_service + actions :create, :delete, :start, :stop, :restart, :reload + default_action :create + + attribute :charset, kind_of: String, default: 'utf8' + attribute :data_dir, kind_of: String, default: nil + attribute :initial_root_password, kind_of: String, default: 'ilikerandompasswords' + attribute :instance, kind_of: String, name_attribute: true + attribute :package_action, kind_of: Symbol, default: :install + attribute :package_name, kind_of: String, default: nil + attribute :package_version, kind_of: String, default: nil + attribute :bind_address, kind_of: String, default: nil + attribute :port, kind_of: String, default: '3306' + attribute :run_group, kind_of: String, default: 'mysql' + attribute :run_user, kind_of: String, default: 'mysql' + attribute :socket, kind_of: String, default: nil + attribute :version, kind_of: String, default: nil + end + end +end diff --git a/cookbooks/mysql/libraries/z_provider_mapping.rb b/cookbooks/mysql/libraries/z_provider_mapping.rb new file mode 100644 index 0000000..7c70bde --- /dev/null +++ b/cookbooks/mysql/libraries/z_provider_mapping.rb @@ -0,0 +1,47 @@ +# provider mappings for Chef 11 + +######### +# service +######### +Chef::Platform.set platform: :amazon, resource: :mysql_service, provider: Chef::Provider::MysqlService::Sysvinit +Chef::Platform.set platform: :centos, version: '< 7.0', resource: :mysql_service, provider: Chef::Provider::MysqlService::Sysvinit +Chef::Platform.set platform: :centos, version: '>= 7.0', resource: :mysql_service, provider: Chef::Provider::MysqlService::Systemd +Chef::Platform.set platform: :debian, resource: :mysql_service, provider: Chef::Provider::MysqlService::Sysvinit +Chef::Platform.set platform: :fedora, version: '< 19', resource: :mysql_service, provider: Chef::Provider::MysqlService::Sysvinit +Chef::Platform.set platform: :fedora, version: '>= 19', resource: :mysql_service, provider: Chef::Provider::MysqlService::Systemd +Chef::Platform.set platform: :omnios, resource: :mysql_service, provider: Chef::Provider::MysqlService::Smf +Chef::Platform.set platform: :redhat, version: '< 7.0', resource: :mysql_service, provider: Chef::Provider::MysqlService::Sysvinit +Chef::Platform.set platform: :redhat, version: '>= 7.0', resource: :mysql_service, provider: Chef::Provider::MysqlService::Systemd +Chef::Platform.set platform: :scientific, version: '< 7.0', resource: :mysql_service, provider: Chef::Provider::MysqlService::Sysvinit +Chef::Platform.set platform: :scientific, version: '>= 7.0', resource: :mysql_service, provider: Chef::Provider::MysqlService::Systemd +Chef::Platform.set platform: :smartos, resource: :mysql_service, provider: Chef::Provider::MysqlService::Smf +Chef::Platform.set platform: :suse, resource: :mysql_service, provider: Chef::Provider::MysqlService::Sysvinit +Chef::Platform.set platform: :ubuntu, resource: :mysql_service, provider: Chef::Provider::MysqlService::Upstart + +######### +# config +######### +Chef::Platform.set platform: :amazon, resource: :mysql_config, provider: Chef::Provider::MysqlConfig +Chef::Platform.set platform: :centos, resource: :mysql_config, provider: Chef::Provider::MysqlConfig +Chef::Platform.set platform: :debian, resource: :mysql_config, provider: Chef::Provider::MysqlConfig +Chef::Platform.set platform: :fedora, resource: :mysql_config, provider: Chef::Provider::MysqlConfig +Chef::Platform.set platform: :omnios, resource: :mysql_config, provider: Chef::Provider::MysqlConfig +Chef::Platform.set platform: :redhat, resource: :mysql_config, provider: Chef::Provider::MysqlConfig +Chef::Platform.set platform: :scientific, resource: :mysql_config, provider: Chef::Provider::MysqlConfig +Chef::Platform.set platform: :smartos, resource: :mysql_config, provider: Chef::Provider::MysqlConfig +Chef::Platform.set platform: :suse, resource: :mysql_config, provider: Chef::Provider::MysqlConfig +Chef::Platform.set platform: :ubuntu, resource: :mysql_config, provider: Chef::Provider::MysqlConfig + +######### +# client +######### +Chef::Platform.set platform: :amazon, resource: :mysql_client, provider: Chef::Provider::MysqlClient +Chef::Platform.set platform: :centos, resource: :mysql_client, provider: Chef::Provider::MysqlClient +Chef::Platform.set platform: :debian, resource: :mysql_client, provider: Chef::Provider::MysqlClient +Chef::Platform.set platform: :fedora, resource: :mysql_client, provider: Chef::Provider::MysqlClient +Chef::Platform.set platform: :omnios, resource: :mysql_client, provider: Chef::Provider::MysqlClient +Chef::Platform.set platform: :redhat, resource: :mysql_client, provider: Chef::Provider::MysqlClient +Chef::Platform.set platform: :scientific, resource: :mysql_client, provider: Chef::Provider::MysqlClient +Chef::Platform.set platform: :smartos, resource: :mysql_client, provider: Chef::Provider::MysqlClient +Chef::Platform.set platform: :suse, resource: :mysql_client, provider: Chef::Provider::MysqlClient +Chef::Platform.set platform: :ubuntu, resource: :mysql_client, provider: Chef::Provider::MysqlClient diff --git a/cookbooks/mysql/metadata.json b/cookbooks/mysql/metadata.json new file mode 100644 index 0000000..1c9d9f1 --- /dev/null +++ b/cookbooks/mysql/metadata.json @@ -0,0 +1 @@ +{"name":"mysql","version":"6.0.22","description":"Provides mysql_service, mysql_config, and mysql_client resources","long_description":"","maintainer":"Chef Software, Inc.","maintainer_email":"cookbooks@chef.io","license":"Apache 2.0","platforms":{"amazon":">= 0.0.0","redhat":">= 0.0.0","centos":">= 0.0.0","scientific":">= 0.0.0","fedora":">= 0.0.0","debian":">= 0.0.0","ubuntu":">= 0.0.0","smartos":">= 0.0.0","omnios":">= 0.0.0","suse":">= 0.0.0"},"dependencies":{"yum-mysql-community":">= 0.0.0","smf":">= 0.0.0"},"recommendations":{},"suggestions":{},"conflicting":{},"providing":{},"replacing":{},"attributes":{},"groupings":{},"recipes":{}} \ No newline at end of file diff --git a/cookbooks/mysql/templates/default/apparmor/usr.sbin.mysqld-instance.erb b/cookbooks/mysql/templates/default/apparmor/usr.sbin.mysqld-instance.erb new file mode 100644 index 0000000..430a311 --- /dev/null +++ b/cookbooks/mysql/templates/default/apparmor/usr.sbin.mysqld-instance.erb @@ -0,0 +1,13 @@ +/etc/<%= @mysql_name %>/*.pem r, +/etc/<%= @mysql_name %>/conf.d/ r, +/etc/<%= @mysql_name %>/conf.d/* r, +/etc/<%= @mysql_name %>/my.cnf r, +<%= @log_dir %>/ r, +<%= @log_dir %>/* rw, +<%= @data_dir %>/ r, +<%= @data_dir %>/** rwk, +<%= @run_dir %>/** rw, +<%= @pid_file %> rw, +<%= @socket_file %> rw, +/tmp/<%= @mysql_name %>/ r, +/tmp/<%= @mysql_name %>/my.sql r, diff --git a/cookbooks/mysql/templates/default/apparmor/usr.sbin.mysqld-local.erb b/cookbooks/mysql/templates/default/apparmor/usr.sbin.mysqld-local.erb new file mode 100644 index 0000000..b261920 --- /dev/null +++ b/cookbooks/mysql/templates/default/apparmor/usr.sbin.mysqld-local.erb @@ -0,0 +1 @@ +#include diff --git a/cookbooks/mysql/templates/default/apparmor/usr.sbin.mysqld.erb b/cookbooks/mysql/templates/default/apparmor/usr.sbin.mysqld.erb new file mode 100644 index 0000000..3e1f1b0 --- /dev/null +++ b/cookbooks/mysql/templates/default/apparmor/usr.sbin.mysqld.erb @@ -0,0 +1,45 @@ +# vim:syntax=apparmor +# Last Modified: Tue Jun 19 17:37:30 2007 +#include + +/usr/sbin/mysqld { + #include + #include + #include + #include + #include + + capability dac_override, + capability sys_resource, + capability setgid, + capability setuid, + + network tcp, + + /etc/hosts.allow r, + /etc/hosts.deny r, + + /etc/mysql/*.pem r, + /etc/mysql/conf.d/ r, + /etc/mysql/conf.d/* r, + /etc/mysql/*.cnf r, + /usr/lib/mysql/plugin/ r, + /usr/lib/mysql/plugin/*.so* mr, + /usr/sbin/mysqld mr, + /usr/share/mysql/** r, + /var/log/mysql.log rw, + /var/log/mysql.err rw, + /var/lib/mysql/ r, + /var/lib/mysql/** rwk, + /var/log/mysql/ r, + /var/log/mysql/* rw, + /var/run/mysqld/mysqld.pid rw, + /var/run/mysqld/mysqld.sock w, + /run/mysqld/mysqld.pid rw, + /run/mysqld/mysqld.sock w, + + /sys/devices/system/cpu/ r, + + # Site-specific additions and overrides. See local/README for details. + #include +} diff --git a/cookbooks/mysql/templates/default/my.cnf.erb b/cookbooks/mysql/templates/default/my.cnf.erb new file mode 100644 index 0000000..15dc9c2 --- /dev/null +++ b/cookbooks/mysql/templates/default/my.cnf.erb @@ -0,0 +1,54 @@ +# Chef generated my.cnf for instance mysql-<%= @config.name %> + +[client] +<% if @config.charset %> +default-character-set = <%= @config.charset %> +<% end %> +<% if @config.port %> +port = <%= @config.port %> +<% end %> +<% if @socket_file %> +socket = <%= @socket_file %> +<% end %> + +[mysql] +<% if @config.charset %> +default-character-set = <%= @config.charset %> +<% end %> + +[mysqld] +<% if @config.run_user %> +user = <%= @config.run_user %> +<% end %> +<% if @pid_file %> +pid-file = <%= @pid_file %> +<% end %> +<% if @socket_file %> +socket = <%= @socket_file %> +<% end %> +<% if @config.bind_address %> +bind-address = <%= @config.bind_address %> +<% end %> +<% if @config.port %> +port = <%= @config.port %> +<% end %> +<% if @data_dir %> +datadir = <%= @data_dir %> +<% end %> +<% if @tmp_dir %> +tmpdir = <%= @tmp_dir %> +<% end %> +<% if @lc_messages_dir %> +lc-messages-dir = <%= @lc_messages_dir %> +<% end %> +<% if @error_log %> +log-error = <%= @error_log %> +<% end %> +<% if @include_dir %> +!includedir <%= @include_dir %> +<% end %> + +[mysqld_safe] +<% if @socket_file %> +socket = <%= @socket_file %> +<% end %> diff --git a/cookbooks/mysql/templates/default/smf/svc.method.mysqld.erb b/cookbooks/mysql/templates/default/smf/svc.method.mysqld.erb new file mode 100644 index 0000000..5cc178a --- /dev/null +++ b/cookbooks/mysql/templates/default/smf/svc.method.mysqld.erb @@ -0,0 +1,28 @@ +#!/sbin/sh +# +# Generated by Chef +# + +. /lib/svc/share/smf_include.sh + +ulimit -n 10240 + +case "$1" in +start) + <%= @mysqld_bin %> \ + --defaults-file=<%= @defaults_file %> \ + --basedir=<%= @base_dir %> \ + --datadir=<%= @data_dir %> \ + --pid-file=<%= @pid_file %> \ + --log-error=<%= @error_log %> & + ;; +stop) + [ -f <%= @pid_file %> ] && kill `/usr/bin/head -1 <%= @pid_file %>` + ;; +*) + echo "Usage: $0 {start|stop}" >&2 + exit 1 + ;; +esac + +exit $SMF_EXIT_OK diff --git a/cookbooks/mysql/templates/default/systemd/mysqld-wait-ready.erb b/cookbooks/mysql/templates/default/systemd/mysqld-wait-ready.erb new file mode 100644 index 0000000..a566bf3 --- /dev/null +++ b/cookbooks/mysql/templates/default/systemd/mysqld-wait-ready.erb @@ -0,0 +1,30 @@ +#!/bin/sh + +daemon_pid="$1" + +# Wait for the server to come up or for the mysqld process to disappear +ret=0 +while /bin/true; do + RESPONSE=`/usr/bin/mysqladmin --no-defaults --socket="<%= @socket_file %>" --user=UNKNOWN_MYSQL_USER ping 2>&1` + mret=$? + if [ $mret -eq 0 ]; then + break + fi + # exit codes 1, 11 (EXIT_CANNOT_CONNECT_TO_SERVICE) are expected, + # anything else suggests a configuration error + if [ $mret -ne 1 -a $mret -ne 11 ]; then + ret=1 + break + fi + # "Access denied" also means the server is alive + echo "$RESPONSE" | grep -q "Access denied for user" && break + + # Check process still exists + if ! /bin/kill -0 $daemon_pid 2>/dev/null; then + ret=1 + break + fi + sleep 1 +done + +exit $ret diff --git a/cookbooks/mysql/templates/default/systemd/mysqld.service.erb b/cookbooks/mysql/templates/default/systemd/mysqld.service.erb new file mode 100644 index 0000000..f1fb6c1 --- /dev/null +++ b/cookbooks/mysql/templates/default/systemd/mysqld.service.erb @@ -0,0 +1,16 @@ +[Unit] +Description=mysql_service[mysql-<%= @config.instance %>] +After=syslog.target +After=network.target + +[Service] +Type=simple +User=<%= @config.run_user %> +Group=<%= @config.run_group %> +ExecStart=<%= @mysqld_bin %> --defaults-file=<%= @etc_dir %>/my.cnf --basedir=<%= @base_dir %> +ExecStartPost=/usr/libexec/mysql-<%= @config.instance %>-wait-ready $MAINPID +TimeoutSec=300 +PrivateTmp=true + +[Install] +WantedBy=multi-user.target diff --git a/cookbooks/mysql/templates/default/sysvinit/mysqld.erb b/cookbooks/mysql/templates/default/sysvinit/mysqld.erb new file mode 100644 index 0000000..c167fa7 --- /dev/null +++ b/cookbooks/mysql/templates/default/sysvinit/mysqld.erb @@ -0,0 +1,266 @@ +#!/bin/bash +# +### BEGIN INIT INFO +# Provides: <%= @mysql_name %> +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Should-Start: $network $time +# Should-Stop: $network $time +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start and stop the mysql database server daemon +# Description: Controls the main MySQL database server daemon "mysqld" +# and its wrapper script "mysqld_safe". +### END INIT INFO + +# set -e +# set -u + +### Exit code reference +# http://fedoraproject.org/wiki/Packaging:SysVInitScript +# http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html + +# Source functions +<% if node['platform_family'] == 'rhel' %> +# Source RHEL function library. +. /etc/rc.d/init.d/functions +<% end %> + +#### +# Variables +#### + +STARTTIMEOUT=30 +STOPTIMEOUT=15 + +#### +# Helper functions +### + +# Boolean function to see if MYSQL_PID exists and is a number +pid_exists() { + PID_EXISTS=1 + if [ -f <%= @pid_file %> ]; then + MYSQLD_PID=`cat <%= @pid_file %> 2>/dev/null` + if [ -n "$MYSQLD_PID" ] && [ -d "/proc/$MYSQLD_PID" ] ; then + PID_EXISTS=0 + fi + fi + return $PID_EXISTS +} + +# Use mysqladmin to ping the service as an invalid user over a socket +running() { + RUNNING=1 + RESPONSE=`<%= @mysqladmin_bin %> --defaults-file=<%= @defaults_file %> --user=UNKNOWN_MYSQL_USER ping 2>&1` + local mret=$? + if pid_exists \ + && [ $mret -eq 0 ] \ + || [ `echo $RESPONSE | grep -q "Access denied for user"` ]; then + RUNNING=0 + fi + return $RUNNING +} + +writable_error_log() { + WRITABLE_ERROR_LOG=1 + touch "<%= @error_log %>" 2>/dev/null + touchret=$? + if [ $touchret -eq 0 ]; then + chown <%= @config.run_user %>:<%= @config.run_group %> <%= @error_log %> + return 0 + else + return $WRITABLE_ERROR_LOG + fi +} + +print_start_success() { + <% if node['platform_family'] == 'rhel' %> + action $"Starting <%= @mysql_name %>: " /bin/true + <% else %> + echo "Staring MySQL instance <%= @mysql_name %>" + <% end %> + return 0; +} + +print_start_failure() { + <% if node['platform_family'] == 'rhel' %> + action $"Starting <%= @mysql_name %>: " /bin/false + <% else %> + echo "Could not start MySQL instance <%= @mysql_name %>" + <% end %> + return 0; +} + +print_reload_success() { + <% if node['platform_family'] == 'rhel' %> + action $"Reloading <%= @mysql_name %>" /bin/true + <% else %> + echo "Reload success for <%= @mysql_name %>" + <% end %> + return 0; +} + +print_reload_failure() { + <% if node['platform_family'] == 'rhel' %> + action $"Reloading <%= @mysql_name %>" /bin/false + <% else %> + echo "Reload failed for <%= @mysql_name %>" + <% end %> + return 0; +} + +print_stop_success() { + <% if node['platform_family'] == 'rhel' %> + action $"Stopping <%= @mysql_name %>: " /bin/true + <% else %> + echo "Stopping MySQL instance <%= @mysql_name %>" + <% end %> + return 0; +} + +print_stop_failure() { + <% if node['platform_family'] == 'rhel' %> + action $"Stopping <%= @mysql_name %>: " /bin/false + <% else %> + echo "Could not stop MySQL instance <%= @mysql_name %>" + <% end %> + return 0; +} + +start_command() { + # Attempt to start <%= @mysql_name %> + echo "Starting MySQL instance <%= @mysql_name %>" + local scl_name="<%= @scl_name %>" + + if [ -z $scl_name ]; then + <%= @mysqld_safe_bin %> \ + --defaults-file=<%= @defaults_file %> \ + >/dev/null 2>&1 & + local pid=$! + else + scl enable $scl_name "<%= @mysqld_safe_bin %> \ + --defaults-file=<%= @defaults_file %> \ + >/dev/null 2>&1 &" + local pid=$! + fi + + return $pid +} + +#### +# Init script actions +### + +# Start <%= @mysql_name %> +start() { + # exit 0 if already running. + if running; then + print_start_success + return 0; + fi + + # exit 4 if we can't write to error_log + if ! writable_error_log; then + print_start_failure + return 4 + fi + + # run program + start_command; + start_pid=$? + + # Timeout loop + local TIMEOUT=$STARTTIMEOUT + while [ $TIMEOUT -gt 0 ]; do + if running; then + break + fi + sleep 1 + let TIMEOUT=${TIMEOUT}-1 + done + + # Handle timeout + if [ $TIMEOUT -eq 0 ]; then + print_start_failure + # clean up + kill $start_pid 2>/dev/null + return 1 + fi + + # successbaby.gif + print_start_success + return 0 +} + +# Reload <%= @mysql_name %> +reload() { + <%= @mysqladmin_bin %> reload + local ret=$? + if [ $ret -eq 0 ]; then + print_reload_success; + else + print_reload_failure; + fi + return $ret +} + +# Status of <%= @mysql_name %> +status() { + if running; then + echo "<%= @mysql_name %> is running" + return 0 + else + echo "<%= @mysql_name %> is not running" + return 1 + fi +} + +# Stop <%= @mysql_name %> +stop() { + if running; then + echo "Stopping MySQL instance <%= @mysql_name %>" + if [ -f <%= @pid_file %> ]; then + /bin/kill `cat <%= @pid_file %> 2>/dev/null` + kstat=$? + fi + + # Timeout loop + local TIMEOUT=$STARTTIMEOUT + while [ $TIMEOUT -gt 0 ]; do + if [ -e <%= @pid_file %> ]; then + sleep 1 + fi + let TIMEOUT=${TIMEOUT}-1 + done + + return $kstat + else + echo "MySQL instance <%= @mysql_name %> Stopped." + return 0 + fi +} + +# main() +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status + ;; + restart) + stop ; start + ;; + reload) + reload + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|reload}" + exit 2 +esac + +exit $? diff --git a/cookbooks/mysql/templates/default/tmpfiles.d.conf.erb b/cookbooks/mysql/templates/default/tmpfiles.d.conf.erb new file mode 100644 index 0000000..59d0426 --- /dev/null +++ b/cookbooks/mysql/templates/default/tmpfiles.d.conf.erb @@ -0,0 +1 @@ +d <%= @run_dir %> 0755 <%= @run_user %> <%= @run_group %> - diff --git a/cookbooks/mysql/templates/default/upstart/mysqld-wait-ready.erb b/cookbooks/mysql/templates/default/upstart/mysqld-wait-ready.erb new file mode 100644 index 0000000..f99308f --- /dev/null +++ b/cookbooks/mysql/templates/default/upstart/mysqld-wait-ready.erb @@ -0,0 +1,22 @@ +#!/bin/sh + +# Wait for the server to come up +ret=0 +while /bin/true; do + RESPONSE=`/usr/bin/mysqladmin --no-defaults --socket="<%= @socket_file %>" --user=UNKNOWN_MYSQL_USER ping 2>&1` + mret=$? + if [ $mret -eq 0 ]; then + break + fi + # exit codes 1, 11 (EXIT_CANNOT_CONNECT_TO_SERVICE) are expected, + # anything else suggests a configuration error + if [ $mret -ne 1 -a $mret -ne 11 ]; then + ret=1 + break + fi + # "Access denied" also means the server is alive + echo "$RESPONSE" | grep -q "Access denied for user" && break + sleep 1 +done + +exit $ret diff --git a/cookbooks/mysql/templates/default/upstart/mysqld.erb b/cookbooks/mysql/templates/default/upstart/mysqld.erb new file mode 100644 index 0000000..4ac214c --- /dev/null +++ b/cookbooks/mysql/templates/default/upstart/mysqld.erb @@ -0,0 +1,26 @@ +# <%= @mysql_name %> Service + +description "MySQL service <%= @mysql_name %>" +author "chef-client" + +start on runlevel [2345] +stop on starting rc RUNLEVEL=[016] + +respawn +respawn limit 2 5 + +env HOME=/etc/<%= @mysql_name %> +umask 007 + +kill timeout 300 + +pre-start script +[ -d /run/<%= @mysql_name %> ] || install -m 755 -o <%= @run_user %> -g <%= @run_group %> -d /run/<%= @mysql_name %> +[ -d <%= @socket_dir %> ] || install -m 755 -o <%= @run_user %> -g <%= @run_group %> -d <%= @socket_dir %> +end script + +exec /usr/sbin/mysqld --defaults-file=<%= @defaults_file %> + +post-start script +/usr/sbin/<%= @mysql_name %>-wait-ready +end script diff --git a/cookbooks/mysql2_chef_gem/CHANGELOG.md b/cookbooks/mysql2_chef_gem/CHANGELOG.md new file mode 100644 index 0000000..ba5b39d --- /dev/null +++ b/cookbooks/mysql2_chef_gem/CHANGELOG.md @@ -0,0 +1,26 @@ +mysql2_chef_gem CHANGELOG +======================== + +1.0.1 (2014-12-25) +------------------ +- Moving from recipe_eval in to include_recipe LWRP + +1.0.0 (2014-12-23) +------------------ +- Replacing recipes with resources +- Mysql and MariaDB providers for linking mysql2 gem +- Expanded platform test coverage + +0.1.1 (2014-09-15) +------------------ +- Correct a typo in documentation +- Correct a test failing with Travis CI + +0.1.0 (2014-09-15) +------------------ +- Correct documentation +- Correct rubocop offenses + +0.0.3 (2014-09-12) +------------------ +- Initial release (copy of mysql-chef_gem, but for mysql2) diff --git a/cookbooks/mysql2_chef_gem/README.md b/cookbooks/mysql2_chef_gem/README.md new file mode 100644 index 0000000..ef0cadf --- /dev/null +++ b/cookbooks/mysql2_chef_gem/README.md @@ -0,0 +1,108 @@ +Mysql2 Chef Gem Installer Cookbook +================================== + +[![Build Status](https://travis-ci.org/sinfomicien/mysql2_chef_gem.png)](https://travis-ci.org/sinfomicien/mysql2_chef_gem) + +mysql2_chef_gem is a library cookbook that provides an LWRP for use +in recipes. It provides a wrapper around `chef_gem` called +`mysql2_chef_gem` that eases the installation process, collecting the +prerequisites and side-stepping the compilation phase arms race. + +Scope +----- +This cookbook is concerned with the installation of the `mysql2` +Rubygem into Chef's gem path. Installation into other Ruby +environments, or installation of related gems such as `mysql` are +outside the scope of this cookbook. + +Requirements +------------ +* Chef 11 or higher +* Ruby 1.9 (preferably from the Chef full-stack installer) + +Platform Support +---------------- +The following platforms have been tested with Test Kitchen and are +known to work. + +``` +|---------------------------------------+-----+-----+-----+-----+-----| +| | 5.0 | 5.1 | 5.5 | 5.6 | 5.7 | +|---------------------------------------+-----+-----+-----+-----+-----| +| Mysql2ChefGem::Mysql / centos-5 | X | | | X | X | +|---------------------------------------+-----+-----+-----+-----+-----| +| Mysql2ChefGem::Mysql / centos-6 | | X | X | X | X | +|---------------------------------------+-----+-----+-----+-----+-----| +| Mysql2ChefGem::Mysql / centos-7 | | | X | X | X | +|---------------------------------------+-----+-----+-----+-----+-----| +| Mysql2ChefGem::Mysql / fedora-20 | | | X | X | X | +|---------------------------------------+-----+-----+-----+-----+-----| +| Mysql2ChefGem::Mysql / debian-7 | | | X | | | +|---------------------------------------+-----+-----+-----+-----+-----| +| Mysql2ChefGem::Mysql / ubuntu-10.04 | | X | | | | +|---------------------------------------+-----+-----+-----+-----+-----| +| Mysql2ChefGem::Mysql / ubuntu-12.04 | | | X | | | +|---------------------------------------+-----+-----+-----+-----+-----| +| Mysql2ChefGem::Mysql / ubuntu-14.04 | | | X | X | | +|---------------------------------------+-----+-----+-----+-----+-----| +| Mysql2ChefGem::Mariadb / fedora-20 | | | X | | | +|---------------------------------------+-----+-----+-----+-----+-----| +| Mysql2ChefGem::Mariadb / ubuntu-14.04 | | | X | | | +|---------------------------------------+-----+-----+-----+-----+-----| +``` + +Usage +----- +Place a dependency on the mysql cookbook in your cookbook's metadata.rb +```ruby +depends 'mysql2_chef_gem', '~> 1.0' +``` + +Then, in a recipe: + +```ruby +mysql2_chef_gem 'default' do + action :install +end +``` + +Resources Overview +------------------ +### mysql2_chef_gem + +The `mysql2_chef_gem` resource the build dependencies and installation +of the `mysql2` rubygem into Chef's Ruby environment + +#### Example +```ruby +mysql2_chef_gem 'default' do + gem_version '0.3.17' + action :install +end +``` +#### Parameters +- `gem_version` - The version of the `mysql` Rubygem to install into + the Chef environment. Defaults to '0.3.17' +- `connectors_url` - URL of a tarball containing pre-compiled MySQL + connector libraries +- `connectors_checksum` - sha256sum of the `connectors_url` tarball + +#### Actions +- `:install` - Build and install the gem into the Chef environment +- `:remove` - Delete the gem from the Chef environment + +#### Providers +Chef selects a default provider based on platform and version, +but you can specify one if your platform support it. + +```ruby +mysql2_chef_gem 'default' do + provider Chef::Provider::Mysql2ChefGem::Mariadb + action :install +end +``` + +Authors +------- +- Author:: Sean OMeara () +- Author:: Nicolas Blanc() diff --git a/cookbooks/mysql2_chef_gem/libraries/matchers.rb b/cookbooks/mysql2_chef_gem/libraries/matchers.rb new file mode 100644 index 0000000..fcfd2ff --- /dev/null +++ b/cookbooks/mysql2_chef_gem/libraries/matchers.rb @@ -0,0 +1,5 @@ +if defined?(ChefSpec) + def install_mysql2_chef_gem(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:mysql2_chef_gem, :install, resource_name) + end +end diff --git a/cookbooks/mysql2_chef_gem/libraries/provider_mysql2_chef_gem_mariadb.rb b/cookbooks/mysql2_chef_gem/libraries/provider_mysql2_chef_gem_mariadb.rb new file mode 100644 index 0000000..e25a427 --- /dev/null +++ b/cookbooks/mysql2_chef_gem/libraries/provider_mysql2_chef_gem_mariadb.rb @@ -0,0 +1,37 @@ +class Chef + class Provider + class Mysql2ChefGem + class Mariadb < Chef::Provider::LWRPBase + use_inline_resources if defined?(use_inline_resources) + + def whyrun_supported? + true + end + + action :install do + recipe_eval do + run_context.include_recipe 'build-essential::default' + end + + # As a recipe: must rely on global node attributes + recipe_eval do + run_context.include_recipe 'mariadb::client' + end + + gem_package 'mysql2' do + gem_binary RbConfig::CONFIG['bindir'] + '/gem' + version new_resource.gem_version + action :install + end + end + + action :remove do + gem_package 'mysql2' do + gem_binary RbConfig::CONFIG['bindir'] + '/gem' + action :remove + end + end + end + end + end +end diff --git a/cookbooks/mysql2_chef_gem/libraries/provider_mysql2_chef_gem_mysql.rb b/cookbooks/mysql2_chef_gem/libraries/provider_mysql2_chef_gem_mysql.rb new file mode 100644 index 0000000..cc1bace --- /dev/null +++ b/cookbooks/mysql2_chef_gem/libraries/provider_mysql2_chef_gem_mysql.rb @@ -0,0 +1,37 @@ +class Chef + class Provider + class Mysql2ChefGem + class Mysql < Chef::Provider::LWRPBase + include Chef::DSL::IncludeRecipe + use_inline_resources if defined?(use_inline_resources) + + def whyrun_supported? + true + end + + action :install do + include_recipe 'build-essential::default' + + # As a resource: can pass version from calling recipe + mysql_client 'default' do + version new_resource.client_version + action :create + end + + gem_package 'mysql2' do + gem_binary RbConfig::CONFIG['bindir'] + '/gem' + version new_resource.gem_version + action :install + end + end + + action :remove do + gem_package 'mysql2' do + gem_binary RbConfig::CONFIG['bindir'] + '/gem' + action :remove + end + end + end + end + end +end diff --git a/cookbooks/mysql2_chef_gem/libraries/resource_mysql2_chef_gem.rb b/cookbooks/mysql2_chef_gem/libraries/resource_mysql2_chef_gem.rb new file mode 100644 index 0000000..92ba02f --- /dev/null +++ b/cookbooks/mysql2_chef_gem/libraries/resource_mysql2_chef_gem.rb @@ -0,0 +1,15 @@ +require 'chef/resource/lwrp_base' + +class Chef + class Resource + class Mysql2ChefGem < Chef::Resource::LWRPBase + self.resource_name = :mysql2_chef_gem + actions :install, :remove + default_action :install + + attribute :mysql2_chef_gem_name, kind_of: String, name_attribute: true, required: true + attribute :gem_version, kind_of: String, default: '0.3.17' + attribute :client_version, kind_of: String, default: nil + end + end +end diff --git a/cookbooks/mysql2_chef_gem/libraries/z_provider_mapping.rb b/cookbooks/mysql2_chef_gem/libraries/z_provider_mapping.rb new file mode 100644 index 0000000..bf0d7aa --- /dev/null +++ b/cookbooks/mysql2_chef_gem/libraries/z_provider_mapping.rb @@ -0,0 +1,17 @@ +######### +# mysql2_chef_gem +######### +Chef::Platform.set platform: :amazon, resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql +Chef::Platform.set platform: :centos, version: '< 7.0', resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql +Chef::Platform.set platform: :centos, version: '>= 7.0', resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql +Chef::Platform.set platform: :debian, resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql +Chef::Platform.set platform: :fedora, version: '< 19', resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql +Chef::Platform.set platform: :fedora, version: '>= 19', resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql +Chef::Platform.set platform: :omnios, resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql +Chef::Platform.set platform: :redhat, version: '< 7.0', resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql +Chef::Platform.set platform: :redhat, version: '>= 7.0', resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql +Chef::Platform.set platform: :scientific, version: '< 7.0', resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql +Chef::Platform.set platform: :scientific, version: '>= 7.0', resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql +Chef::Platform.set platform: :smartos, resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql +Chef::Platform.set platform: :suse, resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql +Chef::Platform.set platform: :ubuntu, resource: :mysql2_chef_gem, provider: Chef::Provider::Mysql2ChefGem::Mysql diff --git a/cookbooks/mysql2_chef_gem/metadata.json b/cookbooks/mysql2_chef_gem/metadata.json new file mode 100644 index 0000000..292454f --- /dev/null +++ b/cookbooks/mysql2_chef_gem/metadata.json @@ -0,0 +1,39 @@ +{ + "name": "mysql2_chef_gem", + "version": "1.0.1", + "description": "Provides the mysql2_chef_gem resource", + "long_description": "", + "maintainer": "Nicolas Blanc", + "maintainer_email": "sinfomicien@gmail.com", + "license": "Apache 2.0", + "platforms": { + "amazon": ">= 0.0.0", + "redhat": ">= 0.0.0", + "centos": ">= 0.0.0", + "scientific": ">= 0.0.0", + "fedora": ">= 0.0.0", + "debian": ">= 0.0.0", + "ubuntu": ">= 0.0.0" + }, + "dependencies": { + "build-essential": ">= 0.0.0", + "mysql": ">= 0.0.0", + "mariadb": ">= 0.0.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + } +} \ No newline at end of file diff --git a/cookbooks/nginx/CHANGELOG.md b/cookbooks/nginx/CHANGELOG.md new file mode 100644 index 0000000..8d5122b --- /dev/null +++ b/cookbooks/nginx/CHANGELOG.md @@ -0,0 +1,435 @@ +nginx Cookbook CHANGELOG +======================== +This file is used to list changes made in each version of the nginx cookbook. + +v2.7.6 / 2015-03-17 +================== + + * Bugfix sites do not need a .conf suffix anymore, [#338][] [@runningman84][] + +v2.7.5 (2015-03-17) +------------------- +**NOTE** As of this release, this cookbook in its current format is deprecated, +and only critical bugs and fixes will be added. +A complete rewrite is in progress, so we appreciate your patience while we sort things out. +The amount of change included here + +* Fix nginx 1.4.4 archive checksum to prevent redownload, [#305][] [@irontoby][] +* Allow setting an empty string to prevent additional repos, [#243][] [@miketheman][] +* Use correct `mime.types` for javascript, [#259][] [@dwradcliffe][] +* Fix `headers_more` module for source installs, [#279][], [@josh-padnick][] & [@miketheman][] +* Remove `libtool` from `geoip` and update download paths & checksums, [@miketheman][] +* Fix unquoted URL with params failing geoip module build (and tests!), [#294][] [@karsten-bruckmann][] & [@miketheman][] +* Fix typo in `source.rb`, [#205][] [@gregkare][] +* Test updates: ChefSpec, test-kitchen. Lots of help by [@jujugrrr][] +* Toolchain updates for testing +* Adds support for `tcp_nopush`, `tcp_nodelay` [@shtouff][] + +After merging a ton of pull requests, here's a brief changelog. Click each to read more. + +* Merge pull request [#335][] from [@stevenolen][] +* Merge pull request [#332][] from [@monsterstrike][] +* Merge pull request [#331][] from [@jalberto][] +* Merge pull request [#327][] from [@nkadel-skyhook][] +* Merge pull request [#326][] from [@bchrobot][] +* Merge pull request [#325][] from [@CanOfSpam3bug324][] +* Merge pull request [#321][] from [@jalberto][] +* Merge pull request [#318][] from [@evertrue][] +* Merge pull request [#314][] from [@bkw][] +* Merge pull request [#312][] from [@thomasmeeus][] +* Merge pull request [#310][] from [@morr][] +* Merge pull request [#305][] from [@irontoby][] +* Merge pull request [#302][] from [@auth0][] +* Merge pull request [#298][] from [@Mytho][] +* Merge pull request [#269][] from [@yveslaroche][] +* Merge pull request [#259][] from [@dwradcliffe][] +* Merge pull request [#254][] from [@evertrue][] +* Merge pull request [#252][] from [@gkra][] +* Merge pull request [#249][] from [@whatcould][] +* Merge pull request [#240][] from [@jcoleman][] +* Merge pull request [#236][] from [@adepue][] +* Merge pull request [#230][] from [@n1koo][] +* Merge pull request [#225][] from [@thommay][] +* Merge pull request [#223][] from [@firmhouse][] +* Merge pull request [#220][] from [@evertrue][] +* Merge pull request [#219][] from [@evertrue][] +* Merge pull request [#204][] from [@usertesting][] +* Merge pull request [#200][] from [@ffuenf][] +* Merge pull request [#188][] from [@larkin][] +* Merge pull request [#184][] from [@tvdinner][] +* Merge pull request [#183][] from [@jenssegers][] +* Merge pull request [#174][] from [@9minutesnooze][] + +https://github.com/miketheman/nginx/compare/v2.7.4...v2.7.5 + +v2.7.4 (2014-06-06) +------------------- +* [COOK-4703] Default openssl version to 1.0.1h to address CVE-2014-0224 + + +v2.7.2 (2014-05-27) +------------------- + +- [COOK-4658] - Nginx::socketproxy if the context is blank or nonexistent, the location in the config file has a double slash at the beginning +- [COOK-4644] - add support to nginx::repo for Amazon Linux +- Allow .kitchen.cloud.yml to use an environment variable for the EC2 Availability Zone + + +v2.7.0 (2014-05-15) +------------------- +- [COOK-4643] - Update metadata lock on ohai +- [COOK-4588] - Give more love to FreeBSD +- [COOK-4601] - Add proxy type: Socket + + +v2.6.2 (2014-04-09) +------------------- +[COOK-4527] - set default openssl source version to 1.0.1g to address CVE-2014-0160 aka Heartbleed + + +v2.6.0 (2014-04-08) +------------------- +- Reverting COOK-4323 + + +v2.5.0 (2014-03-27) +------------------- +- [COOK-4323] - Need a resource to easily configure available sites (vhosts) + + +v2.4.4 (2014-03-13) +------------------- +- Updating for build-essential 2.0 + + +v2.4.2 (2014-02-28) +------------------- +Fixing bad commit from COOK-4330 + + +v2.4.1 (2014-02-27) +------------------- +- [COOK-4345] - nginx default recipe include install type recipe directly + + +v2.4.0 (2014-02-27) +------------------- +- [COOK-4380] - kitchen.yml platform listings for ubuntu-10.04 and ubuntu-12.04 are missing the dot +- [COOK-4330] - Bump nginx version for security issues (CVE-2013-0337, CVE-2013-4547) + + +v2.3.0 (2014-02-25) +------------------- +- **[COOK-4293](https://tickets.chef.io/browse/COOK-4293)** - Update testing Gems in nginx and fix a rubocop warnings +- **[COOK-4237] - Nginx version incorrectly parsed on Ubuntu 13 +- **[COOK-3866] - Nginx default site folder + + +v2.2.2 (2014-01-23) +------------------- +[COOK-3672] - Add gzip_static option + + +v2.2.0 +------ +No changes. Version bump for toolchain + + +v2.1.0 +------ +[COOK-3923] - Enable the list of packages installed by nginx::passenger to be configurable +[COOK-3672] - Nginx should support the gzip_static option +Updating for yum ~> 3.0 +Fixing up style for rubocop +Updating test-kitchen harness + + +v2.0.8 +------ +fixing metadata version error. locking to 3.0 + + +v2.0.6 +------ +Locking yum dependency to '< 3' + + +v2.0.4 +------ +### Bug +- **[COOK-3808](https://tickets.chef.io/browse/COOK-3808)** - nginx::passenger run fails because of broken installation of package dependencies +- **[COOK-3779](https://tickets.chef.io/browse/COOK-3779)** - Build in master fails due to rubocop error + + +v2.0.2 +------ +### Bug +- **[COOK-3808](https://tickets.chef.io/browse/COOK-3808)** - nginx::passenger run fails because of broken installation of package dependencies +- **[COOK-3779](https://tickets.chef.io/browse/COOK-3779)** - Build in master fails due to rubocop error + + +v2.0.0 +------ +### Improvement +- **[COOK-3733](https://tickets.chef.io/browse/COOK-3733)** - Add RPM key names and GPG checking +- **[COOK-3687](https://tickets.chef.io/browse/COOK-3687)** - Add support for `http_perl` +- **[COOK-3603](https://tickets.chef.io/browse/COOK-3603)** - Add a recipe for using custom openssl +- **[COOK-3602](https://tickets.chef.io/browse/COOK-3602)** - Use an attribute for the status module port +- **[COOK-3549](https://tickets.chef.io/browse/COOK-3549)** - Refactor custom modules support +- **[COOK-3521](https://tickets.chef.io/browse/COOK-3521)** - Add support for `http_auth_request` +- **[COOK-3520](https://tickets.chef.io/browse/COOK-3520)** - Add support for `spdy` +- **[COOK-3185](https://tickets.chef.io/browse/COOK-3185)** - Add `gzip_*` attributes +- **[COOK-2712](https://tickets.chef.io/browse/COOK-2712)** - Update `upload_progress` version to 0.9.0 + +### Bug +- **[COOK-3686](https://tickets.chef.io/browse/COOK-3686)** - Remove deprecated 'passenger_use_global_queue' directive +- **[COOK-3626](https://tickets.chef.io/browse/COOK-3626)** - Parameterize hardcoded path to helper scripts +- **[COOK-3571](https://tickets.chef.io/browse/COOK-3571)** - Reloda ohai plugin after installation +- **[COOK-3428](https://tickets.chef.io/browse/COOK-3428)** - Fix an issue where access logs are not disabled when the `disable_access_log` attribute is set to `true` +- **[COOK-3322](https://tickets.chef.io/browse/COOK-3322)** - Fix an issue where `nginx::ohai_plugin` fails when using source recipe +- **[COOK-3241](https://tickets.chef.io/browse/COOK-3241)** - Fix an issue where`nginx::ohai_plugin` fails unless using source recipe + +### New Feature +- **[COOK-3605](https://tickets.chef.io/browse/COOK-3605)** - Add Lua module + + +v1.8.0 +------ +### Bug +- **[COOK-3397](https://tickets.chef.io/browse/COOK-3397)** - Fix user from nginx package on Gentoo +- **[COOK-2968](https://tickets.chef.io/browse/COOK-2968)** - Fix foodcritic failure +- **[COOK-2723](https://tickets.chef.io/browse/COOK-2723)** - Remove duplicate passenger `max_pool_size` + +### Improvement +- **[COOK-3186](https://tickets.chef.io/browse/COOK-3186)** - Add `client_body_buffer_size` and `server_tokens attributes` +- **[COOK-3080](https://tickets.chef.io/browse/COOK-3080)** - Add rate-limiting support +- **[COOK-2927](https://tickets.chef.io/browse/COOK-2927)** - Add support for `real_ip_recursive` directive +- **[COOK-2925](https://tickets.chef.io/browse/COOK-2925)** - Fix ChefSpec converge +- **[COOK-2724](https://tickets.chef.io/browse/COOK-2724)** - Automatically create directory for PID file +- **[COOK-2472](https://tickets.chef.io/browse/COOK-2472)** - Bump nginx version to 1.2.9 +- **[COOK-2312](https://tickets.chef.io/browse/COOK-2312)** - Add additional `mine_types` to the `gzip_types` value + +### New Feature +- **[COOK-3183](https://tickets.chef.io/browse/COOK-3183)** - Allow inclusion in extra-cookbook modules + +v1.7.0 +------ +### Improvement +- [COOK-3030]: The repo_source attribute should allow you to not add any additional repositories to your node + +### Sub-task +- [COOK-2738]: move nginx::passenger attributes to `nginx/attributes/passenger.rb` + +v1.6.0 +------ +### Task +- [COOK-2409]: update nginx::source recipe for new `runit_service` resource +- [COOK-2877]: update nginx cookbook test-kitchen support to 1.0 (alpha) + +### Improvement +- [COOK-1976]: nginx source should be able to configure binary path +- [COOK-2622]: nginx: add upstart support +- [COOK-2725]: add "configtest" subcommand in initscript + +### Bug +- [COOK-2398]: nginx_site definition cannot be used to manage the default site +- [COOK-2493]: Resources in nginx::source recipe always use 1.2.6 version, even overriding version attribute +- [COOK-2531]: Remove usage of non-existant attribute "description" for `apt_repository` +- [COOK-2665]: nginx::source install with custom sbin_path breaks ohai data + +v1.4.0 +------ +- [COOK-2183] - Install nginx package from nginxyum repo +- [COOK-2311] - headers-more should be updated to the latest version +- [COOK-2455] - Support sendfile option (nginx.conf) + +v1.3.0 +------ +- [COOK-1979] - Passenger module requires curl-dev(el) +- [COOK-2219] - Support `proxy_read_timeout` (in nginx.conf) +- [COOK-2220] - Support `client_max_body_size` (in nginx.conf) +- [COOK-2280] - Allow custom timing of nginx_site's reload notification +- [COOK-2304] - nginx cookbook should install 1.2.6 not 1.2.3 for source installs +- [COOK-2309] - checksums for geoip files need to be updated in nginx +- [COOK-2310] - Checksum in the `nginx::upload_progress` recipe is not correct +- [COOK-2314] - nginx::passenger: Install the latest version of passenger +- [COOK-2327] - nginx: passenger recipe should find ruby via Ohai +- [COOK-2328] - nginx: Update mime.types file to the latest +- [COOK-2329] - nginx: Update naxsi rules to the current + +v1.2.0 +------ +- [COOK-1752] - Add headers more module to the nginx cookbook +- [COOK-2209] - nginx source recipe should create web user before creating directories +- [COOK-2221] - make nginx::source compatible with gentoo +- [COOK-2267] - add version for runit recommends + +v1.1.4 +------ +- [COOK-2168] - specify package name as an attribute + +v1.1.2 +------ +- [COOK-1766] - Nginx Source Recipe Rebuilding Source at Every Run +- [COOK-1910] - Add IPv6 module +- [COOK-1966] - nginx cookbook should let you set `gzip_vary` and `gzip_buffers` in nginx.conf +- [COOK-1969]- - nginx::passenger module not included due to use of symbolized `:nginx_configure_flags` +- [COOK-1971] - Template passenger.conf.erb configures key `passenger_max_pool_size` 2 times +- [COOK-1972] - nginx::source compile_nginx_source reports success in spite of failed compilation +- [COOK-1975] - nginx::passenger requires rake gem +- [COOK-1979] - Passenger module requires curl-dev(el) +- [COOK-2080] - Restart nginx on source compilation + +v1.1.0 +------ +- [COOK-1263] - Nginx log (and possibly other) directory creations should be recursive +- [COOK-1515] - move creation of `node['nginx']['dir']` out of commons.rb +- [COOK-1523] - nginx `http_geoip_module` requires libtoolize +- [COOK-1524] - nginx checksums are md5 +- [COOK-1641] - add "use", "`multi_accept`" and "`worker_rlimit_nofile`" to nginx cookbook +- [COOK-1683] - Nginx fails Windows nodes just by being required in metadata +- [COOK-1735] - Support Amazon Linux in nginx::source recipe +- [COOK-1753] - Add ability for nginx::passenger recipe to configure more Passenger global settings +- [COOK-1754] - Allow group to be set in nginx.conf file +- [COOK-1770] - nginx cookbook fails on servers that don't have a "cpu" attribute +- [COOK-1781] - Use 'sv' to reload nginx when using runit +- [COOK-1789] - stop depending on bluepill, runit and yum. they are not required by nginx cookbook +- [COOK-1791] - add name attribute to metadata +- [COOK-1837] - nginx::passenger doesn't work on debian family +- [COOK-1956] - update naxsi version due to incompatibility with newer nginx + +v1.0.2 +------ +- [COOK-1636] - relax the version constraint on ohai + +v1.0.0 +------ +- [COOK-913] - defaults for gzip cause warning on service restart +- [COOK-1020] - duplicate MIME type +- [COOK-1269] - add passenger module support through new recipe +- [COOK-1306] - increment nginx version to 1.2 (now 1.2.3) +- [COOK-1316] - default site should not always be enabled +- [COOK-1417] - resolve errors preventing build from source +- [COOK-1483] - source prefix attribute has no effect +- [COOK-1484] - source relies on /etc/sysconfig +- [COOK-1511] - add support for naxsi module +- [COOK-1525] - nginx source is downloaded every time +- [COOK-1526] - nginx_site does not remove sites +- [COOK-1527] - add `http_echo_module` recipe + +v0.101.6 +-------- +Erroneous cookbook upload due to timeout. + +Version #'s are cheap. + +v0.101.4 +-------- +- [COOK-1280] - Improve RHEL family support and fix ohai_plugins recipe bug +- [COOK-1194] - allow installation method via attribute +- [COOK-458] - fix duplicate nginx processes + +v0.101.2 +-------- +* [COOK-1211] - include the default attributes explicitly so version is available. + +v0.101.0 +-------- +**Attribute Change**: `node['nginx']['url']` -> `node['nginx']['source']['url']`; see the README.md. + +- [COOK-1115] - daemonize when using init script +- [COOK-477] - module compilation support in nginx::source + +v0.100.4 +-------- +- [COOK-1126] - source version bump to 1.0.14 + +v0.100.2 +-------- +- [COOK-1053] - Add :url attribute to nginx cookbook + +v0.100.0 +-------- +- [COOK-818] - add "application/json" per RFC. +- [COOK-870] - bluepill init style support +- [COOK-957] - Compress application/javascript. +- [COOK-981] - Add reload support to NGINX service + +v0.99.2 +------- +- [COOK-809] - attribute to disable access logging +- [COOK-772] - update nginx download source location + + +[#174]: https://github.com/miketheman/nginx/issues/174 +[#183]: https://github.com/miketheman/nginx/issues/183 +[#184]: https://github.com/miketheman/nginx/issues/184 +[#188]: https://github.com/miketheman/nginx/issues/188 +[#200]: https://github.com/miketheman/nginx/issues/200 +[#204]: https://github.com/miketheman/nginx/issues/204 +[#205]: https://github.com/miketheman/nginx/issues/205 +[#219]: https://github.com/miketheman/nginx/issues/219 +[#220]: https://github.com/miketheman/nginx/issues/220 +[#223]: https://github.com/miketheman/nginx/issues/223 +[#225]: https://github.com/miketheman/nginx/issues/225 +[#230]: https://github.com/miketheman/nginx/issues/230 +[#236]: https://github.com/miketheman/nginx/issues/236 +[#240]: https://github.com/miketheman/nginx/issues/240 +[#243]: https://github.com/miketheman/nginx/issues/243 +[#249]: https://github.com/miketheman/nginx/issues/249 +[#252]: https://github.com/miketheman/nginx/issues/252 +[#254]: https://github.com/miketheman/nginx/issues/254 +[#259]: https://github.com/miketheman/nginx/issues/259 +[#269]: https://github.com/miketheman/nginx/issues/269 +[#279]: https://github.com/miketheman/nginx/issues/279 +[#294]: https://github.com/miketheman/nginx/issues/294 +[#298]: https://github.com/miketheman/nginx/issues/298 +[#302]: https://github.com/miketheman/nginx/issues/302 +[#305]: https://github.com/miketheman/nginx/issues/305 +[#310]: https://github.com/miketheman/nginx/issues/310 +[#312]: https://github.com/miketheman/nginx/issues/312 +[#314]: https://github.com/miketheman/nginx/issues/314 +[#318]: https://github.com/miketheman/nginx/issues/318 +[#321]: https://github.com/miketheman/nginx/issues/321 +[#325]: https://github.com/miketheman/nginx/issues/325 +[#326]: https://github.com/miketheman/nginx/issues/326 +[#327]: https://github.com/miketheman/nginx/issues/327 +[#331]: https://github.com/miketheman/nginx/issues/331 +[#332]: https://github.com/miketheman/nginx/issues/332 +[#335]: https://github.com/miketheman/nginx/issues/335 +[#338]: https://github.com/miketheman/nginx/issues/338 +[@9minutesnooze]: https://github.com/9minutesnooze +[@CanOfSpam3bug324]: https://github.com/CanOfSpam3bug324 +[@Mytho]: https://github.com/Mytho +[@adepue]: https://github.com/adepue +[@auth0]: https://github.com/auth0 +[@bchrobot]: https://github.com/bchrobot +[@bkw]: https://github.com/bkw +[@dwradcliffe]: https://github.com/dwradcliffe +[@evertrue]: https://github.com/evertrue +[@ffuenf]: https://github.com/ffuenf +[@firmhouse]: https://github.com/firmhouse +[@gkra]: https://github.com/gkra +[@gregkare]: https://github.com/gregkare +[@irontoby]: https://github.com/irontoby +[@jalberto]: https://github.com/jalberto +[@jcoleman]: https://github.com/jcoleman +[@jenssegers]: https://github.com/jenssegers +[@josh-padnick]: https://github.com/josh-padnick +[@jujugrrr]: https://github.com/jujugrrr +[@karsten-bruckmann]: https://github.com/karsten-bruckmann +[@larkin]: https://github.com/larkin +[@miketheman]: https://github.com/miketheman +[@monsterstrike]: https://github.com/monsterstrike +[@morr]: https://github.com/morr +[@n1koo]: https://github.com/n1koo +[@nkadel-skyhook]: https://github.com/nkadel-skyhook +[@runningman84]: https://github.com/runningman84 +[@shtouff]: https://github.com/shtouff +[@stevenolen]: https://github.com/stevenolen +[@thomasmeeus]: https://github.com/thomasmeeus +[@thommay]: https://github.com/thommay +[@tvdinner]: https://github.com/tvdinner +[@usertesting]: https://github.com/usertesting +[@whatcould]: https://github.com/whatcould +[@yveslaroche]: https://github.com/yveslaroche diff --git a/cookbooks/nginx/README.md b/cookbooks/nginx/README.md new file mode 100644 index 0000000..8281bad --- /dev/null +++ b/cookbooks/nginx/README.md @@ -0,0 +1,521 @@ +nginx Cookbook +============== +[![Cookbook](http://img.shields.io/cookbook/v/nginx.svg)](https://github.com/miketheman/nginx) +[![Build Status](https://travis-ci.org/miketheman/nginx.svg?branch=master)](https://travis-ci.org/miketheman/nginx) +[![Gitter chat](https://img.shields.io/badge/Gitter-miketheman%2Fnginx-brightgreen.svg)](https://gitter.im/miketheman/nginx) + +Installs nginx from package OR source code and sets up configuration handling similar to Debian's Apache2 scripts. + +# READ THIS FIRST + +After having struggled with the cookbook format and the interfaces being brittle, the maintainers have decided to begin rewriting the core implmenetation of the nginx cookbook from the ground up, to allow for better flexibility, testability and maintianability. + +To this end, we request that you not open new issues for the existing codebase. + +Pull requests for bugs will be merged, any obvious optimizations and clarifications will be merged, and a 2.7.5 release will be shipped, and we will focus on writing the 3.0.0 version. + +Thank you for your help on this front! + +-- The Maintainers + +--- + + +Requirements +------------ +### Cookbooks +The following cookbooks are direct dependencies because they're used for common "default" functionality. + +- build-essential (for nginx::source) +- ohai (for nginx::ohai_plugin) + +The following cookbook is not a strict dependency because its use can be controlled by an attribute, so it may not be a common "default." + +- runit (for nginx::source) +- On RHEL family distros, the "yum" cookbook is required for `recipe[yum::epel]`. +- On Ubuntu, when using Nginx.org's stable package, `recipe[apt::default]` is required. + + +### Platforms +The following platforms are supported and tested under test kitchen: + +- Ubuntu 10.04, Ubuntu 12.04 +- CentOS 5.8, 6.3 + +Other Debian and RHEL family distributions are assumed to work. + + +Attributes +---------- +Node attributes for this cookbook are logically separated into different files. Some attributes are set only via a specific recipe. + +### default +Generally used attributes. Some have platform specific values. See `attributes/default.rb`. "The Config" refers to "nginx.conf" the main config file. + +- `node['nginx']['dir']` - Location for Nginx configuration. +- `node['nginx']['conf_template']` - The `source` template to use when creating the `nginx.conf`. +- `node['nginx']['conf_cookbook']` - The cookbook where `node['nginx']['conf_template']` resides. +- `node['nginx']['log_dir']` - Location for Nginx logs. +- `node['nginx']['log_dir_perm']` - Permissions for Nginx logs folder. +- `node['nginx']['user']` - User that Nginx will run as. +- `node['nginx']['group]` - Group for Nginx. +- `node['nginx']['port']` - Port for nginx to listen on. +- `node['nginx']['binary']` - Path to the Nginx binary. +- `node['nginx']['init_style']` - How to run Nginx as a service when + using `nginx::source`. Values can be "runit", "upstart", "init" or + "bluepill". When using runit or bluepill, those recipes will be + included as well and are dependencies of this cookbook. Recipes + are not included for upstart, it is assumed that upstart is built + into the platform you are using (ubuntu or el6). This attribute is + not used in the `nginx` recipe because the package manager's init + script style for the platform is assumed. Upstart is never set as + a default as this represents a change in behavior, if you are running + ubuntu or el6 and want to use upstart, please set this attribute in + a role or similar. +- `node['nginx']['upstart']['foreground']` - Set this to true if you + want upstart to run nginx in the foreground, set to false if you + want upstart to detach and track the process via pid. +- `node['nginx']['upstart']['runlevels']` - String of runlevels in the + format '2345' which determines which runlevels nginx will start at + when entering and stop at when leaving. +- `node['nginx']['upstart']['respawn_limit']` - Respawn limit in upstart + stanza format, count followed by space followed by interval in seconds. +- `node['nginx']['pid']` - Location of the PID file. +- `node['nginx']['keepalive']` - Whether to use `keepalive_timeout`, + any value besides "on" will leave that option out of the config. +- `node['nginx']['keepalive_requests']` - used for config value of + `keepalive_requests`. +- `node['nginx']['keepalive_timeout']` - used for config value of + `keepalive_timeout`. +- `node['nginx']['worker_processes']` - used for config value of + `worker_processes`. +- `node['nginx']['worker_connections']` - used for config value of + `events { worker_connections }` +- `node['nginx']['worker_rlimit_nofile']` - used for config value of + `worker_rlimit_nofile`. Can replace any "ulimit -n" command. The + value depend on your usage (cache or not) but must always be + superior than worker_connections. +- `node['nginx']['multi_accept']` - used for config value of `events { + multi_accept }`. Try to accept() as many connections as possible. + Disable by default. +- `node['nginx']['event']` - used for config value of `events { use + }`. Set the event-model. By default nginx looks for the most + suitable method for your OS. +- `node['nginx']['accept_mutex_delay']` - used for config value of + `accept_mutex_delay` +- `node['nginx']['server_tokens']` - used for config value of + `server_tokens`. +- `node['nginx']['server_names_hash_bucket_size']` - used for config + value of `server_names_hash_bucket_size`. +- `node['nginx']['disable_access_log']` - set to true to disable the + general access log, may be useful on high traffic sites. +- `node['nginx']['access_log_options']` - Set to a string of additional options + to be appended to the access log directive +- `node['nginx']['error_log_options']` - Set to a string of additional options + to be appended to the error log directive +- `node['nginx']['default_site_enabled']` - enable the default site +- `node['nginx']['sendfile']` - Whether to use `sendfile`. Defaults to "on". +- `node['nginx']['tcp_nopush']` - Whether to use `tcp_nopush`. Defaults to "on". +- `node['nginx']['tcp_nodelay']` - Whether to use `tcp_nodelay`. Defaults to "on". +- `node['nginx']['install_method']` - Whether nginx is installed from + packages or from source. +- `node['nginx']['types_hash_max_size']` - Used for the + `types_hash_max_size` configuration directive. +- `node['nginx']['types_hash_bucket_size']` - Used for the + `types_hash_bucket_size` configuration directive. +- `node['nginx']['proxy_read_timeout']` - defines a timeout (between two + successive read operations) for reading a response from the proxied server. +- `node['nginx']['client_body_buffer_size']` - used for config value of + `client_body_buffer_size`. +- `node['nginx']['client_max_body_size']` - specifies the maximum accepted body + size of a client request, as indicated by the request header Content-Length. +- `node['nginx']['repo_source']` - when installed from a package this attribute affects + which yum repositories, if any, will be added before installing the nginx package. The + default value of 'epel' will use the `yum::epel` recipe, 'nginx' will use the + `nginx::repo` recipe, 'passenger' will use the 'nginx::repo_passenger' recipe, and setting no value will not add any additional repositories. +* `node['nginx']['sts_max_age']` - Enable Strict Transport Security for all apps (See: http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security). This attribute adds the following header: + + Strict-Transport-Security max-age=SECONDS + +to all incoming requests and takes an integer (in seconds) as its argument. +* `node['nginx']['default']['modules']` - Array specifying which +modules to enable via the conf-enabled config include function. +Currently the only valid value is "socketproxy". + +Other configurations + +- `node['nginx']['extra_configs']` - a Hash of key/values to nginx configuration. + +Rate Limiting + +- `node['nginx']['enable_rate_limiting']` - set to true to enable rate + limiting (`limit_req_zone` in nginx.conf) +- `node['nginx']['rate_limiting_zone_name']` - sets the zone in + `limit_req_zone`. +- `node['nginx']['rate_limiting_backoff']` - sets the backoff time for + `limit_req_zone`. +- `node['nginx']['rate_limit']` - set the rate limit amount for + `limit_req_zone`. + +### gzip module + +- `node['nginx']['gzip']` - Whether to use gzip, can be "on" or "off" +- `node['nginx']['gzip_http_version']` - used for config value of `gzip_http_version`. +- `node['nginx']['gzip_comp_level']` - used for config value of `gzip_comp_level`. +- `node['nginx']['gzip_proxied']` - used for config value of `gzip_proxied`. +- `node['nginx']['gzip_vary']` - used for config value of `gzip_vary`. +- `node['nginx']['gzip_buffers']` - used for config value of `gzip_buffers`. +- `node['nginx']['gzip_types']` - used for config value of `gzip_types` - must be an Array. +- `node['nginx']['gzip_min_length']` - used for config value of `gzip_min_length`. +- `node['nginx']['gzip_disable']` - used for config value of `gzip_disable`. +- `node['nginx']['gzip_static']` - used for config value of `gzip_static` (`http_gzip_static_module` must be enabled) +### Attributes set in recipes + +#### nginx::source +- `node['nginx']['daemon_disable']` - Whether the daemon should be + disabled which can be true or false; disable the daemon (run in the + foreground) when using a service supervisor such as runit or + bluepill for "init_style". This is automatically set in the + `nginx::source` recipe when the init style is not bluepill or runit. + +#### nginx::authorized_ips +- `node['nginx']['remote_ip_var']` - The remote ip variable name to + use. +- `node['nginx']['authorized_ips']` - IPs authorized by the module + +#### nginx::http_realip_module +From: http://nginx.org/en/docs/http/ngx_http_realip_module.html + +- `node['nginx']['realip']['header']` - Header to use for the RealIp + Module; only accepts "X-Forwarded-For" or "X-Real-IP" +- `node['nginx']['realip']['addresses']` - Addresses to use for the + `http_realip` configuration. +- `node['nginx']['realip']['real_ip_recursive']` - If recursive search is enabled, the original client address that matches one of the trusted addresses is replaced by the last non-trusted address sent in the request header field. Can be on "on" or "off" (default). + +### source +These attributes are used in the `nginx::source` recipe. Some of them +are dynamically modified during the run. See `attributes/source.rb` +for default values. + +- `node['nginx']['source']['url']` - (versioned) URL for the Nginx + source code. By default this will use the version specified as + `node['nginx']['version']`. +- `node['nginx']['source']['prefix']` - (versioned) prefix for + installing nginx from source +- `node['nginx']['source']['conf_path']` - location of the main config + file, in `node['nginx']['dir']` by default. +- `node['nginx']['source']['modules']` - Array of modules that should + be compiled into Nginx by including their recipes in + `nginx::source`. +- `node['nginx']['source']['default_configure_flags']` - The default + flags passed to the configure script when building Nginx. +- `node['nginx']['configure_flags']` - Preserved for compatibility and + dynamically generated from the + `node['nginx']['source']['default_configure_flags']` in the + `nginx::source` recipe. +* `node['nginx']['source']['use_existing_user']` - set to `true` if you + do not want `nginx::source` recipe to create system user with name + `node['nginx']['user']`. + +### geoip +These attributes are used in the `nginx::http_geoip_module` recipe. +Please note that the `country_dat_checksum` and `city_dat_checksum` +are based on downloads from a datacenter in Fremont, CA, USA. You +really should override these with checksums for the geo tarballs from +your node location. + +**Note** The upstream, maxmind.com, may block access for repeated + downloads of the data files. It is recommended that you download and + host the data files, and change the URLs in the attributes. + +- `node['nginx']['geoip']['path']` - Location where to install the + geoip libraries. +- `node['nginx']['geoip']['enable_city']` - Whether to enable City + data +- `node['nginx']['geoip']['country_dat_url']` - Country data tarball + URL +- `node['nginx']['geoip']['country_dat_checksum']` - Country data + tarball checksum +- `node['nginx']['geoip']['city_dat_url']` - City data tarball URL +- `node['nginx']['geoip']['city_dat_checksum']` - City data tarball + checksum +- `node['nginx']['geoip']['lib_version']` - Version of the GeoIP + library to install +- `node['nginx']['geoip']['lib_url']` - (Versioned) Tarball URL of the + GeoIP library +- `node['nginx']['geoip']['lib_checksum']` - Checksum of the GeoIP + library tarball + +### upload_progress +These attributes are used in the `nginx::upload_progress_module` +recipe. + +- `node['nginx']['upload_progress']['url']` - URL for the tarball. +- `node['nginx']['upload_progress']['checksum']` - Checksum of the + tarball. +- `node['nginx']['upload_progress']['javascript_output']` - Output in javascript. + Default is `true` for backwards compatibility. +- `node['nginx']['upload_progress']['zone_name']` - Zone name which will + be used to store the per-connection tracking information. + Default is `proxied`. +- `node['nginx']['upload_progress']['zone_size']` - Zone size in bytes. + Default is `1m` (1 megabyte). + +### passenger +These attributes are used in the `nginx::passenger` recipe. + +- `node['nginx']['passenger']['version']` - passenger gem version +- `node['nginx']['passenger']['root']` - passenger gem root path +- `node['nginx']['passenger']['install_rake']` - set to false if rake already present on system +- `node['nginx']['passenger']['max_pool_size']` - maximum passenger + pool size (default=10) +- `node['nginx']['passenger']['ruby']` - Ruby path for Passenger to + use (default=`$(which ruby)`) +- `node['nginx']['passenger']['spawn_method']` - passenger spawn + method to use (default=`smart-lv2`) +- `node['nginx']['passenger']['buffer_response']` - turns on or off + response buffering (default=`on`) +- `node['nginx']['passenger']['max_pool_size']` - passenger maximum + pool size (default=`6`) +- `node['nginx']['passenger']['min_instances']` - minimum instances + (default=`1`) +- `node['nginx']['passenger']['max_instances_per_app']` - maximum + instances per app (default=`0`) +- `node['nginx']['passenger']['pool_idle_time']` - passenger pool idle + time (default=`300`) +- `node['nginx']['passenger']['max_requests']` - maximum requests + (default=`0`) +- `node['nginx']['passenger']['nodejs']` - Nodejs path for Passenger to + use (default=nil) + +Basic configuration to use the official Phusion Passenger repositories: +- `node['nginx']['repo_source']` - 'passenger' +- `node['nginx']['package_name']` - 'nginx-extras' +- `node['nginx']['passenger']['install_method']` - 'package' + +### echo +These attributes are used in the `nginx::http_echo_module` recipe. + +- `node['nginx']['echo']['version']` - The version of `http_echo` you + want (default: 0.40) +- `node['nginx']['echo']['url']` - URL for the tarball. +- `node['nginx']['echo']['checksum']` - Checksum of the tarball. + +### status +These attributes are used in the `nginx::http_stub_status_module` recipe. + +- `node['nginx']['status']['port']` - The port on which nginx will + serve the status info (default: 8090) + +### syslog +These attributes are used in the `nginx::syslog_module` recipe. + +- `node['nginx']['syslog']['git_repo']` - The git repository url to use + for the syslog patches. +- `node['nginx']['syslog']['git_revision']` - The revision on the git + repository to checkout. + +### openssl_source +These attributes are used in the `nginx::openssl_source` recipe. + +- `node['nginx']['openssl_source']['version']` - The version of OpenSSL + you want to download and use (default: 1.0.1e) +- `node['nginx']['openssl_source']['url']` - The url for the OpenSSL source + + +## socketproxy.rb + +These attributes are used in the `nginx::socketproxy` recipe. + +* `node['nginx']['socketproxy']['root']` - The directory (on your server) where socketproxy apps are deployed. +* `node['nginx']['socketproxy']['default_app']` - Static assets directory for requests to "/" that don't meet any proxy_pass filter requirements. +* `node['nginx']['socketproxy']['apps']['app_name']['prepend_slash']` - Prepend a slash to requests to app "app_name" before sending them to the socketproxy socket. +* `node['nginx']['socketproxy']['apps']['app_name']['context_name']` - URI (e.g. "app_name" in order to achieve "http://mydomain.com/app_name") at which to host the application "app_name" +* `node['nginx']['socketproxy']['apps']['app_name']['subdir']` - Directory (under `node['nginx']['socketproxy']['root']`) in which to find the application. + +Recipes +------- +This cookbook provides three main recipes for installing Nginx. + +- `default.rb` - *Use this recipe* if you have a native package for + Nginx. +- `repo.rb` - The developer of Nginx also maintain + [stable packages](http://nginx.org/en/download.html) for several + platforms. +- `source.rb` - *Use this recipe* if you do not have a native package for + Nginx, or if you want to install a newer version than is available, + or if you have custom module compilation needs. + +Several recipes are related to the `source` recipe specifically. See +that recipe's section below for a description. + +### default +The default recipe will install Nginx as a native package for the +system through the package manager and sets up the configuration +according to the Debian site enable/disable style with `sites-enabled` +using the `nxensite` and `nxdissite` scripts. The nginx service will +be managed with the normal init scripts that are presumably included +in the native package. + +Includes the `ohai_plugin` recipe so the plugin is available. + +### socketproxy + +This will add socketproxy support to your nginx proxy setup. Do not +include this recipe directly. Instead, add it to the +`node['nginx']['default']['modules']` array (see below). + +### ohai_plugin + +This recipe provides an Ohai plugin as a template. It is included by +both the `default` and `source` recipes. + +### authorized_ips +Sets up configuration for the `authorized_ip` nginx module. + +### source +This recipe is responsible for building Nginx from source. It ensures +that the required packages to build Nginx are installed (pcre, +openssl, compile tools). The source will be downloaded from the +`node['nginx']['source']['url']`. The `node['nginx']['user']` will be +created as a system user. If you want to use existing user set +`node['nginx']['source']['use_existing_user']` to `true`. The appropriate +configuration and log directories and config files will be created +as well according to the attributes `node['nginx']['dir']` and +`node['nginx']['log_dir']`. + +The recipe attempts to detect whether additional modules should be +added to the configure command through recipe inclusion (see below), +and whether the version or configuration flags have changed and should +trigger a recompile. + +The nginx service will be set up according to +`node['nginx']['init_style']`. Available options are: + +- runit: uses runit cookbook and sets up `runit_service`. +- bluepill: uses bluepill cookbook and sets up `bluepill_service`. +- anything else (e.g., "init") will use the nginx init script + template. + +**RHEL/CentOS** This recipe should work on RHEL/CentOS with "init" as + the init style. + +The following recipes are used to build module support into Nginx. To +use a module in the `nginx::source` recipe, add its recipe name to the +attribute `node['nginx']['source']['modules']`. + +- `ipv6.rb` - enables IPv6 support +- `http_echo_module.rb` - downloads the `http_echo_module` module and + enables it as a module when compiling nginx. +- `http_geoip_module.rb` - installs the GeoIP libraries and data files + and enables the module for compilation. +- `http_gzip_static_module.rb` - enables the module for compilation. Be sure to set `node['nginx']['gzip_static'] = 'yes'`. +- `http_perl_module.rb` - enables embedded Perl for compilation. +- `http_realip_module.rb` - enables the module for compilation and + creates the configuration. +- `http_ssl_module.rb` - enables SSL for compilation. +- `http_stub_status_module.rb` - provides `nginx_status` configuration + and enables the module for compilation. +- `naxsi_module` - enables the naxsi module for the web application + firewall for nginx. +- `passenger` - builds the passenger gem and configuration for + "`mod_passenger`". +- `syslog` - enables syslog support for nginx. This only works with + source builds. See https://github.com/yaoweibin/nginx_syslog_patch +- `upload_progress_module.rb` - builds the `upload_progress` module + and enables it as a module when compiling nginx. +- `openssl_source.rb` - downloads and uses custom OpenSSL source + when compiling nginx + +Definitions +----------- + +The cookbook provides a new definition. At some point in the future this definition may be refactored into a lightweight resource and provider as suggested by [foodcritic rule FC015](http://acrmp.github.com/foodcritic/#FC015). + +### nginx\_site + +Enable or disable a Server Block in +`#{node['nginx']['dir']}/sites-available` by calling nxensite or +nxdissite (introduced by this cookbook) to manage the symbolic link in +`#{node['nginx']['dir']}/sites-enabled`. + +The template for the site must be managed as a separate resource. + +### Parameters: + +* `name` - Name of the site. +* `enable` - Default true, which uses `nxensite` to enable the site. If false, the site will be disabled with `nxdissite`. + + +Adding New Modules +------------------ +To add a new module to be compiled into nginx in the source recipe, +the node's run state is manipulated in a recipe, and the module as a +recipe should be added to `node['nginx']['source']['modules']`. For +example: + +```ruby +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ['--with-http_stub_status_module'] +``` + +The recipe will be included by `recipe[nginx::source]` automatically, +adding the configure flags. Add any other configuration templates or +other resources as required. See the recipes described above for +examples. + + +Ohai Plugin +----------- +The `ohai_plugin` recipe includes an Ohai plugin. It will be +automatically installed and activated, providing the following +attributes via ohai, no matter how nginx is installed (source or +package): + +- `node['nginx']['version']` - version of nginx +- `node['nginx']['configure_arguments']` - options passed to + `./configure` when nginx was built +- `node['nginx']['prefix']` - installation prefix +- `node['nginx']['conf_path']` - configuration file path + +In the source recipe, it is used to determine whether control +attributes for building nginx have changed. + + +Usage +----- +Include the recipe on your node or role that fits how you wish to +install Nginx on your system per the recipes section above. Modify the +attributes as required in your role to change how various +configuration is applied per the attributes section above. In general, +override attributes in the role should be used when changing +attributes. + +There's some redundancy in that the config handling hasn't been +separated from the installation method (yet), so use only one of the +recipes, default or source. + + +License & Authors +----------------- +- Author:: Joshua Timberman () +- Author:: Adam Jacob () +- Author:: AJ Christensen () +- Author:: Jamie Winsor () +- Author:: Mike Fiedler () + +```text +Copyright 2008-2014, Chef Software, Inc + +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/cookbooks/nginx/attributes/auth_request.rb b/cookbooks/nginx/attributes/auth_request.rb new file mode 100644 index 0000000..c4dbcf0 --- /dev/null +++ b/cookbooks/nginx/attributes/auth_request.rb @@ -0,0 +1,23 @@ +# +# Cookbook Name:: nginx +# Attributes:: auth_request +# +# Author:: David Radcliffe () +# +# Copyright 2013, David Radcliffe +# +# 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. +# + +default['nginx']['auth_request']['url'] = 'http://mdounin.ru/hg/ngx_http_auth_request_module/archive/ee8ff54f9b66.tar.gz' +default['nginx']['auth_request']['checksum'] = '7ab85e1c350c5a9c60ed1319c45fed144cc3c3e1' diff --git a/cookbooks/nginx/attributes/default.rb b/cookbooks/nginx/attributes/default.rb new file mode 100644 index 0000000..c3e96fe --- /dev/null +++ b/cookbooks/nginx/attributes/default.rb @@ -0,0 +1,131 @@ +# +# Cookbook Name:: nginx +# Attributes:: default +# +# Author:: Adam Jacob () +# Author:: Joshua Timberman () +# +# Copyright 2009-2013, Chef Software, Inc. +# +# 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. +# + +# In order to update the version, the checksum attribute must be changed too. +# This attribute is in the source.rb file, though we recommend overriding +# attributes by modifying a role, or the node itself. +default['nginx']['version'] = '1.6.2' +default['nginx']['package_name'] = 'nginx' +default['nginx']['port'] = '80' +default['nginx']['dir'] = '/etc/nginx' +default['nginx']['script_dir'] = '/usr/sbin' +default['nginx']['log_dir'] = '/var/log/nginx' +default['nginx']['log_dir_perm'] = '0750' +default['nginx']['binary'] = '/usr/sbin/nginx' +default['nginx']['default_root'] = '/var/www/nginx-default' +default['nginx']['ulimit'] = '1024' + +default['nginx']['pid'] = '/var/run/nginx.pid' + +case node['platform_family'] +when 'debian' + default['nginx']['user'] = 'www-data' + default['nginx']['init_style'] = 'runit' + if platform == 'ubuntu' && platform_version == '14.04' + default['nginx']['pid'] = '/run/nginx.pid' + end +when 'rhel', 'fedora' + default['nginx']['user'] = 'nginx' + default['nginx']['init_style'] = 'init' + default['nginx']['repo_source'] = 'epel' +when 'gentoo' + default['nginx']['user'] = 'nginx' + default['nginx']['init_style'] = 'init' +when 'freebsd' + default['nginx']['package_name'] = 'www/nginx' + default['nginx']['user'] = 'www' + default['nginx']['dir'] = '/usr/local/etc/nginx' + default['nginx']['script_dir'] = '/usr/local/sbin' + default['nginx']['binary'] = '/usr/local/sbin/nginx' + default['nginx']['default_root'] = '/usr/local/www/nginx-dist' +when 'suse' + default['nginx']['user'] = 'wwwrun' + default['nginx']['init_style'] = 'init' + default['nginx']['group'] = 'www' +else + default['nginx']['user'] = 'www-data' + default['nginx']['init_style'] = 'init' +end + +default['nginx']['upstart']['runlevels'] = '2345' +default['nginx']['upstart']['respawn_limit'] = nil +default['nginx']['upstart']['foreground'] = true + +default['nginx']['group'] = node['nginx']['group'] || node['nginx']['user'] + +default['nginx']['gzip'] = 'on' +default['nginx']['gzip_static'] = 'off' +default['nginx']['gzip_http_version'] = '1.0' +default['nginx']['gzip_comp_level'] = '2' +default['nginx']['gzip_proxied'] = 'any' +default['nginx']['gzip_vary'] = 'off' +default['nginx']['gzip_buffers'] = nil +default['nginx']['gzip_types'] = %w( + text/plain + text/css + application/x-javascript + text/xml + application/xml + application/rss+xml + application/atom+xml + text/javascript + application/javascript + application/json + text/mathml +) +default['nginx']['gzip_min_length'] = 1_000 +default['nginx']['gzip_disable'] = 'MSIE [1-6]\.' + +default['nginx']['keepalive'] = 'on' +default['nginx']['keepalive_requests'] = 100 +default['nginx']['keepalive_timeout'] = 65 +default['nginx']['worker_processes'] = node['cpu'] && node['cpu']['total'] ? node['cpu']['total'] : 1 +default['nginx']['worker_connections'] = 1_024 +default['nginx']['worker_rlimit_nofile'] = nil +default['nginx']['multi_accept'] = false +default['nginx']['event'] = nil +default['nginx']['accept_mutex_delay'] = nil +default['nginx']['server_tokens'] = nil +default['nginx']['server_names_hash_bucket_size'] = 64 +default['nginx']['variables_hash_max_size'] = 1024 +default['nginx']['variables_hash_bucket_size'] = 64 +default['nginx']['sendfile'] = 'on' +default['nginx']['underscores_in_headers'] = nil +default['nginx']['tcp_nodelay'] = 'on' +default['nginx']['tcp_nopush'] = 'on' + +default['nginx']['access_log_options'] = nil +default['nginx']['error_log_options'] = nil +default['nginx']['disable_access_log'] = false +default['nginx']['log_formats'] = {} +default['nginx']['install_method'] = 'package' +default['nginx']['default_site_enabled'] = true +default['nginx']['types_hash_max_size'] = 2_048 +default['nginx']['types_hash_bucket_size'] = 64 + +default['nginx']['proxy_read_timeout'] = nil +default['nginx']['client_body_buffer_size'] = nil +default['nginx']['client_max_body_size'] = nil +default['nginx']['large_client_header_buffers'] = nil +default['nginx']['default']['modules'] = [] + +default['nginx']['extra_configs'] = {} diff --git a/cookbooks/nginx/attributes/devel.rb b/cookbooks/nginx/attributes/devel.rb new file mode 100644 index 0000000..cb193a5 --- /dev/null +++ b/cookbooks/nginx/attributes/devel.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: nginx +# Attributes:: devel +# +# Author:: Arthur Freyman () +# +# Copyright 2013, Riot Games +# +# 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. +# + +default['nginx']['devel']['version'] = '0.2.18' +default['nginx']['devel']['url'] = "https://github.com/simpl/ngx_devel_kit/archive/v#{node['nginx']['devel']['version']}.tar.gz" +default['nginx']['devel']['checksum'] = 'c9c9f0a1b068d38c6c45b15d9605f1b2344dbcd45abf0764cd8e2ba92d6a3d2c' diff --git a/cookbooks/nginx/attributes/echo.rb b/cookbooks/nginx/attributes/echo.rb new file mode 100644 index 0000000..bd9f3f1 --- /dev/null +++ b/cookbooks/nginx/attributes/echo.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: nginx +# Attributes:: echo +# +# Author:: Danial Pearce () +# +# Copyright 2013, Danial Pearce +# +# 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. +# + +default['nginx']['echo']['version'] = '0.57' +default['nginx']['echo']['url'] = "https://github.com/openresty/echo-nginx-module/archive/v#{node['nginx']['echo']['version']}.tar.gz" +default['nginx']['echo']['checksum'] = '8467237ca0fae74ca7a32fbd34fc6044df307098415d48068214c9c235695a07' diff --git a/cookbooks/nginx/attributes/geoip.rb b/cookbooks/nginx/attributes/geoip.rb new file mode 100644 index 0000000..c72141b --- /dev/null +++ b/cookbooks/nginx/attributes/geoip.rb @@ -0,0 +1,31 @@ +# +# Cookbook Name:: nginx +# Attributes:: geoip +# +# Author:: Jamie Winsor () +# +# Copyright 2012-2013, Riot Games +# +# 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. +# + +default['nginx']['geoip']['path'] = '/srv/geoip' +default['nginx']['geoip']['enable_city'] = true +default['nginx']['geoip']['country_dat_url'] = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz' +default['nginx']['geoip']['country_dat_checksum'] = '79ff1099e96c2dc1c2539c9a18aaa13a9afd085cae477df60d95f1644d42bc07' +default['nginx']['geoip']['city_dat_url'] = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz' +default['nginx']['geoip']['city_dat_checksum'] = '8a6467033a528f68b1a97de24d9d0ce86c8e8e83683820e16e433ddbd3f712f7' +default['nginx']['geoip']['lib_version'] = '1.6.3' +lib_version = node['nginx']['geoip']['lib_version'] # convenience variable for line length +default['nginx']['geoip']['lib_url'] = "https://github.com/maxmind/geoip-api-c/releases/download/v#{lib_version}/GeoIP-#{lib_version}.tar.gz" +default['nginx']['geoip']['lib_checksum'] = 'e483839a81a91c3c85df89ef409fc7b526c489e0355d537861cfd1ea9534a8f2' diff --git a/cookbooks/nginx/attributes/headers_more.rb b/cookbooks/nginx/attributes/headers_more.rb new file mode 100644 index 0000000..18ec681 --- /dev/null +++ b/cookbooks/nginx/attributes/headers_more.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: nginx +# Attributes:: headers_more +# +# Author:: Lucas Jandrew () +# +# Copyright 2012-2013, Riot Games +# +# 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. +# + +default['nginx']['headers_more']['version'] = '0.25' +default['nginx']['headers_more']['source_url'] = "https://github.com/openresty/headers-more-nginx-module/archive/v#{node['nginx']['headers_more']['version']}.tar.gz" +default['nginx']['headers_more']['source_checksum'] = '1473f96f59dcec9d83ce65d691559993c1f80da8c0a4c0c0a30dae9f969eeabf' diff --git a/cookbooks/nginx/attributes/lua.rb b/cookbooks/nginx/attributes/lua.rb new file mode 100644 index 0000000..effdef9 --- /dev/null +++ b/cookbooks/nginx/attributes/lua.rb @@ -0,0 +1,28 @@ +# +# Cookbook Name:: nginx +# Attributes:: lua +# +# Author:: Arthur Freyman () +# +# Copyright 2013, Riot Games +# +# 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. +# + +default['nginx']['lua']['version'] = '0.8.7' +default['nginx']['lua']['url'] = "https://github.com/chaoslawful/lua-nginx-module/archive/v#{node['nginx']['lua']['version']}.tar.gz" +default['nginx']['lua']['checksum'] = '4b9be3c159b9c884a38e044e07aaf4d06bd2893977d0b0dae02c124d8e907f93' + +default['nginx']['luajit']['version'] = '2.0.2' +default['nginx']['luajit']['url'] = "http://luajit.org/download/LuaJIT-#{node['nginx']['luajit']['version']}.tar.gz" +default['nginx']['luajit']['checksum'] = 'c05202974a5890e777b181908ac237625b499aece026654d7cc33607e3f46c38' diff --git a/cookbooks/nginx/attributes/naxsi.rb b/cookbooks/nginx/attributes/naxsi.rb new file mode 100644 index 0000000..3c03e38 --- /dev/null +++ b/cookbooks/nginx/attributes/naxsi.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: nginx +# Attributes:: naxsi +# +# Author:: Artiom Lunev () +# +# Copyright 2012-2013, Artiom Lunev +# +# 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. +# + +default['nginx']['naxsi']['version'] = '0.53-2' +default['nginx']['naxsi']['url'] = "https://github.com/nbs-system/naxsi/archive/#{node['nginx']['naxsi']['version']}.tar.gz" +default['nginx']['naxsi']['checksum'] = '3eadff1d91995beae41b92733ade28091c2075a24ae37058f4d6aa90b0f4b660' diff --git a/cookbooks/nginx/attributes/openssl_source.rb b/cookbooks/nginx/attributes/openssl_source.rb new file mode 100644 index 0000000..abe29a4 --- /dev/null +++ b/cookbooks/nginx/attributes/openssl_source.rb @@ -0,0 +1,23 @@ +# +# Cookbook Name:: nginx +# Attributes:: openssl_source +# +# Author:: David Radcliffe () +# +# Copyright 2013, David Radcliffe +# +# 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. +# + +default['nginx']['openssl_source']['version'] = '1.0.1h' +default['nginx']['openssl_source']['url'] = "http://www.openssl.org/source/openssl-#{node['nginx']['openssl_source']['version']}.tar.gz" diff --git a/cookbooks/nginx/attributes/pagespeed.rb b/cookbooks/nginx/attributes/pagespeed.rb new file mode 100644 index 0000000..48ca814 --- /dev/null +++ b/cookbooks/nginx/attributes/pagespeed.rb @@ -0,0 +1,9 @@ +# +# Cookbook Name:: nginx +# Recipe:: pagespeed_module +# +default['nginx']['pagespeed']['version'] = '1.8.31.4' +default['nginx']['pagespeed']['url'] = "https://github.com/pagespeed/ngx_pagespeed/archive/release-#{node['nginx']['pagespeed']['version']}-beta.tar.gz" +default['nginx']['psol']['url'] = "https://dl.google.com/dl/page-speed/psol/#{node['nginx']['pagespeed']['version']}.tar.gz" +default['nginx']['pagespeed']['packages']['rhel'] = %w(gcc-c++ pcre-dev pcre-devel zlib-devel make) +default['nginx']['pagespeed']['packages']['debian'] = %w(build-essential zlib1g-dev libpcre3 libpcre3-dev) diff --git a/cookbooks/nginx/attributes/passenger.rb b/cookbooks/nginx/attributes/passenger.rb new file mode 100644 index 0000000..5478284 --- /dev/null +++ b/cookbooks/nginx/attributes/passenger.rb @@ -0,0 +1,58 @@ +# +# Cookbook Name:: nginx +# Attribute:: passenger +# +# Author:: Alex Dergachev () +# +# Copyright 2013, Chef Software, Inc. +# Copyright 2012, Susan Potter +# +# 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. +# + +node.default['nginx']['passenger']['version'] = '4.0.57' + +if node['nginx']['repo_source'] == 'passenger' + node.default['nginx']['passenger']['root'] = '/usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini' + node.default['nginx']['passenger']['ruby'] = '/usr/bin/ruby' +elsif node['languages'].attribute?('ruby') + node.default['nginx']['passenger']['root'] = "#{node['languages']['ruby']['gems_dir']}/gems/passenger-#{node['nginx']['passenger']['version']}" + node.default['nginx']['passenger']['ruby'] = node['languages']['ruby']['ruby_bin'] +else + Chef::Log.warn("node['languages']['ruby'] attribute not detected in #{cookbook_name}::#{recipe_name}") + Chef::Log.warn("Install a Ruby for automatic detection of node['nginx']['passenger'] attributes (root, ruby)") + Chef::Log.warn('Using default values that may or may not work for this system.') + node.default['nginx']['passenger']['root'] = "/usr/lib/ruby/gems/1.8/gems/passenger-#{node['nginx']['passenger']['version']}" + node.default['nginx']['passenger']['ruby'] = '/usr/bin/ruby' +end + +if platform_family?('rhel') && node['platform_version'].to_i >= 6 + node.default['nginx']['passenger']['packages']['rhel'] = %w(ruby-devel libcurl-devel) +else + node.default['nginx']['passenger']['packages']['rhel'] = %w(ruby-devel curl-devel) +end +node.default['nginx']['passenger']['packages']['fedora'] = %w(ruby-devel libcurl-devel) +node.default['nginx']['passenger']['packages']['debian'] = %w(ruby-dev libcurl4-gnutls-dev) + +node.default['nginx']['passenger']['install_rake'] = true +node.default['nginx']['passenger']['spawn_method'] = 'smart-lv2' +node.default['nginx']['passenger']['buffer_response'] = 'on' +node.default['nginx']['passenger']['max_pool_size'] = 6 +node.default['nginx']['passenger']['min_instances'] = 1 +node.default['nginx']['passenger']['max_instances_per_app'] = 0 +node.default['nginx']['passenger']['pool_idle_time'] = 300 +node.default['nginx']['passenger']['max_requests'] = 0 +node.default['nginx']['passenger']['gem_binary'] = nil + +# NodeJs disable by default +node.default['nginx']['passenger']['nodejs'] = nil diff --git a/cookbooks/nginx/attributes/rate_limiting.rb b/cookbooks/nginx/attributes/rate_limiting.rb new file mode 100644 index 0000000..afa0240 --- /dev/null +++ b/cookbooks/nginx/attributes/rate_limiting.rb @@ -0,0 +1,23 @@ +# +# Cookbook Name:: nginx +# Attribute:: rate_limiting +# +# Copyright 2013, Chef Software, Inc. +# +# 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. +# + +default['nginx']['enable_rate_limiting'] = false +default['nginx']['rate_limiting_zone_name'] = 'default' +default['nginx']['rate_limiting_backoff'] = '10m' +default['nginx']['rate_limit'] = '1r/s' diff --git a/cookbooks/nginx/attributes/repo.rb b/cookbooks/nginx/attributes/repo.rb new file mode 100644 index 0000000..2a69ffd --- /dev/null +++ b/cookbooks/nginx/attributes/repo.rb @@ -0,0 +1,35 @@ +# +# Cookbook Name:: nginx +# Recipe:: repo +# +# Author:: Nick Rycar +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +case node['platform_family'] +when 'rhel', 'fedora' + case node['platform'] + when 'centos' + # See http://wiki.nginx.org/Install + default['nginx']['upstream_repository'] = "http://nginx.org/packages/centos/#{node['platform_version'].to_i}/$basearch/" + when 'amazon' + default['nginx']['upstream_repository'] = 'http://nginx.org/packages/rhel/6/$basearch/' + else + default['nginx']['upstream_repository'] = "http://nginx.org/packages/rhel/#{node['platform_version'].to_i}/$basearch/" + end +when 'debian' + default['nginx']['upstream_repository'] = "http://nginx.org/packages/#{node['platform']}" +end diff --git a/cookbooks/nginx/attributes/set_misc.rb b/cookbooks/nginx/attributes/set_misc.rb new file mode 100644 index 0000000..33fb21f --- /dev/null +++ b/cookbooks/nginx/attributes/set_misc.rb @@ -0,0 +1,8 @@ +# +# Cookbook Name:: nginx +# Attributes:: set_misc +# + +default['nginx']['set_misc']['version'] = '0.24' +default['nginx']['set_misc']['url'] = "https://github.com/agentzh/set-misc-nginx-module/archive/v#{node['nginx']['set_misc']['version']}.tar.gz" +default['nginx']['set_misc']['checksum'] = 'da404a7dac5fa4a0a86f42b4ec7648b607f4cd66' diff --git a/cookbooks/nginx/attributes/socketproxy.rb b/cookbooks/nginx/attributes/socketproxy.rb new file mode 100644 index 0000000..17dca60 --- /dev/null +++ b/cookbooks/nginx/attributes/socketproxy.rb @@ -0,0 +1,13 @@ +default['nginx']['socketproxy']['root'] = '/usr/share/nginx/apps' +default['nginx']['socketproxy']['app_owner'] = 'root' +default['nginx']['socketproxy']['logname'] = 'socketproxy' +default['nginx']['socketproxy']['log_level'] = 'error' +# default['nginx']['socketproxy']['default_app'] = 'default' +# default['nginx']['socketproxy']['apps'] = { +# 'default' => { +# 'prepend_slash' => false, +# 'context_name' => '', +# 'subdir' => 'current', +# 'socket_path' => 'shared/sockets/unicorn.sock' +# } +# } diff --git a/cookbooks/nginx/attributes/source.rb b/cookbooks/nginx/attributes/source.rb new file mode 100644 index 0000000..580eccf --- /dev/null +++ b/cookbooks/nginx/attributes/source.rb @@ -0,0 +1,42 @@ +# +# Cookbook Name:: nginx +# Attributes:: source +# +# Author:: Jamie Winsor () +# +# Copyright 2012-2013, Riot Games +# +# 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. +# + +include_attribute 'nginx::default' + +default['nginx']['source']['version'] = node['nginx']['version'] +default['nginx']['source']['prefix'] = "/opt/nginx-#{node['nginx']['source']['version']}" +default['nginx']['source']['conf_path'] = "#{node['nginx']['dir']}/nginx.conf" +default['nginx']['source']['sbin_path'] = "#{node['nginx']['source']['prefix']}/sbin/nginx" +default['nginx']['source']['default_configure_flags'] = %W( + --prefix=#{node['nginx']['source']['prefix']} + --conf-path=#{node['nginx']['dir']}/nginx.conf + --sbin-path=#{node['nginx']['source']['sbin_path']} +) + +default['nginx']['configure_flags'] = [] +default['nginx']['source']['version'] = node['nginx']['version'] +default['nginx']['source']['url'] = "http://nginx.org/download/nginx-#{node['nginx']['source']['version']}.tar.gz" +default['nginx']['source']['checksum'] = 'b5608c2959d3e7ad09b20fc8f9e5bd4bc87b3bc8ba5936a513c04ed8f1391a18' +default['nginx']['source']['modules'] = %w( + nginx::http_ssl_module + nginx::http_gzip_static_module +) +default['nginx']['source']['use_existing_user'] = false diff --git a/cookbooks/nginx/attributes/status.rb b/cookbooks/nginx/attributes/status.rb new file mode 100644 index 0000000..b4ad16b --- /dev/null +++ b/cookbooks/nginx/attributes/status.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: nginx +# Attributes:: status +# +# Author:: David Radcliffe () +# +# Copyright 2013, David Radcliffe +# +# 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. +# + +default['nginx']['status']['port'] = '8090' diff --git a/cookbooks/nginx/attributes/syslog.rb b/cookbooks/nginx/attributes/syslog.rb new file mode 100644 index 0000000..e95ce51 --- /dev/null +++ b/cookbooks/nginx/attributes/syslog.rb @@ -0,0 +1,24 @@ + +# +# Cookbook Name:: nginx +# Attributes:: syslog +# +# Author:: Bob Ziuchkovski () +# +# Copyright 2014, UserTesting +# +# 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. +# + +default['nginx']['syslog']['git_repo'] = 'https://github.com/yaoweibin/nginx_syslog_patch.git' +default['nginx']['syslog']['git_revision'] = 'master' diff --git a/cookbooks/nginx/attributes/upload_progress.rb b/cookbooks/nginx/attributes/upload_progress.rb new file mode 100644 index 0000000..a4e316f --- /dev/null +++ b/cookbooks/nginx/attributes/upload_progress.rb @@ -0,0 +1,26 @@ +# +# Cookbook Name:: nginx +# Attributes:: upload_progress +# +# Author:: Jamie Winsor () +# +# Copyright 2012, Riot Games +# +# 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. +# + +default['nginx']['upload_progress']['url'] = 'https://github.com/masterzen/nginx-upload-progress-module/tarball/v0.9.0' +default['nginx']['upload_progress']['checksum'] = '3fb903dab595cf6656fa0fc5743a48daffbba2f6b5c554836be630800eaad4e2' +default['nginx']['upload_progress']['javascript_output'] = true +default['nginx']['upload_progress']['zone_name'] = 'proxied' +default['nginx']['upload_progress']['zone_size'] = '1m' diff --git a/cookbooks/nginx/definitions/nginx_site.rb b/cookbooks/nginx/definitions/nginx_site.rb new file mode 100644 index 0000000..a0e9e5c --- /dev/null +++ b/cookbooks/nginx/definitions/nginx_site.rb @@ -0,0 +1,50 @@ +# +# Cookbook Name:: nginx +# Definition:: nginx_site +# +# Author:: AJ Christensen +# +# Copyright 2008-2013, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +define :nginx_site, :enable => true, :timing => :delayed do + if params[:enable] + + if params[:template] + template "#{node['nginx']['dir']}/sites-available/#{params[:name]}" do + source params[:template] + variables(params[:variables]) + end + end + + execute "nxensite #{params[:name]}" do + command "#{node['nginx']['script_dir']}/nxensite #{params[:name]}" + notifies :reload, 'service[nginx]', params[:timing] + not_if do + ::File.symlink?("#{node['nginx']['dir']}/sites-enabled/#{params[:name]}") || + ::File.symlink?("#{node['nginx']['dir']}/sites-enabled/000-#{params[:name]}") + end + end + else + execute "nxdissite #{params[:name]}" do + command "#{node['nginx']['script_dir']}/nxdissite #{params[:name]}" + notifies :reload, 'service[nginx]', params[:timing] + only_if do + ::File.symlink?("#{node['nginx']['dir']}/sites-enabled/#{params[:name]}") || + ::File.symlink?("#{node['nginx']['dir']}/sites-enabled/000-#{params[:name]}") + end + end + end +end diff --git a/cookbooks/nginx/files/default/mime.types b/cookbooks/nginx/files/default/mime.types new file mode 100644 index 0000000..6437e2d --- /dev/null +++ b/cookbooks/nginx/files/default/mime.types @@ -0,0 +1,78 @@ +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/json json; + application/atom+xml atom; + application/rss+xml rss; + text/cache.manifest manifest; + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + application/java-archive jar war ear; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.ms-excel xls; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/zip zip; + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + font/ttf ttf; + font/opentype otf; + application/x-font-woff woff; + application/vnd.ms-fontobject eot; + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + video/3gpp 3gpp 3gp; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/cookbooks/nginx/files/default/naxsi_core.rules b/cookbooks/nginx/files/default/naxsi_core.rules new file mode 100644 index 0000000..8a8800a --- /dev/null +++ b/cookbooks/nginx/files/default/naxsi_core.rules @@ -0,0 +1,82 @@ +################################## +## INTERNAL RULES IDS:1-10 ## +################################## +#weird_request : 1 +#big_body : 2 +#no_content_type : 3 + +#@MainRule "msg:weird/incorrect request" id:1; +#@MainRule "msg:big request, unparsed" id:2; +#@MainRule "msg:uncommon hex encoding (%00 etc.)" id:10; +#@MainRule "msg:uncommon/empty content-type in POST" id:11; +#@MainRule "msg:uncommon/malformed URL" id:12; + +#MainRule "str:123FREETEXT" "msg:exemple learning test pattern" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:BLOCK" id:0; + +################################## +## SQL Injections IDs:1000-1099 ## +################################## +MainRule "rx:select|union|update|delete|insert|table|from|ascii|hex|unhex|drop" "msg:sql keywords" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1000; +MainRule "str:\"" "msg:double quote" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8,$XSS:8" id:1001; +MainRule "str:0x" "msg:0x, possible hex encoding" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:2" id:1002; +## Hardcore rules +MainRule "str:/*" "msg:mysql comment (/*)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1003; +MainRule "str:*/" "msg:mysql comment (*/)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1004; +MainRule "str:|" "msg:mysql keyword (|)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1005; +MainRule "str:&&" "msg:mysql keyword (&&)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1006; +## end of hardcore rules +MainRule "str:--" "msg:mysql comment (--)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1007; +MainRule "str:;" "msg:; in stuff" "mz:BODY|URL|ARGS" "s:$SQL:4,$XSS:8" id:1008; +MainRule "str:=" "msg:equal in var, probable sql/xss" "mz:ARGS|BODY" "s:$SQL:2" id:1009; +MainRule "str:(" "msg:parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4,$XSS:8" id:1010; +MainRule "str:)" "msg:parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4,$XSS:8" id:1011; +MainRule "str:'" "msg:simple quote" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$SQL:4,$XSS:8" id:1013; +MainRule "str:," "msg:, in stuff" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1015; +MainRule "str:#" "msg:mysql comment (#)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1016; + +############################### +## OBVIOUS RFI IDs:1100-1199 ## +############################### +MainRule "str:http://" "msg:http:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1100; +MainRule "str:https://" "msg:https:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1101; +MainRule "str:ftp://" "msg:ftp:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1102; +MainRule "str:php://" "msg:php:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1103; +MainRule "str:sftp://" "msg:sftp:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1104; +MainRule "str:zlib://" "msg:zlib:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1105; +MainRule "str:data://" "msg:data:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1106; +MainRule "str:glob://" "msg:glob:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1107; +MainRule "str:phar://" "msg:phar:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1108; +MainRule "str:file://" "msg:file:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1109; + +####################################### +## Directory traversal IDs:1200-1299 ## +####################################### +MainRule "str:.." "msg:double dot" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1200; +MainRule "str:/etc/passwd" "msg:obvious probe" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1202; +MainRule "str:c:\\" "msg:obvious windows path" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1203; +MainRule "str:cmd.exe" "msg:obvious probe" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1204; +MainRule "str:\\" "msg:backslash" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1205; +#MainRule "str:/" "msg:slash in args" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:2" id:1206; + +######################################## +## Cross Site Scripting IDs:1300-1399 ## +######################################## +MainRule "str:<" "msg:html open tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1302; +MainRule "str:>" "msg:html close tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1303; +MainRule "str:[" "msg:[, possible js" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1310; +MainRule "str:]" "msg:], possible js" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1311; +MainRule "str:~" "msg:~ character" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1312; +MainRule "str:`" "msg:grave accent !" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1314; +MainRule "rx:%[2|3]." "msg:double encoding !" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1315; + +#################################### +## Evading tricks IDs: 1400-1500 ## +#################################### +MainRule "str:&#" "msg: utf7/8 encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1400; +MainRule "str:%U" "msg: M$ encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1401; +MainRule negative "rx:multipart/form-data|application/x-www-form-urlencoded" "msg:Content is neither mulipart/x-www-form.." "mz:$HEADERS_VAR:Content-type" "s:$EVADE:4" id:1402; + +############################# +## File uploads: 1500-1600 ## +############################# +MainRule "rx:.ph|.asp|.ht" "msg:asp/php file upload!" "mz:FILE_EXT" "s:$UPLOAD:8" id:1500; diff --git a/cookbooks/nginx/libraries/matchers.rb b/cookbooks/nginx/libraries/matchers.rb new file mode 100644 index 0000000..a72f01e --- /dev/null +++ b/cookbooks/nginx/libraries/matchers.rb @@ -0,0 +1,20 @@ +if defined?(ChefSpec) + # Custom ChefSpec matchers + module ChefSpec::Matchers + RSpec::Matchers.define :enable_nginx_site do |site| + match do |chef_run| + chef_run.resource_collection.all_resources.any? do |resource| + resource.resource_name == :execute && resource.name =~ /.*nxensite.*#{site}/ + end + end + end + + RSpec::Matchers.define :disable_nginx_site do |site| + match do |chef_run| + chef_run.resource_collection.all_resources.any? do |resource| + resource.resource_name == :execute && resource.name =~ /.*nxdissite.*#{site}/ + end + end + end + end +end diff --git a/cookbooks/nginx/metadata.json b/cookbooks/nginx/metadata.json new file mode 100644 index 0000000..0daff0e --- /dev/null +++ b/cookbooks/nginx/metadata.json @@ -0,0 +1,351 @@ +{ + "name": "nginx", + "description": "Installs and configures nginx", + "long_description": "", + "maintainer": "Chef Software, Inc.", + "maintainer_email": "cookbooks@chef.io", + "license": "Apache 2.0", + "platforms": { + "amazon": ">= 0.0.0", + "centos": ">= 0.0.0", + "debian": ">= 0.0.0", + "fedora": ">= 0.0.0", + "oracle": ">= 0.0.0", + "redhat": ">= 0.0.0", + "scientific": ">= 0.0.0", + "ubuntu": ">= 0.0.0" + }, + "dependencies": { + "apt": "~> 2.2", + "bluepill": "~> 2.3", + "build-essential": "~> 2.0", + "ohai": "~> 2.0", + "runit": "~> 1.2", + "yum-epel": "~> 0.3" + }, + "recommendations": { + + }, + "suggestions": { + + }, + "conflicting": { + + }, + "providing": { + + }, + "replacing": { + + }, + "attributes": { + "nginx/dir": { + "display_name": "Nginx Directory", + "description": "Location of nginx configuration files", + "default": "/etc/nginx", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/log_dir": { + "display_name": "Nginx Log Directory", + "description": "Location for nginx logs", + "default": "/var/log/nginx", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/user": { + "display_name": "Nginx User", + "description": "User nginx will run as", + "default": "www-data", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/binary": { + "display_name": "Nginx Binary", + "description": "Location of the nginx server binary", + "default": "/usr/sbin/nginx", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/gzip": { + "display_name": "Nginx Gzip", + "description": "Whether gzip is enabled", + "default": "on", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/gzip_http_version": { + "display_name": "Nginx Gzip HTTP Version", + "description": "Version of HTTP Gzip", + "default": "1.0", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/gzip_comp_level": { + "display_name": "Nginx Gzip Compression Level", + "description": "Amount of compression to use", + "default": "2", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/gzip_proxied": { + "display_name": "Nginx Gzip Proxied", + "description": "Whether gzip is proxied", + "default": "any", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/gzip_types": { + "display_name": "Nginx Gzip Types", + "description": "Supported MIME-types for gzip", + "type": "array", + "default": [ + "text/plain", + "text/css", + "application/x-javascript", + "text/xml", + "application/xml", + "application/xml+rss", + "text/javascript", + "application/javascript", + "application/json" + ], + "choice": [ + + ], + "calculated": false, + "required": "optional", + "recipes": [ + + ] + }, + "nginx/keepalive": { + "display_name": "Nginx Keepalive", + "description": "Whether to enable keepalive", + "default": "on", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/keepalive_timeout": { + "display_name": "Nginx Keepalive Timeout", + "default": "65", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/worker_processes": { + "display_name": "Nginx Worker Processes", + "description": "Number of worker processes", + "default": "1", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/worker_connections": { + "display_name": "Nginx Worker Connections", + "description": "Number of connections per worker", + "default": "1024", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/server_names_hash_bucket_size": { + "display_name": "Nginx Server Names Hash Bucket Size", + "default": "64", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/types_hash_max_size": { + "display_name": "Nginx Types Hash Max Size", + "default": "2048", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/types_hash_bucket_size": { + "display_name": "Nginx Types Hash Bucket Size", + "default": "64", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/disable_access_log": { + "display_name": "Disable Access Log", + "default": "false", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/default_site_enabled": { + "display_name": "Default site enabled", + "default": "true", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/sendfile": { + "display_name": "Nginx sendfile", + "description": "Whether to enable sendfile", + "default": "on", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/tcp_nopush": { + "display_name": "Nginx tcp_nopush", + "description": "Whether to enable tcp_nopush", + "default": "on", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "nginx/tcp_nodelay": { + "display_name": "Nginx tcp_nodelay", + "description": "Whether to enable tcp_nodelay", + "default": "on", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + } + }, + "groupings": { + + }, + "recipes": { + "nginx": "Installs nginx package and sets up configuration with Debian apache style with sites-enabled/sites-available", + "nginx::source": "Installs nginx from source and sets up configuration with Debian apache style with sites-enabled/sites-available" + }, + "version": "2.7.6", + "source_url": "", + "issues_url": "" +} diff --git a/cookbooks/nginx/metadata.rb b/cookbooks/nginx/metadata.rb new file mode 100644 index 0000000..7a66a31 --- /dev/null +++ b/cookbooks/nginx/metadata.rb @@ -0,0 +1,125 @@ +name 'nginx' +maintainer 'Chef Software, Inc.' +maintainer_email 'cookbooks@chef.io' +license 'Apache 2.0' +description 'Installs and configures nginx' +version '2.7.6' + +recipe 'nginx', 'Installs nginx package and sets up configuration with Debian apache style with sites-enabled/sites-available' +recipe 'nginx::source', 'Installs nginx from source and sets up configuration with Debian apache style with sites-enabled/sites-available' + +depends 'apt', '~> 2.2' +depends 'bluepill', '~> 2.3' +depends 'build-essential', '~> 2.0' +depends 'ohai', '~> 2.0' +depends 'runit', '~> 1.2' +depends 'yum-epel', '~> 0.3' + +supports 'amazon' +supports 'centos' +supports 'debian' +supports 'fedora' +supports 'oracle' +supports 'redhat' +supports 'scientific' +supports 'ubuntu' + +attribute 'nginx/dir', + :display_name => 'Nginx Directory', + :description => 'Location of nginx configuration files', + :default => '/etc/nginx' + +attribute 'nginx/log_dir', + :display_name => 'Nginx Log Directory', + :description => 'Location for nginx logs', + :default => '/var/log/nginx' + +attribute 'nginx/user', + :display_name => 'Nginx User', + :description => 'User nginx will run as', + :default => 'www-data' + +attribute 'nginx/binary', + :display_name => 'Nginx Binary', + :description => 'Location of the nginx server binary', + :default => '/usr/sbin/nginx' + +attribute 'nginx/gzip', + :display_name => 'Nginx Gzip', + :description => 'Whether gzip is enabled', + :default => 'on' + +attribute 'nginx/gzip_http_version', + :display_name => 'Nginx Gzip HTTP Version', + :description => 'Version of HTTP Gzip', + :default => '1.0' + +attribute 'nginx/gzip_comp_level', + :display_name => 'Nginx Gzip Compression Level', + :description => 'Amount of compression to use', + :default => '2' + +attribute 'nginx/gzip_proxied', + :display_name => 'Nginx Gzip Proxied', + :description => 'Whether gzip is proxied', + :default => 'any' + +attribute 'nginx/gzip_types', + :display_name => 'Nginx Gzip Types', + :description => 'Supported MIME-types for gzip', + :type => 'array', + :default => ['text/plain', 'text/css', 'application/x-javascript', 'text/xml', 'application/xml', 'application/xml+rss', 'text/javascript', 'application/javascript', 'application/json'] + +attribute 'nginx/keepalive', + :display_name => 'Nginx Keepalive', + :description => 'Whether to enable keepalive', + :default => 'on' + +attribute 'nginx/keepalive_timeout', + :display_name => 'Nginx Keepalive Timeout', + :default => '65' + +attribute 'nginx/worker_processes', + :display_name => 'Nginx Worker Processes', + :description => 'Number of worker processes', + :default => '1' + +attribute 'nginx/worker_connections', + :display_name => 'Nginx Worker Connections', + :description => 'Number of connections per worker', + :default => '1024' + +attribute 'nginx/server_names_hash_bucket_size', + :display_name => 'Nginx Server Names Hash Bucket Size', + :default => '64' + +attribute 'nginx/types_hash_max_size', + :display_name => 'Nginx Types Hash Max Size', + :default => '2048' + +attribute 'nginx/types_hash_bucket_size', + :display_name => 'Nginx Types Hash Bucket Size', + :default => '64' + +attribute 'nginx/disable_access_log', + :display_name => 'Disable Access Log', + :default => 'false' + +attribute 'nginx/default_site_enabled', + :display_name => 'Default site enabled', + :default => 'true' + +attribute 'nginx/sendfile', + :display_name => 'Nginx sendfile', + :description => 'Whether to enable sendfile', + :default => 'on' + +attribute 'nginx/tcp_nopush', + :display_name => 'Nginx tcp_nopush', + :description => 'Whether to enable tcp_nopush', + :default => 'on' + +attribute 'nginx/tcp_nodelay', + :display_name => 'Nginx tcp_nodelay', + :description => 'Whether to enable tcp_nodelay', + :default => 'on' diff --git a/cookbooks/nginx/recipes/authorized_ips.rb b/cookbooks/nginx/recipes/authorized_ips.rb new file mode 100644 index 0000000..d6949ae --- /dev/null +++ b/cookbooks/nginx/recipes/authorized_ips.rb @@ -0,0 +1,32 @@ +# +# Cookbook Name:: nginx +# Recipe:: authorized_ips +# +# Author:: Jamie Winsor () +# +# Copyright 2012-2013, Riot Games +# +# 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. +# + +node.default['nginx']['remote_ip_var'] = 'remote_addr' +node.default['nginx']['authorized_ips'] = ['127.0.0.1/32'] + +template 'authorized_ip' do + path "#{node['nginx']['dir']}/authorized_ip" + source 'modules/authorized_ip.erb' + owner 'root' + group node['root_group'] + mode '0644' + notifies :reload, 'service[nginx]', :delayed +end diff --git a/cookbooks/nginx/recipes/commons.rb b/cookbooks/nginx/recipes/commons.rb new file mode 100644 index 0000000..0492bb0 --- /dev/null +++ b/cookbooks/nginx/recipes/commons.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: nginx +# Recipe:: commons +# +# Author:: AJ Christensen +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +include_recipe 'nginx::commons_dir' +include_recipe 'nginx::commons_script' +include_recipe 'nginx::commons_conf' diff --git a/cookbooks/nginx/recipes/commons_conf.rb b/cookbooks/nginx/recipes/commons_conf.rb new file mode 100644 index 0000000..fccd470 --- /dev/null +++ b/cookbooks/nginx/recipes/commons_conf.rb @@ -0,0 +1,42 @@ +# +# Cookbook Name:: nginx +# Recipe:: common/conf +# +# Author:: AJ Christensen +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +template 'nginx.conf' do + path "#{node['nginx']['dir']}/nginx.conf" + source node['nginx']['conf_template'] + cookbook node['nginx']['conf_cookbook'] + owner 'root' + group node['root_group'] + mode '0644' + notifies :reload, 'service[nginx]', :delayed +end + +template "#{node['nginx']['dir']}/sites-available/default" do + source 'default-site.erb' + owner 'root' + group node['root_group'] + mode '0644' + notifies :reload, 'service[nginx]', :delayed +end + +nginx_site 'default' do + enable node['nginx']['default_site_enabled'] +end diff --git a/cookbooks/nginx/recipes/commons_dir.rb b/cookbooks/nginx/recipes/commons_dir.rb new file mode 100644 index 0000000..bfad3f7 --- /dev/null +++ b/cookbooks/nginx/recipes/commons_dir.rb @@ -0,0 +1,57 @@ +# +# Cookbook Name:: nginx +# Recipe:: common/dir +# +# Author:: AJ Christensen +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +directory node['nginx']['dir'] do + owner 'root' + group node['root_group'] + mode '0755' + recursive true +end + +directory node['nginx']['log_dir'] do + mode node['nginx']['log_dir_perm'] + owner node['nginx']['user'] + action :create + recursive true +end + +directory File.dirname(node['nginx']['pid']) do + owner 'root' + group node['root_group'] + mode '0755' + recursive true +end + +%w(sites-available sites-enabled conf.d).each do |leaf| + directory File.join(node['nginx']['dir'], leaf) do + owner 'root' + group node['root_group'] + mode '0755' + end +end + +if !node['nginx']['default_site_enabled'] && (node['platform_family'] == 'rhel' || node['platform_family'] == 'fedora') + %w(default.conf example_ssl.conf).each do |config| + file "/etc/nginx/conf.d/#{config}" do + action :delete + end + end +end diff --git a/cookbooks/nginx/recipes/commons_script.rb b/cookbooks/nginx/recipes/commons_script.rb new file mode 100644 index 0000000..324d374 --- /dev/null +++ b/cookbooks/nginx/recipes/commons_script.rb @@ -0,0 +1,29 @@ +# +# Cookbook Name:: nginx +# Recipe:: common/script +# +# Author:: AJ Christensen +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +%w(nxensite nxdissite).each do |nxscript| + template "#{node['nginx']['script_dir']}/#{nxscript}" do + source "#{nxscript}.erb" + mode '0755' + owner 'root' + group node['root_group'] + end +end diff --git a/cookbooks/nginx/recipes/default.rb b/cookbooks/nginx/recipes/default.rb new file mode 100644 index 0000000..3f75eec --- /dev/null +++ b/cookbooks/nginx/recipes/default.rb @@ -0,0 +1,31 @@ +# +# Cookbook Name:: nginx +# Recipe:: default +# +# Author:: AJ Christensen +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +include_recipe "nginx::#{node['nginx']['install_method']}" + +service 'nginx' do + supports :status => true, :restart => true, :reload => true + action :start +end + +node['nginx']['default']['modules'].each do |ngx_module| + include_recipe "nginx::#{ngx_module}" +end diff --git a/cookbooks/nginx/recipes/headers_more_module.rb b/cookbooks/nginx/recipes/headers_more_module.rb new file mode 100644 index 0000000..08c586c --- /dev/null +++ b/cookbooks/nginx/recipes/headers_more_module.rb @@ -0,0 +1,50 @@ +# +# Cookbook Name:: nginx +# Recipe:: headers_more_module +# +# Author:: Lucas Jandrew () +# +# Copyright 2012-2013, Riot Games +# +# 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. + +tar_location = "#{Chef::Config['file_cache_path']}/headers_more.tar.gz" +module_location = "#{Chef::Config['file_cache_path']}/headers_more/#{node['nginx']['headers_more']['source_checksum']}" + +remote_file tar_location do + source node['nginx']['headers_more']['source_url'] + checksum node['nginx']['headers_more']['source_checksum'] + owner 'root' + group node['root_group'] + mode '0644' +end + +directory module_location do + owner 'root' + group node['root_group'] + mode '0755' + recursive true + action :create +end + +bash 'extract_headers_more' do + cwd ::File.dirname(tar_location) + user 'root' + code <<-EOH + tar -zxf #{tar_location} -C #{module_location} + EOH + not_if { ::File.exist?("#{module_location}/headers-more-nginx-module-#{node['nginx']['headers_more']['version']}/config") } +end + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ["--add-module=#{module_location}/headers-more-nginx-module-#{node['nginx']['headers_more']['version']}/"] diff --git a/cookbooks/nginx/recipes/http_auth_request_module.rb b/cookbooks/nginx/recipes/http_auth_request_module.rb new file mode 100644 index 0000000..5283ac5 --- /dev/null +++ b/cookbooks/nginx/recipes/http_auth_request_module.rb @@ -0,0 +1,52 @@ +# +# Cookbook Name:: nginx +# Recipe:: http_auth_request_module +# +# Author:: David Radcliffe () +# +# Copyright 2013, David Radcliffe +# +# 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. +# + +# Documentation: +# http://nginx.org/en/docs/http/ngx_http_auth_request_module.html +if node['nginx']['source']['version'] >= '1.5.4' + node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ['--with-http_auth_request_module'] +else + arm_src_filename = ::File.basename(node['nginx']['auth_request']['url']) + arm_src_filepath = "#{Chef::Config['file_cache_path']}/#{arm_src_filename}" + arm_extract_path = "#{Chef::Config['file_cache_path']}/nginx_auth_request/#{node['nginx']['auth_request']['checksum']}" + + remote_file arm_src_filepath do + source node['nginx']['auth_request']['url'] + checksum node['nginx']['auth_request']['checksum'] + owner 'root' + group node['root_group'] + mode '0644' + end + + bash 'extract_auth_request_module' do + cwd ::File.dirname(arm_src_filepath) + code <<-EOH + mkdir -p #{arm_extract_path} + tar xzf #{arm_src_filename} -C #{arm_extract_path} + mv #{arm_extract_path}/*/* #{arm_extract_path}/ + EOH + not_if { ::File.exist?(arm_extract_path) } + end + + node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ["--add-module=#{arm_extract_path}"] +end diff --git a/cookbooks/nginx/recipes/http_echo_module.rb b/cookbooks/nginx/recipes/http_echo_module.rb new file mode 100644 index 0000000..85c2861 --- /dev/null +++ b/cookbooks/nginx/recipes/http_echo_module.rb @@ -0,0 +1,46 @@ +# +# Cookbook Name:: nginx +# Recipe:: http_echo_module +# +# Author:: Danial Pearce () +# +# Copyright 2012-2013, CushyCMS +# +# 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. +# + +echo_src_filename = "echo-nginx-module-v#{node['nginx']['echo']['version']}.tar.gz" +echo_src_filepath = "#{Chef::Config['file_cache_path']}/#{echo_src_filename}" +echo_extract_path = "#{Chef::Config['file_cache_path']}/nginx_echo_module/#{node['nginx']['echo']['checksum']}" + +remote_file echo_src_filepath do + source node['nginx']['echo']['url'] + checksum node['nginx']['echo']['checksum'] + owner 'root' + group node['root_group'] + mode '0644' +end + +bash 'extract_http_echo_module' do + cwd ::File.dirname(echo_src_filepath) + code <<-EOH + mkdir -p #{echo_extract_path} + tar xzf #{echo_src_filename} -C #{echo_extract_path} + mv #{echo_extract_path}/*/* #{echo_extract_path}/ + EOH + + not_if { ::File.exist?(echo_extract_path) } +end + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ["--add-module=#{echo_extract_path}"] diff --git a/cookbooks/nginx/recipes/http_geoip_module.rb b/cookbooks/nginx/recipes/http_geoip_module.rb new file mode 100644 index 0000000..f61b711 --- /dev/null +++ b/cookbooks/nginx/recipes/http_geoip_module.rb @@ -0,0 +1,113 @@ +# +# Cookbook Name:: nginx +# Recipe:: http_geoip_module +# +# Author:: Jamie Winsor () +# +# Copyright 2012-2013, Riot Games +# +# 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. +# + +country_dat = "#{node['nginx']['geoip']['path']}/GeoIP.dat" +country_src_filename = ::File.basename(node['nginx']['geoip']['country_dat_url']) +country_src_filepath = "#{Chef::Config['file_cache_path']}/#{country_src_filename}" +city_dat = nil +city_src_filename = ::File.basename(node['nginx']['geoip']['city_dat_url']) +city_src_filepath = "#{Chef::Config['file_cache_path']}/#{city_src_filename}" +geolib_filename = ::File.basename(node['nginx']['geoip']['lib_url']) +geolib_filepath = "#{Chef::Config['file_cache_path']}/#{geolib_filename}" + +remote_file geolib_filepath do + source node['nginx']['geoip']['lib_url'] + checksum node['nginx']['geoip']['lib_checksum'] + owner 'root' + group node['root_group'] + mode '0644' +end + +bash 'extract_geolib' do + cwd ::File.dirname(geolib_filepath) + code <<-EOH + tar xzvf #{geolib_filepath} -C #{::File.dirname(geolib_filepath)} + cd GeoIP-#{node['nginx']['geoip']['lib_version']} + ./configure + make && make install + EOH + environment('echo' => 'echo') if node['platform_family'] == 'rhel' && node['platform_version'].to_f < 6 + creates "/usr/local/lib/libGeoIP.so.#{node['nginx']['geoip']['lib_version']}" + subscribes :run, "remote_file[#{geolib_filepath}]" +end + +directory node['nginx']['geoip']['path'] do + owner 'root' + group node['root_group'] + mode '0755' + recursive true +end + +remote_file country_src_filepath do + not_if do + File.exist?(country_src_filepath) && + File.mtime(country_src_filepath) > Time.now - 86_400 + end + source node['nginx']['geoip']['country_dat_url'] + checksum node['nginx']['geoip']['country_dat_checksum'] + owner 'root' + group node['root_group'] + mode '0644' +end + +bash 'gunzip_geo_lite_country_dat' do + code <<-EOH + gunzip -c "#{country_src_filepath}" > #{country_dat} + EOH + creates country_dat +end + +if node['nginx']['geoip']['enable_city'] + city_dat = "#{node['nginx']['geoip']['path']}/GeoLiteCity.dat" + + remote_file city_src_filepath do + not_if do + File.exist?(city_src_filepath) && + File.mtime(city_src_filepath) > Time.now - 86_400 + end + source node['nginx']['geoip']['city_dat_url'] + checksum node['nginx']['geoip']['city_dat_checksum'] + owner 'root' + group node['root_group'] + mode '0644' + end + + bash 'gunzip_geo_lite_city_dat' do + code <<-EOH + gunzip -c "#{city_src_filepath}" > #{city_dat} + EOH + creates city_dat + end +end + +template "#{node['nginx']['dir']}/conf.d/http_geoip.conf" do + source 'modules/http_geoip.conf.erb' + owner 'root' + group node['root_group'] + mode '0644' + variables( + :country_dat => country_dat, + :city_dat => city_dat + ) +end + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ['--with-http_geoip_module', "--with-ld-opt='-Wl,-R,/usr/local/lib -L /usr/local/lib'"] diff --git a/cookbooks/nginx/recipes/http_gzip_static_module.rb b/cookbooks/nginx/recipes/http_gzip_static_module.rb new file mode 100644 index 0000000..4607c35 --- /dev/null +++ b/cookbooks/nginx/recipes/http_gzip_static_module.rb @@ -0,0 +1,30 @@ +# +# Cookbook Name:: nginx +# Recipe:: http_gzip_static_module +# +# Author:: Jamie Winsor () +# +# Copyright 2012-2013, Riot Games +# +# 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. +# + +template "#{node['nginx']['dir']}/conf.d/http_gzip_static.conf" do + source 'modules/http_gzip_static.conf.erb' + owner 'root' + group node['root_group'] + mode '0644' +end + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ['--with-http_gzip_static_module'] diff --git a/cookbooks/nginx/recipes/http_mp4_module.rb b/cookbooks/nginx/recipes/http_mp4_module.rb new file mode 100644 index 0000000..555f597 --- /dev/null +++ b/cookbooks/nginx/recipes/http_mp4_module.rb @@ -0,0 +1,2 @@ +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ['--with-http_mp4_module'] diff --git a/cookbooks/nginx/recipes/http_perl_module.rb b/cookbooks/nginx/recipes/http_perl_module.rb new file mode 100644 index 0000000..e4f55d9 --- /dev/null +++ b/cookbooks/nginx/recipes/http_perl_module.rb @@ -0,0 +1,23 @@ +# +# Cookbook Name:: nginx +# Recipe:: http_perl_module +# +# Author:: Akzhan Abdulin () +# +# Copyright 2012-2013, REG.RU +# +# 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. +# + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ['--with-http_perl_module'] diff --git a/cookbooks/nginx/recipes/http_realip_module.rb b/cookbooks/nginx/recipes/http_realip_module.rb new file mode 100644 index 0000000..6451bf9 --- /dev/null +++ b/cookbooks/nginx/recipes/http_realip_module.rb @@ -0,0 +1,38 @@ +# +# Cookbook Name:: nginx +# Recipe:: http_realip_module +# +# Author:: Jamie Winsor () +# +# Copyright 2012-2013, Riot Games +# +# 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. +# + +# Documentation: http://wiki.nginx.org/HttpRealIpModule + +# Currently only accepts X-Forwarded-For or X-Real-IP +node.default['nginx']['realip']['header'] = 'X-Forwarded-For' +node.default['nginx']['realip']['addresses'] = ['127.0.0.1'] +node.default['nginx']['realip']['real_ip_recursive'] = 'off' + +template "#{node['nginx']['dir']}/conf.d/http_realip.conf" do + source 'modules/http_realip.conf.erb' + owner 'root' + group node['root_group'] + mode '0644' + notifies :reload, 'service[nginx]', :delayed +end + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ['--with-http_realip_module'] diff --git a/cookbooks/nginx/recipes/http_spdy_module.rb b/cookbooks/nginx/recipes/http_spdy_module.rb new file mode 100644 index 0000000..1eafa9b --- /dev/null +++ b/cookbooks/nginx/recipes/http_spdy_module.rb @@ -0,0 +1,23 @@ +# +# Cookbook Name:: nginx +# Recipe:: http_spdy_module +# +# Author:: Christoph Buente () +# +# Copyright 2013, MeinekleineFarm.org +# +# 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. +# + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ['--with-http_spdy_module'] diff --git a/cookbooks/nginx/recipes/http_ssl_module.rb b/cookbooks/nginx/recipes/http_ssl_module.rb new file mode 100644 index 0000000..6ff4f7c --- /dev/null +++ b/cookbooks/nginx/recipes/http_ssl_module.rb @@ -0,0 +1,23 @@ +# +# Cookbook Name:: nginx +# Recipe:: http_ssl_module +# +# Author:: Jamie Winsor () +# +# Copyright 2012-2013, Riot Games +# +# 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. +# + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ['--with-http_ssl_module'] diff --git a/cookbooks/nginx/recipes/http_stub_status_module.rb b/cookbooks/nginx/recipes/http_stub_status_module.rb new file mode 100644 index 0000000..c07243c --- /dev/null +++ b/cookbooks/nginx/recipes/http_stub_status_module.rb @@ -0,0 +1,36 @@ +# +# Cookbook Name:: nginx +# Recipe:: http_stub_status_module +# +# Author:: Jamie Winsor () +# +# Copyright 2012-2013, Riot Games +# +# 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. +# + +include_recipe 'nginx::authorized_ips' + +template 'nginx_status' do + path "#{node['nginx']['dir']}/sites-available/nginx_status" + source 'modules/nginx_status.erb' + owner 'root' + group node['root_group'] + mode '0644' + notifies :reload, 'service[nginx]', :delayed +end + +nginx_site 'nginx_status' + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ['--with-http_stub_status_module'] diff --git a/cookbooks/nginx/recipes/ipv6.rb b/cookbooks/nginx/recipes/ipv6.rb new file mode 100644 index 0000000..2c67601 --- /dev/null +++ b/cookbooks/nginx/recipes/ipv6.rb @@ -0,0 +1,23 @@ +# +# Cookbook Name:: nginx +# Recipe:: ipv6 +# +# Author:: Alan Harper (alan@sct.com.au) +# +# Copyright 2013 Alan Harper +# +# 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. +# + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ['--with-ipv6'] diff --git a/cookbooks/nginx/recipes/lua.rb b/cookbooks/nginx/recipes/lua.rb new file mode 100644 index 0000000..9526389 --- /dev/null +++ b/cookbooks/nginx/recipes/lua.rb @@ -0,0 +1,47 @@ +# +# Cookbook Name:: nginx +# Recipe:: default +# +# Copyright 2013, Chef Software, Inc. +# +# 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. +# + +luajit_src_filename = ::File.basename(node['nginx']['luajit']['url']) +luajit_src_filepath = "#{Chef::Config['file_cache_path']}/#{luajit_src_filename}" +luajit_extract_path = "#{Chef::Config['file_cache_path']}/luajit-#{node['nginx']['luajit']['version']}" + +remote_file luajit_src_filepath do + source node['nginx']['luajit']['url'] + checksum node['nginx']['luajit']['checksum'] + owner 'root' + group node['root_group'] + mode '0644' +end + +bash 'extract_luajit' do + cwd ::File.dirname(luajit_src_filepath) + code <<-EOH + mkdir -p #{luajit_extract_path} + tar xzf #{luajit_src_filename} -C #{luajit_extract_path} + cd luajit-#{node['nginx']['luajit']['version']}/LuaJIT-#{node['nginx']['luajit']['version']} + make && make install + export LUAJIT_INC="/usr/local/include/luajit-2.0" + export LUAJIT_LIB="usr/local/lib" + EOH + not_if { ::File.exist?(luajit_extract_path) } +end + +package 'lua-devel' do + action :install +end diff --git a/cookbooks/nginx/recipes/naxsi_module.rb b/cookbooks/nginx/recipes/naxsi_module.rb new file mode 100644 index 0000000..063f537 --- /dev/null +++ b/cookbooks/nginx/recipes/naxsi_module.rb @@ -0,0 +1,52 @@ +# +# Cookbook Name:: nginx +# Recipe:: naxsi_module +# +# Author:: Artiom Lunev () +# +# Copyright 2012-2013, Artiom Lunev +# +# 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. +# + +cookbook_file "#{node['nginx']['dir']}/naxsi_core.rules" do + source 'naxsi_core.rules' + owner 'root' + group node['root_group'] + mode '0644' + notifies :reload, 'service[nginx]', :delayed +end + +naxsi_src_filename = ::File.basename(node['nginx']['naxsi']['url']) +naxsi_src_filepath = "#{Chef::Config['file_cache_path']}/#{naxsi_src_filename}" +naxsi_extract_path = "#{Chef::Config['file_cache_path']}/nginx-naxsi-#{node['nginx']['naxsi']['version']}" + +remote_file naxsi_src_filepath do + source node['nginx']['naxsi']['url'] + checksum node['nginx']['naxsi']['checksum'] + owner 'root' + group node['root_group'] + mode '0644' +end + +bash 'extract_naxsi_module' do + cwd ::File.dirname(naxsi_src_filepath) + code <<-EOH + mkdir -p #{naxsi_extract_path} + tar xzf #{naxsi_src_filename} -C #{naxsi_extract_path} + EOH + not_if { ::File.exist?(naxsi_extract_path) } +end + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ["--add-module=#{naxsi_extract_path}/naxsi-#{node['nginx']['naxsi']['version']}/naxsi_src"] diff --git a/cookbooks/nginx/recipes/ngx_devel_module.rb b/cookbooks/nginx/recipes/ngx_devel_module.rb new file mode 100644 index 0000000..3c15c54 --- /dev/null +++ b/cookbooks/nginx/recipes/ngx_devel_module.rb @@ -0,0 +1,44 @@ +# +# Cookbook Name:: nginx +# Recipes:: devel +# +# Author:: Arthur Freyman () +# +# Copyright 2013, Riot Games +# +# 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. +# + +devel_src_filename = ::File.basename(node['nginx']['devel']['url']) +devel_src_filepath = "#{Chef::Config['file_cache_path']}/#{devel_src_filename}" +devel_extract_path = "#{Chef::Config['file_cache_path']}/nginx-devel-#{node['nginx']['devel']['version']}" + +remote_file devel_src_filepath do + source node['nginx']['devel']['url'] + checksum node['nginx']['devel']['checksum'] + owner 'root' + group node['root_group'] + mode '0644' +end + +bash 'extract_devel_module' do + cwd ::File.dirname(devel_src_filepath) + code <<-EOH + mkdir -p #{devel_extract_path} + tar xzf #{devel_src_filename} -C #{devel_extract_path} + EOH + not_if { ::File.exist?(devel_extract_path) } +end + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ["--add-module=#{devel_extract_path}/ngx_devel_kit-#{node['nginx']['devel']['version']}"] diff --git a/cookbooks/nginx/recipes/ngx_lua_module.rb b/cookbooks/nginx/recipes/ngx_lua_module.rb new file mode 100644 index 0000000..2371f27 --- /dev/null +++ b/cookbooks/nginx/recipes/ngx_lua_module.rb @@ -0,0 +1,47 @@ +# +# Cookbook Name:: nginx +# Recipes:: lua +# +# Author:: Arthur Freyman () +# +# Copyright 2013, Riot Games +# +# 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. +# + +lua_src_filename = ::File.basename(node['nginx']['lua']['url']) +lua_src_filepath = "#{Chef::Config['file_cache_path']}/#{lua_src_filename}" +lua_extract_path = "#{Chef::Config['file_cache_path']}/nginx-lua-#{node['nginx']['lua']['version']}" + +remote_file lua_src_filepath do + source node['nginx']['lua']['url'] + checksum node['nginx']['lua']['checksum'] + owner 'root' + group node['root_group'] + mode '0644' +end + +bash 'extract_lua_module' do + cwd ::File.dirname(lua_src_filepath) + code <<-EOH + mkdir -p #{lua_extract_path} + tar xzf #{lua_src_filename} -C #{lua_extract_path} + EOH + not_if { ::File.exist?(lua_extract_path) } +end + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ["--add-module=#{lua_extract_path}/lua-nginx-module-#{node['nginx']['lua']['version']}"] + +include_recipe 'nginx::lua' +include_recipe 'nginx::ngx_devel_module' diff --git a/cookbooks/nginx/recipes/ohai_plugin.rb b/cookbooks/nginx/recipes/ohai_plugin.rb new file mode 100644 index 0000000..a474d49 --- /dev/null +++ b/cookbooks/nginx/recipes/ohai_plugin.rb @@ -0,0 +1,35 @@ +# +# Cookbook Name:: nginx +# Recipe:: ohai_plugin +# +# Author:: Jamie Winsor () +# +# Copyright 2012-2013, Riot Games +# +# 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. +# + +ohai 'reload_nginx' do + plugin 'nginx' + action :nothing +end + +template "#{node['ohai']['plugin_path']}/nginx.rb" do + source 'plugins/nginx.rb.erb' + owner 'root' + group node['root_group'] + mode '0755' + notifies :reload, 'ohai[reload_nginx]', :immediately +end + +include_recipe 'ohai::default' diff --git a/cookbooks/nginx/recipes/openssl_source.rb b/cookbooks/nginx/recipes/openssl_source.rb new file mode 100644 index 0000000..f286f3d --- /dev/null +++ b/cookbooks/nginx/recipes/openssl_source.rb @@ -0,0 +1,45 @@ +# +# Cookbook Name:: nginx +# Recipe:: openssl_source +# +# Author:: David Radcliffe () +# +# Copyright 2013, David Radcliffe +# +# 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. +# + +src_filename = ::File.basename(node['nginx']['openssl_source']['url']) +src_filepath = "#{Chef::Config['file_cache_path']}/#{src_filename}" +extract_path = "#{Chef::Config['file_cache_path']}/openssl-#{node['nginx']['openssl_source']['version']}" + +remote_file src_filepath do + source node['nginx']['openssl_source']['url'] + owner 'root' + group node['root_group'] + mode '0644' + not_if { ::File.exist?(src_filepath) } +end + +bash 'extract_openssl' do + cwd ::File.dirname(src_filepath) + code <<-EOH + mkdir -p #{extract_path} + tar xzf #{src_filename} -C #{extract_path} + mv #{extract_path}/*/* #{extract_path}/ + EOH + not_if { ::File.exist?(extract_path) } +end + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ["--with-openssl=#{extract_path}"] diff --git a/cookbooks/nginx/recipes/package.rb b/cookbooks/nginx/recipes/package.rb new file mode 100644 index 0000000..eddc8c1 --- /dev/null +++ b/cookbooks/nginx/recipes/package.rb @@ -0,0 +1,52 @@ +# +# Cookbook Name:: nginx +# Recipe:: package +# Author:: AJ Christensen +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +include_recipe 'nginx::ohai_plugin' + +if platform_family?('rhel') + if node['nginx']['repo_source'] == 'epel' + include_recipe 'yum-epel' + elsif node['nginx']['repo_source'] == 'nginx' + include_recipe 'nginx::repo' + package_install_opts = '--disablerepo=* --enablerepo=nginx' + elsif node['nginx']['repo_source'].to_s.empty? + log "node['nginx']['repo_source'] was not set, no additional yum repositories will be installed." do + level :debug + end + else + fail ArgumentError, "Unknown value '#{node['nginx']['repo_source']}' was passed to the nginx cookbook." + end +elsif platform_family?('debian') + include_recipe 'nginx::repo_passenger' if node['nginx']['repo_source'] == 'passenger' + include_recipe 'nginx::repo' if node['nginx']['repo_source'] == 'nginx' +end + +package node['nginx']['package_name'] do + options package_install_opts + notifies :reload, 'ohai[reload_nginx]', :immediately + not_if 'which nginx' +end + +service 'nginx' do + supports :status => true, :restart => true, :reload => true + action :enable +end + +include_recipe 'nginx::commons' diff --git a/cookbooks/nginx/recipes/pagespeed_module.rb b/cookbooks/nginx/recipes/pagespeed_module.rb new file mode 100644 index 0000000..b8fc608 --- /dev/null +++ b/cookbooks/nginx/recipes/pagespeed_module.rb @@ -0,0 +1,62 @@ +# +# Cookbook Name:: nginx +# Recipe:: pagespeed_module +# + +src_filename = ::File.basename(node['nginx']['pagespeed']['url']) +src_filepath = "#{Chef::Config['file_cache_path']}/#{src_filename}" +extract_path = "#{Chef::Config['file_cache_path']}/nginx_pagespeed-#{node['nginx']['pagespeed']['version']}" + +remote_file src_filepath do + source node['nginx']['pagespeed']['url'] + owner 'root' + group node['root_group'] + mode '0644' + not_if { ::File.exist?(src_filepath) } +end + +psol_src_filename = "psol-#{::File.basename(node['nginx']['psol']['url'])}" +psol_src_filepath = "#{Chef::Config['file_cache_path']}/#{psol_src_filename}" +psol_extract_path = "#{Chef::Config['file_cache_path']}/nginx_pagespeed-#{node['nginx']['pagespeed']['version']}/psol" + +remote_file psol_src_filepath do + source node['nginx']['psol']['url'] + owner 'root' + group node['root_group'] + mode '0644' + not_if { ::File.exist?(psol_src_filepath) } +end + +packages = value_for_platform_family( + %w(rhel) => node['nginx']['pagespeed']['packages']['rhel'], + %w(debian) => node['nginx']['pagespeed']['packages']['debian'] +) + +unless packages.empty? + packages.each do |name| + package name + end +end + +bash 'extract_pagespeed' do + cwd ::File.dirname(src_filepath) + code <<-EOH + mkdir -p #{extract_path} + tar xzf #{src_filename} -C #{extract_path} + mv #{extract_path}/*/* #{extract_path}/ + EOH + not_if { ::File.exist?(extract_path) } +end + +bash 'extract_psol' do + cwd ::File.dirname(psol_src_filepath) + code <<-EOH + mkdir -p #{psol_extract_path} + tar xzf #{psol_src_filename} -C #{psol_extract_path} + mv #{psol_extract_path}/*/* #{psol_extract_path}/ + EOH + not_if { ::File.exist?(psol_extract_path) } +end + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ["--add-module=#{extract_path}"] diff --git a/cookbooks/nginx/recipes/passenger.rb b/cookbooks/nginx/recipes/passenger.rb new file mode 100644 index 0000000..93a3ae6 --- /dev/null +++ b/cookbooks/nginx/recipes/passenger.rb @@ -0,0 +1,56 @@ +# +# Cookbook Name:: nginx +# Recipe:: Passenger +# +# Copyright 2013, Chef Software, Inc. +# +# 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. +# + +packages = value_for_platform_family( + %w(rhel) => node['nginx']['passenger']['packages']['rhel'], + %w(fedora) => node['nginx']['passenger']['packages']['fedora'], + %w(debian) => node['nginx']['passenger']['packages']['debian'] +) + +unless packages.empty? + packages.each do |name| + package name + end +end + +gem_package 'rake' if node['nginx']['passenger']['install_rake'] + +if node['nginx']['passenger']['install_method'] == 'package' + package node['nginx']['package_name'] + package 'passenger' +elsif node['nginx']['passenger']['install_method'] == 'source' + + gem_package 'passenger' do + action :install + version node['nginx']['passenger']['version'] + gem_binary node['nginx']['passenger']['gem_binary'] if node['nginx']['passenger']['gem_binary'] + end + + node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ["--add-module=#{node['nginx']['passenger']['root']}/ext/nginx"] + +end + +template "#{node['nginx']['dir']}/conf.d/passenger.conf" do + source 'modules/passenger.conf.erb' + owner 'root' + group node['root_group'] + mode '0644' + notifies :reload, 'service[nginx]', :delayed +end diff --git a/cookbooks/nginx/recipes/repo.rb b/cookbooks/nginx/recipes/repo.rb new file mode 100644 index 0000000..de24b7a --- /dev/null +++ b/cookbooks/nginx/recipes/repo.rb @@ -0,0 +1,41 @@ +# +# Cookbook Name:: nginx +# Recipe:: repo +# Author:: Nick Rycar +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +case node['platform_family'] +when 'rhel', 'fedora' + + yum_repository 'nginx' do + description 'Nginx.org Repository' + baseurl node['nginx']['upstream_repository'] + gpgkey 'http://nginx.org/keys/nginx_signing.key' + action :create + end + +when 'debian' + include_recipe 'apt::default' + + apt_repository 'nginx' do + uri node['nginx']['upstream_repository'] + distribution node['lsb']['codename'] + components %w(nginx) + deb_src true + key 'http://nginx.org/keys/nginx_signing.key' + end +end diff --git a/cookbooks/nginx/recipes/repo_passenger.rb b/cookbooks/nginx/recipes/repo_passenger.rb new file mode 100644 index 0000000..4b3dad4 --- /dev/null +++ b/cookbooks/nginx/recipes/repo_passenger.rb @@ -0,0 +1,39 @@ +# Cookbook Name:: nginx +# Recipe:: repo_passenger +# Author:: Jose Alberto Suarez Lopez +# +# 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. +# + +case node['platform_family'] +when 'rhel', 'fedora' + + log 'There is not official phusion passenger repo for redhat based systems.' do + level :info + end + +when 'debian' + include_recipe 'apt::default' + package 'apt-transport-https' + + apt_repository 'phusionpassenger' do + uri 'https://oss-binaries.phusionpassenger.com/apt/passenger' + distribution node['lsb']['codename'] + components %w(main) + deb_src true + keyserver 'keyserver.ubuntu.com' + key '561F9B9CAC40B2F7' + end + + include_recipe 'nginx::passenger' +end diff --git a/cookbooks/nginx/recipes/set_misc.rb b/cookbooks/nginx/recipes/set_misc.rb new file mode 100644 index 0000000..53b1060 --- /dev/null +++ b/cookbooks/nginx/recipes/set_misc.rb @@ -0,0 +1,30 @@ +# +# Cookbook Name:: nginx +# Recipes:: set_misc +# + +set_misc_src_filename = ::File.basename(node['nginx']['set_misc']['url']) +set_misc_src_filepath = "#{Chef::Config['file_cache_path']}/#{set_misc_src_filename}" +set_misc_extract_path = "#{Chef::Config['file_cache_path']}/nginx-set_misc-#{node['nginx']['set_misc']['version']}" + +remote_file set_misc_src_filepath do + source node['nginx']['set_misc']['url'] + checksum node['nginx']['set_misc']['checksum'] + owner 'root' + group 'root' + mode '0644' +end + +bash 'extract_set_misc_module' do + cwd ::File.dirname(set_misc_src_filepath) + code <<-EOH + mkdir -p #{set_misc_extract_path} + tar xzf #{set_misc_src_filename} -C #{set_misc_extract_path} + EOH + not_if { ::File.exist?(set_misc_extract_path) } +end + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ["--add-module=#{set_misc_extract_path}/set-misc-nginx-module-#{node['nginx']['set_misc']['version']}"] + +include_recipe 'nginx::ngx_devel_module' diff --git a/cookbooks/nginx/recipes/socketproxy.rb b/cookbooks/nginx/recipes/socketproxy.rb new file mode 100644 index 0000000..4498922 --- /dev/null +++ b/cookbooks/nginx/recipes/socketproxy.rb @@ -0,0 +1,26 @@ +include_recipe 'nginx::commons_dir' + +directory node['nginx']['socketproxy']['root'] do + owner node['nginx']['socketproxy']['app_owner'] + group node['nginx']['socketproxy']['app_owner'] + mode 00755 + action :create +end + +context_names = node['nginx']['socketproxy']['apps'].map do |_app, app_conf| + app_conf['context_name'] +end + +fail 'More than one app has the same context_name configured.' if context_names.uniq.length != context_names.length + +template node['nginx']['dir'] + '/sites-available/socketproxy.conf' do + source 'modules/socketproxy.conf.erb' + owner 'root' + group 'root' + mode 00644 + notifies :reload, 'service[nginx]', :delayed +end + +link node['nginx']['dir'] + '/sites-enabled/socketproxy.conf' do + to node['nginx']['dir'] + '/sites-available/socketproxy.conf' +end diff --git a/cookbooks/nginx/recipes/source.rb b/cookbooks/nginx/recipes/source.rb new file mode 100644 index 0000000..5f565b6 --- /dev/null +++ b/cookbooks/nginx/recipes/source.rb @@ -0,0 +1,205 @@ +# +# Cookbook Name:: nginx +# Recipe:: source +# +# Author:: Adam Jacob () +# Author:: Joshua Timberman () +# Author:: Jamie Winsor () +# +# Copyright 2009-2013, Chef Software, Inc. +# +# 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. +# + +# This is for Chef 10 and earlier where attributes aren't loaded +# deterministically (resolved in Chef 11). +node.load_attribute_by_short_filename('source', 'nginx') if node.respond_to?(:load_attribute_by_short_filename) + +nginx_url = node['nginx']['source']['url'] || + "http://nginx.org/download/nginx-#{node['nginx']['source']['version']}.tar.gz" + +node.set['nginx']['binary'] = node['nginx']['source']['sbin_path'] +node.set['nginx']['daemon_disable'] = true + +unless node['nginx']['source']['use_existing_user'] + user node['nginx']['user'] do + system true + shell '/bin/false' + home '/var/www' + end +end + +include_recipe 'nginx::ohai_plugin' +include_recipe 'nginx::commons_dir' +include_recipe 'nginx::commons_script' +include_recipe 'build-essential::default' + +src_filepath = "#{Chef::Config['file_cache_path'] || '/tmp'}/nginx-#{node['nginx']['source']['version']}.tar.gz" +packages = value_for_platform_family( + %w(rhel fedora suse) => %w(pcre-devel openssl-devel), + %w(gentoo) => [], + %w(default) => %w(libpcre3 libpcre3-dev libssl-dev) +) + +packages.each do |name| + package name +end + +remote_file nginx_url do + source nginx_url + checksum node['nginx']['source']['checksum'] + path src_filepath + backup false +end + +node.run_state['nginx_force_recompile'] = false +node.run_state['nginx_configure_flags'] = + node['nginx']['source']['default_configure_flags'] | node['nginx']['configure_flags'] + +include_recipe 'nginx::commons_conf' + +cookbook_file "#{node['nginx']['dir']}/mime.types" do + source 'mime.types' + owner 'root' + group node['root_group'] + mode '0644' + notifies :reload, 'service[nginx]', :delayed +end + +# source install depends on the existence of the `tar` package +package 'tar' + +# Unpack downloaded source so we could apply nginx patches +# in custom modules - example http://yaoweibin.github.io/nginx_tcp_proxy_module/ +# patch -p1 < /path/to/nginx_tcp_proxy_module/tcp.patch +bash 'unarchive_source' do + cwd ::File.dirname(src_filepath) + code <<-EOH + tar zxf #{::File.basename(src_filepath)} -C #{::File.dirname(src_filepath)} + EOH + not_if { ::File.directory?("#{Chef::Config['file_cache_path'] || '/tmp'}/nginx-#{node['nginx']['source']['version']}") } +end + +node['nginx']['source']['modules'].each do |ngx_module| + include_recipe ngx_module +end + +configure_flags = node.run_state['nginx_configure_flags'] +nginx_force_recompile = node.run_state['nginx_force_recompile'] + +bash 'compile_nginx_source' do + cwd ::File.dirname(src_filepath) + code <<-EOH + cd nginx-#{node['nginx']['source']['version']} && + ./configure #{node.run_state['nginx_configure_flags'].join(' ')} && + make && make install + EOH + + not_if do + nginx_force_recompile == false && + node.automatic_attrs['nginx'] && + node.automatic_attrs['nginx']['version'] == node['nginx']['source']['version'] && + node.automatic_attrs['nginx']['configure_arguments'].sort == configure_flags.sort + end + + notifies :restart, 'service[nginx]' + notifies :reload, 'ohai[reload_nginx]', :immediately +end + +case node['nginx']['init_style'] +when 'runit' + node.set['nginx']['src_binary'] = node['nginx']['binary'] + include_recipe 'runit::default' + + runit_service 'nginx' + + service 'nginx' do + supports :status => true, :restart => true, :reload => true + reload_command "#{node['runit']['sv_bin']} hup #{node['runit']['service_dir']}/nginx" + end +when 'bluepill' + include_recipe 'bluepill::default' + + template "#{node['bluepill']['conf_dir']}/nginx.pill" do + source 'nginx.pill.erb' + mode '0644' + end + + bluepill_service 'nginx' do + action [:enable, :load] + end + + service 'nginx' do + supports :status => true, :restart => true, :reload => true + reload_command "[[ -f #{node['nginx']['pid']} ]] && kill -HUP `cat #{node['nginx']['pid']}` || true" + action :nothing + end +when 'upstart' + # we rely on this to set up nginx.conf with daemon disable instead of doing + # it in the upstart init script. + node.set['nginx']['daemon_disable'] = node['nginx']['upstart']['foreground'] + + template '/etc/init/nginx.conf' do + source 'nginx-upstart.conf.erb' + owner 'root' + group node['root_group'] + mode '0644' + end + + service 'nginx' do + provider Chef::Provider::Service::Upstart + supports :status => true, :restart => true, :reload => true + action :nothing + end +else + node.set['nginx']['daemon_disable'] = false + + generate_init = true + + case node['platform'] + when 'gentoo' + generate_template = false + when 'debian', 'ubuntu' + generate_template = true + defaults_path = '/etc/default/nginx' + when 'freebsd' + generate_init = false + else + generate_template = true + defaults_path = '/etc/sysconfig/nginx' + end + + template '/etc/init.d/nginx' do + source 'nginx.init.erb' + owner 'root' + group node['root_group'] + mode '0755' + end if generate_init + + if generate_template + template defaults_path do + source 'nginx.sysconfig.erb' + owner 'root' + group node['root_group'] + mode '0644' + end + end + + service 'nginx' do + supports :status => true, :restart => true, :reload => true + action :enable + end +end + +node.run_state.delete('nginx_configure_flags') +node.run_state.delete('nginx_force_recompile') diff --git a/cookbooks/nginx/recipes/syslog_module.rb b/cookbooks/nginx/recipes/syslog_module.rb new file mode 100644 index 0000000..aea546b --- /dev/null +++ b/cookbooks/nginx/recipes/syslog_module.rb @@ -0,0 +1,69 @@ +# +# Cookbook Name:: nginx +# Recipe:: syslog_module +# +# Author:: Bob Ziuchkovski () +# +# Copyright 2014, UserTesting +# +# 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. +# + +nginx_src = "#{Chef::Config['file_cache_path']}/nginx-#{node['nginx']['source']['version']}" +nginx_syslog_src = "#{Chef::Config['file_cache_path']}/nginx_syslog_module" + +major, minor, patch = node['nginx']['source']['version'].split('.').map { |s| Integer(s) } +fail 'Unsupported nginx version' if major != 1 +case minor +when 2 + case patch + when 0..6 + syslog_patch = 'syslog_1.2.0.patch' + else + syslog_patch = 'syslog_1.2.7.patch' + end +when 3 + case patch + when 0..9 + syslog_patch = 'syslog_1.2.0.patch' + when 10..13 + syslog_patch = 'syslog_1.3.11.patch' + else + syslog_patch = 'syslog_1.3.14.patch' + end +when 4 + syslog_patch = 'syslog_1.4.0.patch' +when 5..6 + syslog_patch = 'syslog_1.5.6.patch' +when 7 + syslog_patch = 'syslog_1.7.0.patch' +else + fail 'Unsupported nginx version' +end + +git nginx_syslog_src do + repository node['nginx']['syslog']['git_repo'] + revision node['nginx']['syslog']['git_revision'] + action :sync + user 'root' + group 'root' +end + +execute 'apply_nginx_syslog_patch' do + cwd nginx_src + command "patch -p1 < #{nginx_syslog_src}/#{syslog_patch}" + not_if "patch -p1 --dry-run --reverse --silent < #{nginx_syslog_src}/#{syslog_patch}", :cwd => nginx_src +end + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ["--add-module=#{nginx_syslog_src}"] diff --git a/cookbooks/nginx/recipes/upload_progress_module.rb b/cookbooks/nginx/recipes/upload_progress_module.rb new file mode 100644 index 0000000..ccb1cfb --- /dev/null +++ b/cookbooks/nginx/recipes/upload_progress_module.rb @@ -0,0 +1,53 @@ +# +# Cookbook Name:: nginx +# Recipe:: upload_progress_module +# +# Author:: Jamie Winsor () +# +# Copyright 2012-2013, Riot Games +# +# 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. +# + +upm_src_filename = ::File.basename(node['nginx']['upload_progress']['url']) +upm_src_filepath = "#{Chef::Config['file_cache_path']}/#{upm_src_filename}" +upm_extract_path = "#{Chef::Config['file_cache_path']}/nginx_upload_progress/#{node['nginx']['upload_progress']['checksum']}" + +remote_file upm_src_filepath do + source node['nginx']['upload_progress']['url'] + checksum node['nginx']['upload_progress']['checksum'] + owner 'root' + group node['root_group'] + mode '0644' +end + +template "#{node['nginx']['dir']}/conf.d/upload_progress.conf" do + source 'modules/upload_progress.erb' + owner 'root' + group node['root_group'] + mode '0644' + notifies :reload, 'service[nginx]', :delayed +end + +bash 'extract_upload_progress_module' do + cwd ::File.dirname(upm_src_filepath) + code <<-EOH + mkdir -p #{upm_extract_path} + tar xzf #{upm_src_filename} -C #{upm_extract_path} + mv #{upm_extract_path}/*/* #{upm_extract_path}/ + EOH + not_if { ::File.exist?(upm_extract_path) } +end + +node.run_state['nginx_configure_flags'] = + node.run_state['nginx_configure_flags'] | ["--add-module=#{upm_extract_path}"] diff --git a/cookbooks/nginx/templates/debian/nginx.init.erb b/cookbooks/nginx/templates/debian/nginx.init.erb new file mode 100644 index 0000000..5a3711e --- /dev/null +++ b/cookbooks/nginx/templates/debian/nginx.init.erb @@ -0,0 +1,97 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: nginx +# Required-Start: $local_fs $remote_fs $network $syslog +# Required-Stop: $local_fs $remote_fs $network $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts the nginx web server +# Description: starts nginx using start-stop-daemon +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=<%= node['nginx']['binary'] %> +NAME=nginx +DESC=nginx +PID=<%= node['nginx']['pid'] %> + +# Include nginx defaults if available +if [ -f /etc/default/nginx ]; then + . /etc/default/nginx +fi + +test -x $DAEMON || exit 0 + +set -e + +. /lib/lsb/init-functions + +test_nginx_config() { + if $DAEMON -t $DAEMON_OPTS >/dev/null 2>&1; then + return 0 + else + $DAEMON -t $DAEMON_OPTS + return $? + fi +} + +case "$1" in + start) + echo -n "Starting $DESC: " + test_nginx_config + # Check if the ULIMIT is set in /etc/default/nginx + if [ -n "$ULIMIT" ]; then + # Set the ulimits + ulimit $ULIMIT + fi + start-stop-daemon --start --quiet --pidfile $PID \ + --exec $DAEMON -- $DAEMON_OPTS || true + echo "$NAME." + ;; + + stop) + echo -n "Stopping $DESC: " + start-stop-daemon --stop --quiet --pidfile $PID \ + --exec $DAEMON || true + echo "$NAME." + ;; + + restart|force-reload) + echo -n "Restarting $DESC: " + start-stop-daemon --stop --quiet --pidfile \ + $PID --exec $DAEMON || true + sleep 1 + test_nginx_config + start-stop-daemon --start --quiet --pidfile \ + $PID --exec $DAEMON -- $DAEMON_OPTS || true + echo "$NAME." + ;; + + reload) + echo -n "Reloading $DESC configuration: " + test_nginx_config + start-stop-daemon --stop --signal HUP --quiet --pidfile $PID \ + --exec $DAEMON || true + echo "$NAME." + ;; + + configtest|testconfig) + echo -n "Testing $DESC configuration: " + if test_nginx_config; then + echo "$NAME." + else + exit $? + fi + ;; + + status) + status_of_proc -p $PID "$DAEMON" nginx && exit 0 || exit $? + ;; + *) + echo "Usage: $NAME {start|stop|restart|reload|force-reload|status|configtest}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/cookbooks/nginx/templates/default/default-site.erb b/cookbooks/nginx/templates/default/default-site.erb new file mode 100644 index 0000000..e76812e --- /dev/null +++ b/cookbooks/nginx/templates/default/default-site.erb @@ -0,0 +1,11 @@ +server { + listen <%= node['nginx']['port'] -%>; + server_name <%= node['hostname'] %>; + + access_log <%= node['nginx']['log_dir'] %>/localhost.access.log; + + location / { + root <%= node['nginx']['default_root'] %>; + index index.html index.htm; + } +} diff --git a/cookbooks/nginx/templates/default/modules/authorized_ip.erb b/cookbooks/nginx/templates/default/modules/authorized_ip.erb new file mode 100644 index 0000000..159f27e --- /dev/null +++ b/cookbooks/nginx/templates/default/modules/authorized_ip.erb @@ -0,0 +1,6 @@ +geo $<%= node['nginx']['remote_ip_var'] %> $authorized_ip { + default no; + <% node['nginx']['authorized_ips'].each do |ip| %> + <%= "#{ip} yes;" %> + <% end %> +} diff --git a/cookbooks/nginx/templates/default/modules/http_geoip.conf.erb b/cookbooks/nginx/templates/default/modules/http_geoip.conf.erb new file mode 100644 index 0000000..f1ce82a --- /dev/null +++ b/cookbooks/nginx/templates/default/modules/http_geoip.conf.erb @@ -0,0 +1,4 @@ +geoip_country <%= @country_dat %>; +<% if @city_dat -%> +geoip_city <%= @city_dat %>; +<% end -%> diff --git a/cookbooks/nginx/templates/default/modules/http_gzip_static.conf.erb b/cookbooks/nginx/templates/default/modules/http_gzip_static.conf.erb new file mode 100644 index 0000000..360d46e --- /dev/null +++ b/cookbooks/nginx/templates/default/modules/http_gzip_static.conf.erb @@ -0,0 +1 @@ +gzip_static <%= node['nginx']['gzip_static'] %>; diff --git a/cookbooks/nginx/templates/default/modules/http_realip.conf.erb b/cookbooks/nginx/templates/default/modules/http_realip.conf.erb new file mode 100644 index 0000000..5f9ffd1 --- /dev/null +++ b/cookbooks/nginx/templates/default/modules/http_realip.conf.erb @@ -0,0 +1,7 @@ +<% node['nginx']['realip']['addresses'].each do |address| %> +set_real_ip_from <%= address %>; +<% end %> +real_ip_header <%= node['nginx']['realip']['header'] %>; +<% if node['nginx']['version'] >= '1.2.1' -%> +real_ip_recursive <%= node['nginx']['realip']['real_ip_recursive'] %>; +<% end -%> diff --git a/cookbooks/nginx/templates/default/modules/nginx_status.erb b/cookbooks/nginx/templates/default/modules/nginx_status.erb new file mode 100644 index 0000000..77e295d --- /dev/null +++ b/cookbooks/nginx/templates/default/modules/nginx_status.erb @@ -0,0 +1,14 @@ +include authorized_ip; + +server { + listen <%= node['nginx']['status']['port'] %>; + server_name _; + + location /nginx_status { + if ($authorized_ip = no) { + return 404; + } + stub_status on; + access_log off; + } +} diff --git a/cookbooks/nginx/templates/default/modules/passenger.conf.erb b/cookbooks/nginx/templates/default/modules/passenger.conf.erb new file mode 100644 index 0000000..992b14f --- /dev/null +++ b/cookbooks/nginx/templates/default/modules/passenger.conf.erb @@ -0,0 +1,13 @@ +passenger_root <%= node['nginx']['passenger']['root'] %>; +passenger_ruby <%= node['nginx']['passenger']['ruby'] %>; +passenger_max_pool_size <%= node['nginx']['passenger']['max_pool_size'] %>; +passenger_spawn_method <%= node['nginx']['passenger']['spawn_method'] %>; +passenger_buffer_response <%= node['nginx']['passenger']['buffer_response'] %>; +passenger_min_instances <%= node['nginx']['passenger']['min_instances'] %>; +passenger_max_instances_per_app <%= node['nginx']['passenger']['max_instances_per_app'] %>; +passenger_pool_idle_time <%= node['nginx']['passenger']['pool_idle_time'] %>; +passenger_max_requests <%= node['nginx']['passenger']['max_requests'] %>; + +<%- if node['nginx']['passenger']['nodejs'] %> + passenger_nodejs <%= node['nginx']['passenger']['nodejs'] %>; +<% end %> diff --git a/cookbooks/nginx/templates/default/modules/socketproxy.conf.erb b/cookbooks/nginx/templates/default/modules/socketproxy.conf.erb new file mode 100644 index 0000000..60cb2e3 --- /dev/null +++ b/cookbooks/nginx/templates/default/modules/socketproxy.conf.erb @@ -0,0 +1,89 @@ + server { + + set $app_home <%= node['nginx']['socketproxy']['root'] %>; + + <% if node['nginx']['sts_max_age'] -%> + add_header Strict-Transport-Security "max-age=<%= node['nginx']['sts_max_age'] %>"; + <% end -%> + + listen <%= node['nginx']['port'] %> default; + + access_log <%= node['nginx']['log_dir'] %>/<%= node['nginx']['socketproxy']['logname'] %>.access.log<% if node['nginx']['access_log_options'] %> <%= node['nginx']['access_log_options'] %><% end %>; + error_log <%= node['nginx']['log_dir'] %>/<%= node['nginx']['socketproxy']['logname'] %>.error.log <%= node['nginx']['socketproxy']['log_level'] %>; + + <% if node['nginx']['server_name'] -%> + server_name ~^<%= node['nginx']['server_name'] %>\..*$; + <% end -%> + + client_max_body_size 4G; + keepalive_timeout 5; + + root $app_home/<%= node['nginx']['socketproxy']['default_app'] %>/<%= node['nginx']['socketproxy']['apps'][node['nginx']['socketproxy']['default_app']]['subdir'] %>/public; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + + if ($request_method !~ ^(GET|HEAD|PUT|POST|DELETE|OPTIONS|PATCH)$ ) { + return 405; + } + + <% node['nginx']['socketproxy']['apps'].each do |app, app_conf| + if app_conf['context_name'] + base_loc = "/#{app_conf['context_name'].gsub(/^\/+/,'').gsub(/\/+$/,'')}" + else + base_loc = "" + end + -%> + + location ~ "^<%= base_loc %>/assets/(.*/)*.*-[0-9a-f]{32}.*" { + gzip_static on; + expires max; + add_header Cache-Control public; + } + + location ^~ /<%= app_conf['context_name'] %> { + + alias $app_home/<%= app %>/<%= app_conf['subdir'] %>/public/; + + try_files $uri/index.html $uri.html $uri @app_<%= app %>; + error_page 404 /404.html; + error_page 422 /422.html; + error_page 500 502 503 504 /500.html; + error_page 403 /403.html; + + } + + location @app_<%= app %> { + + proxy_read_timeout 600; + <% + if app_conf['socket']['type'] + case app_conf['socket']['type'] + when 'unix' + -%> + proxy_pass http://unix:$app_home/<%= app %>/<%= app_conf['socket']['path'] %><% if app_conf['prepend_slash'] %>:/<% end %>; + <% + when 'tcp' + -%> + proxy_pass http://localhost:<%= app_conf['socket']['port'] -%>; + <% + end + else + -%> + proxy_pass http://unix:$app_home/<%= app %>/<%= app_conf['socket_path'] %><% if app_conf['prepend_slash'] %>:/<% end %>; + <% + end + -%> + + } + + <% end # node['nginx']['socketproxy']['apps'].each -%> + + error_page 500 502 504 /50x.html; + location = /50x.html { + root html; + } + +} diff --git a/cookbooks/nginx/templates/default/modules/upload_progress.erb b/cookbooks/nginx/templates/default/modules/upload_progress.erb new file mode 100644 index 0000000..39387d2 --- /dev/null +++ b/cookbooks/nginx/templates/default/modules/upload_progress.erb @@ -0,0 +1,4 @@ +upload_progress <%= node['nginx']['upload_progress']['zone_name'] -%> <%= node['nginx']['upload_progress']['zone_size'] -%>; +<% if node['nginx']['upload_progress']['javascript_output'] -%> +upload_progress_java_output; +<% end -%> diff --git a/cookbooks/nginx/templates/default/nginx-upstart.conf.erb b/cookbooks/nginx/templates/default/nginx-upstart.conf.erb new file mode 100644 index 0000000..35cf867 --- /dev/null +++ b/cookbooks/nginx/templates/default/nginx-upstart.conf.erb @@ -0,0 +1,39 @@ +# nginx + +description "nginx http daemon" + +start on (local-filesystems and net-device-up IFACE=lo and runlevel [<%= node['nginx']['upstart']['runlevels'] %>]) +stop on runlevel [!<%= node['nginx']['upstart']['runlevels'] %>] + +env DAEMON=<%= node['nginx']['binary'] %> +env PID=<%= node['nginx']['pid'] %> +env CONFIG=<%= node['nginx']['source']['conf_path'] %> + +respawn +<% if node['nginx']['upstart']['respawn_limit'] %> +respawn limit <%= node['nginx']['upstart']['respawn_limit'] %> +<% end %> + +pre-start script + ${DAEMON} -t + if [ $? -ne 0 ]; then + exit $? + fi +end script + +<% unless node['nginx']['upstart']['foreground'] %> +expect fork +<% else %> +console output +<% end %> + +exec ${DAEMON} -c "${CONFIG}" + +<% if node.recipe?('nginx::passenger') and not node['nginx']['upstart']['foreground'] %> +# classic example of why pidfiles should have gone away +# with the advent of fork(). we missed that bus a long +# time ago so hack around it. +post-stop script + start-stop-daemon --stop --pidfile ${PID} --name nginx --exec ${DAEMON} --signal QUIT +end script +<% end %> diff --git a/cookbooks/nginx/templates/default/nginx.conf.erb b/cookbooks/nginx/templates/default/nginx.conf.erb new file mode 100644 index 0000000..169eb24 --- /dev/null +++ b/cookbooks/nginx/templates/default/nginx.conf.erb @@ -0,0 +1,103 @@ +user <%= node['nginx']['user'] %><% if node['nginx']['user'] != node['nginx']['group'] %> <%= node['nginx']['group'] %><% end %>; +worker_processes <%= node['nginx']['worker_processes'] %>; +<% if node['nginx']['daemon_disable'] -%> +daemon off; +<% end -%> +<% if node['nginx']['worker_rlimit_nofile'] -%> +worker_rlimit_nofile <%= node['nginx']['worker_rlimit_nofile'] %>; +<% end -%> + +error_log <%= node['nginx']['log_dir'] %>/error.log<% if node['nginx']['error_log_options'] %> <%= node['nginx']['error_log_options'] %><% end %>; +pid <%= node['nginx']['pid'] %>; + +events { + worker_connections <%= node['nginx']['worker_connections'] %>; +<% if node['nginx']['multi_accept'] -%> + multi_accept on; +<% end -%> +<% if node['nginx']['event'] -%> + use <%= node['nginx']['event'] %>; +<% end -%> +<% if node['nginx']['accept_mutex_delay'] -%> + accept_mutex_delay <%= node['nginx']['accept_mutex_delay'] %>ms; +<% end -%> +} + +http { + <% if node.recipe?('nginx::naxsi_module') %> + include <%= node['nginx']['dir'] %>/naxsi_core.rules; + <% end %> + + include <%= node['nginx']['dir'] %>/mime.types; + default_type application/octet-stream; + + <% node['nginx']['log_formats'].each do |name, format| %> + log_format <%= name %> <%= format %>; + <% end -%> + + <% if node['nginx']['disable_access_log'] -%> + access_log off; + <% else -%> + access_log <%= node['nginx']['log_dir'] %>/access.log<% if node['nginx']['access_log_options'] %> <%= node['nginx']['access_log_options'] %><% end %>; + <% end %> + <% if node['nginx']['server_tokens'] -%> + server_tokens <%= node['nginx']['server_tokens'] %>; + <% end -%> + + sendfile <%= node['nginx']['sendfile'] %>; + tcp_nopush <%= node['nginx']['tcp_nopush'] %>; + tcp_nodelay <%= node['nginx']['tcp_nodelay'] %>; + + <% if node['nginx']['keepalive'] == 'on' %> + keepalive_requests <%= node['nginx']['keepalive_requests'] %>; + keepalive_timeout <%= node['nginx']['keepalive_timeout'] %>; + <% end %> + + <% unless node['nginx']['underscores_in_headers'].nil? %> + underscores_in_headers <%= node['nginx']['underscores_in_headers'] %>; + <% end %> + + gzip <%= node['nginx']['gzip'] %>; + <% if node['nginx']['gzip'] == 'on' %> + gzip_http_version <%= node['nginx']['gzip_http_version'] %>; + gzip_comp_level <%= node['nginx']['gzip_comp_level'] %>; + gzip_proxied <%= node['nginx']['gzip_proxied'] %>; + gzip_vary <%= node['nginx']['gzip_vary'] %>; + <% if node['nginx']['gzip_buffers'] -%> + gzip_buffers <%= node['nginx']['gzip_buffers'] %>; + <% end -%> + gzip_types <%= node['nginx']['gzip_types'].join(' ') %>; + gzip_min_length <%= node['nginx']['gzip_min_length'] %>; + gzip_disable "<%= node['nginx']['gzip_disable'] %>"; + <% end %> + + + variables_hash_max_size <%= node['nginx']['variables_hash_max_size'] %>; + variables_hash_bucket_size <%= node['nginx']['variables_hash_bucket_size'] %>; + server_names_hash_bucket_size <%= node['nginx']['server_names_hash_bucket_size'] %>; + types_hash_max_size <%= node['nginx']['types_hash_max_size'] %>; + types_hash_bucket_size <%= node['nginx']['types_hash_bucket_size'] %>; + <% if node['nginx']['proxy_read_timeout'] -%> + proxy_read_timeout <%= node['nginx']['proxy_read_timeout'] %>; + <% end -%> + <% if node['nginx']['client_body_buffer_size'] -%> + client_body_buffer_size <%= node['nginx']['client_body_buffer_size'] %>; + <% end -%> + <% if node['nginx']['client_max_body_size'] -%> + client_max_body_size <%= node['nginx']['client_max_body_size'] %>; + <% end -%> + <% if node['nginx']['large_client_header_buffers'] -%> + large_client_header_buffers <%= node['nginx']['large_client_header_buffers'] %>; + <% end -%> + + <% if node['nginx']['enable_rate_limiting'] -%> + limit_req_zone $binary_remote_addr zone=<%= node['nginx']['rate_limiting_zone_name'] %>:<%= node['nginx']['rate_limiting_backoff'] %> rate=<%= node['nginx']['rate_limit'] %>; + <% end -%> + + <% node['nginx']['extra_configs'].each do |key, value| -%> + <%= key %> <%= value %>; + <% end -%> + + include <%= node['nginx']['dir'] %>/conf.d/*.conf; + include <%= node['nginx']['dir'] %>/sites-enabled/*; +} diff --git a/cookbooks/nginx/templates/default/nginx.init.erb b/cookbooks/nginx/templates/default/nginx.init.erb new file mode 100644 index 0000000..db945ad --- /dev/null +++ b/cookbooks/nginx/templates/default/nginx.init.erb @@ -0,0 +1,111 @@ +#!/bin/sh +# +# nginx +# +# chkconfig: - 57 47 +# description: nginx +# processname: nginx +# config: /etc/sysconfig/nginx +# + +# Source function library. +. /etc/rc.d/init.d/functions + +# Source networking configuration. +. /etc/sysconfig/network + +# Check that networking is up. +[ "$NETWORKING" = "no" ] && exit +exec=<%= node['nginx']['binary'] %> +prog=$(basename $exec) + +# default options, overruled by items in sysconfig +NGINX_GLOBAL="" + +[ -e /etc/sysconfig/nginx ] && . /etc/sysconfig/nginx + +lockfile=/var/lock/subsys/nginx + +start() { + [ -x $exec ] || exit 5 + echo -n $"Starting $prog: " + # if not running, start it up here, usually something like "daemon $exec" + options="" + if [ "${NGINX_GLOBAL}" != "" ]; then + options="-g ${NGINX_GLOBAL}" + fi + $exec $options + retval=$? + echo + [ $retval -eq 0 ] && touch $lockfile + return $retval +} + +stop() { + echo -n $"Stopping $prog: " + $exec -s stop + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile + return $retval +} + +restart() { + stop + start +} + +reload() { + echo -n $"Reloading $prog: " + $exec -s reload + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile + return $retval +} + +configtest() { + if [ "$#" -ne 0 ] ; then + case "$1" in + -q) + FLAG=$1 + ;; + *) + ;; + esac + shift + fi + ${exec} -t $FLAG + RETVAL=$? + return $RETVAL +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status nginx + ;; + restart) + restart + ;; + reload|force-reload) + reload + ;; + condrestart) + [ -f $lockfile ] && restart || : + ;; + configtest) + configtest + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart|configtest}" + exit 1 +esac + +exit $? diff --git a/cookbooks/nginx/templates/default/nginx.pill.erb b/cookbooks/nginx/templates/default/nginx.pill.erb new file mode 100644 index 0000000..ed90493 --- /dev/null +++ b/cookbooks/nginx/templates/default/nginx.pill.erb @@ -0,0 +1,15 @@ +Bluepill.application("nginx", :log_file => "<%= node['nginx']['log_dir'] %>/bluepill-nginx.log") do |app| + app.process("nginx") do |process| + process.pid_file = "<%= node['nginx']['pid'] %>" + process.working_dir = "<%= node['nginx']['source']['prefix'] %>" + process.start_command = "<%= node['nginx']['binary'] %> -c <%= node['nginx']['dir'] %>/nginx.conf" + process.stop_command = "kill -QUIT {{PID}}" + process.restart_command = "kill -HUP {{PID}}" + process.daemonize = true + process.stdout = process.stderr = "<%= node['nginx']['log_dir'] %>/nginx.log" + + process.monitor_children do |child_process| + child_process.stop_command = "kill -QUIT {{PID}}" + end + end +end diff --git a/cookbooks/nginx/templates/default/nginx.sysconfig.erb b/cookbooks/nginx/templates/default/nginx.sysconfig.erb new file mode 100644 index 0000000..3f15788 --- /dev/null +++ b/cookbooks/nginx/templates/default/nginx.sysconfig.erb @@ -0,0 +1 @@ +NGINX_GLOBAL=<%= node['nginx']['global'] %> diff --git a/cookbooks/nginx/templates/default/nxdissite.erb b/cookbooks/nginx/templates/default/nxdissite.erb new file mode 100644 index 0000000..8ba676b --- /dev/null +++ b/cookbooks/nginx/templates/default/nxdissite.erb @@ -0,0 +1,29 @@ +#!/bin/sh -e + +SYSCONFDIR='<%= node['nginx']['dir'] %>' + +if [ -z $1 ]; then + echo "Which site would you like to disable?" + echo -n "Your choices are: " + ls $SYSCONFDIR/sites-enabled/* | \ + sed -e "s,$SYSCONFDIR/sites-enabled/,,g" | xargs echo + echo -n "Site name? " + read SITENAME +else + SITENAME=$1 +fi + +if [ $SITENAME = "default" ]; then + PRIORITY="000" +fi + +if ! [ -e $SYSCONFDIR/sites-enabled/$SITENAME -o \ + -e $SYSCONFDIR/sites-enabled/"$PRIORITY"-"$SITENAME" ]; then + echo "This site is already disabled, or does not exist!" + exit 1 +fi + +if ! rm $SYSCONFDIR/sites-enabled/$SITENAME 2>/dev/null; then + rm -f $SYSCONFDIR/sites-enabled/"$PRIORITY"-"$SITENAME" +fi +echo "Site $SITENAME disabled; reload nginx to disable." diff --git a/cookbooks/nginx/templates/default/nxensite.erb b/cookbooks/nginx/templates/default/nxensite.erb new file mode 100644 index 0000000..31a3c84 --- /dev/null +++ b/cookbooks/nginx/templates/default/nxensite.erb @@ -0,0 +1,38 @@ +#!/bin/sh -e + +SYSCONFDIR='<%= node['nginx']['dir'] %>' + +if [ -z $1 ]; then + echo "Which site would you like to enable?" + echo -n "Your choices are: " + ls $SYSCONFDIR/sites-available/* | \ + sed -e "s,$SYSCONFDIR/sites-available/,,g" | xargs echo + echo -n "Site name? " + read SITENAME +else + SITENAME=$1 +fi + +if [ $SITENAME = "default" ]; then + PRIORITY="000" +fi + +if [ -e $SYSCONFDIR/sites-enabled/$SITENAME -o \ + -e $SYSCONFDIR/sites-enabled/"$PRIORITY"-"$SITENAME" ]; then + echo "This site is already enabled!" + exit 0 +fi + +if ! [ -e $SYSCONFDIR/sites-available/$SITENAME ]; then + echo "This site does not exist!" + exit 1 +fi + +if [ $SITENAME = "default" ]; then + ln -sf $SYSCONFDIR/sites-available/$SITENAME \ + $SYSCONFDIR/sites-enabled/"$PRIORITY"-"$SITENAME" +else + ln -sf $SYSCONFDIR/sites-available/$SITENAME $SYSCONFDIR/sites-enabled/$SITENAME +fi + +echo "Site $SITENAME installed; reload nginx to enable." diff --git a/cookbooks/nginx/templates/default/plugins/nginx.rb.erb b/cookbooks/nginx/templates/default/plugins/nginx.rb.erb new file mode 100644 index 0000000..4e2d4f7 --- /dev/null +++ b/cookbooks/nginx/templates/default/plugins/nginx.rb.erb @@ -0,0 +1,66 @@ +# +# Author:: Jamie Winsor () +# +# Copyright 2012, Riot Games +# +# 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. +# + +provides "nginx" +provides "nginx/version" +provides "nginx/configure_arguments" +provides "nginx/prefix" +provides "nginx/conf_path" + +def parse_flags(flags) + prefix = nil + conf_path = nil + + flags.each do |flag| + case flag + when /^--prefix=(.+)$/ + prefix = $1 + when /^--conf-path=(.+)$/ + conf_path = $1 + end + end + + [ prefix, conf_path ] +end + +nginx Mash.new unless nginx +nginx[:version] = nil unless nginx[:version] +nginx[:configure_arguments] = Array.new unless nginx[:configure_arguments] +nginx[:prefix] = nil unless nginx[:prefix] +nginx[:conf_path] = nil unless nginx[:conf_path] + +status, stdout, stderr = run_command(:no_status_check => true, :command => "<%= node['nginx']['binary'] %> -V") + +if status == 0 + stderr.split("\n").each do |line| + case line + when /^configure arguments:(.+)/ + # This could be better: I'm splitting on configure arguments which removes them and also + # adds a blank string at index 0 of the array. This is why we drop index 0 and map to + # add the '--' prefix back to the configure argument. + nginx[:configure_arguments] = $1.split(/\s--/).drop(1).map { |ca| "--#{ca}" } + + prefix, conf_path = parse_flags(nginx[:configure_arguments]) + + nginx[:prefix] = prefix + nginx[:conf_path] = conf_path + when /^nginx version: nginx\/(\d+\.\d+\.\d+)/ + nginx[:version] = $1 + end + end +end diff --git a/cookbooks/nginx/templates/default/sv-nginx-log-run.erb b/cookbooks/nginx/templates/default/sv-nginx-log-run.erb new file mode 100644 index 0000000..a79a518 --- /dev/null +++ b/cookbooks/nginx/templates/default/sv-nginx-log-run.erb @@ -0,0 +1,2 @@ +#!/bin/sh +exec svlogd -tt ./main diff --git a/cookbooks/nginx/templates/default/sv-nginx-run.erb b/cookbooks/nginx/templates/default/sv-nginx-run.erb new file mode 100644 index 0000000..4367891 --- /dev/null +++ b/cookbooks/nginx/templates/default/sv-nginx-run.erb @@ -0,0 +1,4 @@ +#!/bin/sh +ulimit -n <%= node['nginx']['ulimit'] %> +exec 2>&1 +exec <%= node['nginx']['src_binary'] %> -c <%= node['nginx']['dir'] %>/nginx.conf diff --git a/cookbooks/nginx/templates/gentoo/nginx.init.erb b/cookbooks/nginx/templates/gentoo/nginx.init.erb new file mode 100644 index 0000000..57a6c31 --- /dev/null +++ b/cookbooks/nginx/templates/gentoo/nginx.init.erb @@ -0,0 +1,87 @@ +#!/sbin/runscript +# Copyright 1999-2012 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: /var/cvsroot/gentoo-x86/www-servers/nginx/files/nginx.initd,v 1.1 2012/02/11 10:17:30 hollow Exp $ + +extra_commands="configtest" +extra_started_commands="upgrade reload" + +description="Robust, small and high performance http and reverse proxy server" +description_configtest="Run nginx' internal config check." +description_upgrade="Upgrade the nginx binary without losing connections." +description_reload="Reload the nginx configuration without losing connections." + +nginx_config=<%= node['nginx']['source']['conf_path'] %> + +command=<%= node['nginx']['binary'] %> +command_args="-c ${nginx_config}" +pidfile=<%= node['nginx']['pid'] %> + +depend() { + need net + use dns logger netmount +} + +start_pre() { + if [ "${RC_CMD}" != "restart" ]; then + configtest || return 1 + fi +} + +stop_pre() { + if [ "${RC_CMD}" = "restart" ]; then + configtest || return 1 + fi +} + +stop_post() { + rm -f ${pidfile} +} + +reload() { + configtest || return 1 + ebegin "Refreshing nginx' configuration" + kill -HUP `cat ${pidfile}` &>/dev/null + eend $? "Failed to reload nginx" +} + +upgrade() { + configtest || return 1 + ebegin "Upgrading nginx" + + einfo "Sending USR2 to old binary" + kill -USR2 `cat ${pidfile}` &>/dev/null + + einfo "Sleeping 3 seconds before pid-files checking" + sleep 3 + + if [ ! -f ${pidfile}.oldbin ]; then + eerror "File with old pid not found" + return 1 + fi + + if [ ! -f ${pidfile} ]; then + eerror "New binary failed to start" + return 1 + fi + + einfo "Sleeping 3 seconds before WINCH" + sleep 3 ; kill -WINCH `cat ${pidfile}.oldbin` + + einfo "Sending QUIT to old binary" + kill -QUIT `cat ${pidfile}.oldbin` + + einfo "Upgrade completed" + eend $? "Upgrade failed" +} + +configtest() { + ebegin "Checking nginx' configuration" + ${command} -c ${nginx_config} -t -q + + if [ $? -ne 0 ]; then + ${command} -c ${nginx_config} -t + fi + + eend $? "failed, please correct errors above" +} diff --git a/cookbooks/nginx/templates/suse/nginx.init.erb b/cookbooks/nginx/templates/suse/nginx.init.erb new file mode 100644 index 0000000..e3e6ec9 --- /dev/null +++ b/cookbooks/nginx/templates/suse/nginx.init.erb @@ -0,0 +1,115 @@ +#!/bin/sh +# +# nginx +# +### BEGIN INIT INFO +# Provides: nginx +# Required-Start: $local_fs $remote_fs $network $syslog +# Required-Stop: $local_fs $remote_fs $network $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts the nginx web server +# Description: starts nginx using start-stop-daemon +### END INIT INFO + +# Source function library. +. /etc/rc.status + +rc_reset + +# Check that networking is up. +[ "$NETWORKING" = "no" ] && exit +exec=<%= node['nginx']['binary'] %> +prog=$(basename $exec) + +# default options, overruled by items in sysconfig +NGINX_GLOBAL="" + +[ -e /etc/sysconfig/nginx ] && . /etc/sysconfig/nginx + +lockfile=/var/lock/subsys/nginx + +start() { + [ -x $exec ] || exit 5 + echo -n $"Starting $prog: " + # if not running, start it up here, usually something like "daemon $exec" + options="" + if [ "${NGINX_GLOBAL}" != "" ]; then + options="-g ${NGINX_GLOBAL}" + fi + $exec $options + retval=$? + echo + [ $retval -eq 0 ] && touch $lockfile + rc_status -v +} + +stop() { + echo -n $"Stopping $prog: " + $exec -s stop + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile + rc_status -v +} + +restart() { + stop + start + rc_status +} + +reload() { + echo -n $"Reloading $prog: " + $exec -s reload + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile + rc_status -v +} + +configtest() { + if [ "$#" -ne 0 ] ; then + case "$1" in + -q) + FLAG=$1 + ;; + *) + ;; + esac + shift + fi + ${exec} -t $FLAG + RETVAL=$? + return $RETVAL +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status nginx + ;; + restart) + restart + ;; + reload|force-reload) + reload + ;; + condrestart) + [ -f $lockfile ] && restart || : + ;; + configtest) + configtest + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart|configtest}" + exit 1 +esac + +exit $? diff --git a/cookbooks/nginx/templates/ubuntu/nginx.init.erb b/cookbooks/nginx/templates/ubuntu/nginx.init.erb new file mode 100644 index 0000000..5a3711e --- /dev/null +++ b/cookbooks/nginx/templates/ubuntu/nginx.init.erb @@ -0,0 +1,97 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: nginx +# Required-Start: $local_fs $remote_fs $network $syslog +# Required-Stop: $local_fs $remote_fs $network $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts the nginx web server +# Description: starts nginx using start-stop-daemon +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=<%= node['nginx']['binary'] %> +NAME=nginx +DESC=nginx +PID=<%= node['nginx']['pid'] %> + +# Include nginx defaults if available +if [ -f /etc/default/nginx ]; then + . /etc/default/nginx +fi + +test -x $DAEMON || exit 0 + +set -e + +. /lib/lsb/init-functions + +test_nginx_config() { + if $DAEMON -t $DAEMON_OPTS >/dev/null 2>&1; then + return 0 + else + $DAEMON -t $DAEMON_OPTS + return $? + fi +} + +case "$1" in + start) + echo -n "Starting $DESC: " + test_nginx_config + # Check if the ULIMIT is set in /etc/default/nginx + if [ -n "$ULIMIT" ]; then + # Set the ulimits + ulimit $ULIMIT + fi + start-stop-daemon --start --quiet --pidfile $PID \ + --exec $DAEMON -- $DAEMON_OPTS || true + echo "$NAME." + ;; + + stop) + echo -n "Stopping $DESC: " + start-stop-daemon --stop --quiet --pidfile $PID \ + --exec $DAEMON || true + echo "$NAME." + ;; + + restart|force-reload) + echo -n "Restarting $DESC: " + start-stop-daemon --stop --quiet --pidfile \ + $PID --exec $DAEMON || true + sleep 1 + test_nginx_config + start-stop-daemon --start --quiet --pidfile \ + $PID --exec $DAEMON -- $DAEMON_OPTS || true + echo "$NAME." + ;; + + reload) + echo -n "Reloading $DESC configuration: " + test_nginx_config + start-stop-daemon --stop --signal HUP --quiet --pidfile $PID \ + --exec $DAEMON || true + echo "$NAME." + ;; + + configtest|testconfig) + echo -n "Testing $DESC configuration: " + if test_nginx_config; then + echo "$NAME." + else + exit $? + fi + ;; + + status) + status_of_proc -p $PID "$DAEMON" nginx && exit 0 || exit $? + ;; + *) + echo "Usage: $NAME {start|stop|restart|reload|force-reload|status|configtest}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/cookbooks/nodejs/CHANGELOG.md b/cookbooks/nodejs/CHANGELOG.md new file mode 100644 index 0000000..a159124 --- /dev/null +++ b/cookbooks/nodejs/CHANGELOG.md @@ -0,0 +1,66 @@ +## v2.0.0 (unreleased) + * Travis integration + * Gems updated + * Rewrite cookbook dependencies + * Added complete test-kitchen integration : Rake, rubocop, foodcritic, vagrant, bats testing ... + * Added NodeJS ```install_method``` option (sources, bins or packages) + * Added NPM ```install_method``` option (sources or packages) + * NPM version can now be chosen independently from nodejs' embedded version + * Added a ```nodejs_npm``` LWRP to manage, install and resolve NPM packages + +## v1.3.0 + * update default versions to the latest: node - v0.10.15 and npm - v1.3.5 + * default to package installation of nodejs on smartos ([@wanelo-pair][]) + * Add Raspberry pi support ([@robertkowalski][]) + +## v1.2.0 + * implement installation from package on RedHat - ([@vaskas][]) + +## v1.1.3: + * update default version of node to 0.10.13 - and npm - v1.3.4 ([@jodosha][]) + +## v1.1.2: + * update default version of node to 0.10.2 - ([@bakins][]) + * fully migrated to test-kitchen 1.alpha and vagrant 1.1.x/berkshelf 1.3.1 + +## v1.1.1: + * update default versions to the latest: node - v0.10.0 and npm - v1.2.14 + * `make_thread` is now a real attribute - ([@ChrisLundquist][]) + + +## v1.1.0: + * rewrite the package install; remove rpm support since there are no longer any packages available anywhere + * add support to install `legacy_packages` from ubuntu repo as well as the latest 0.10.x branch (this is default). + +## v1.0.4: + * add support for binary installation method ([@JulesAU][]) + +## v1.0.3: + - unreleased + +## v1.0.2: + * add smartos support for package install ([@sax][]) + * support to compile with all processors available (default 2 if unknown) - ([@ChrisLundquist][]) + * moved to `platform_family` syntax + * ensure npm recipe honours the 'source' or 'package' setting - ([@markbirbeck][]) + * updated the default versions to the latest stable node/npm + +## v1.0.1: + + * fixed bug that prevented overwritting the node/npm versions (moved the `src_url`s as local variables instead of attributes) - ([@johannesbecker][]) + * updated the default versions to the latest node/npm + +## v1.0.0: + +* added packages installation support ([@smith][]) + +[@JulesAU]: https://github.com/JulesAU +[@sax]: https://github.com/sax +[@ChrisLundquist]: https://github.com/ChrisLundquist +[@markbirbeck]: https://github.com/markbirbeck +[@johannesbecker]: https://github.com/johannesbecker +[@smith]: https://github.com/smith +[@bakins]: https://github.com/bakins +[@vaskas]: https://github.com/vaskas +[@robertkowalski]: https://github.com/robertkowalski +[@wanelo-pair]: https://github.com/wanelo-pair diff --git a/cookbooks/nodejs/README.md b/cookbooks/nodejs/README.md new file mode 100644 index 0000000..42b86d0 --- /dev/null +++ b/cookbooks/nodejs/README.md @@ -0,0 +1,145 @@ +# [nodejs-cookbook](https://github.com/redguide/nodejs) +[![CK Version](http://img.shields.io/cookbook/v/nodejs.svg)](https://supermarket.getchef.com/cookbooks/nodejs) [![Build Status](https://img.shields.io/travis/redguide/nodejs.svg)](https://travis-ci.org/redguide/nodejs) +[![Gitter chat](https://badges.gitter.im/redguide/nodejs.png)](https://gitter.im/redguide/nodejs) + +## DESCRIPTION + +Installs node.js/io.js and manage npm + +## USAGE + +Include the nodejs recipe to install node on your system based on the default installation method: +```chef +include_recipe "nodejs" +``` + +### Engine + +You can select different engine by setting `node['nodejs']['engine']` +``` +node['nodejs']['engine'] => 'node' # default +node['nodejs']['engine'] => 'iojs' +``` + +You can also use recipes `nodejs::nodejs` or `nodejs::iojs`. + +### Install methods + +#### Package + +Install node from packages: + +```chef +node['nodejs']['install_method'] = 'package' # Not necessary because it's the default +include_recipe "nodejs" +# Or +include_recipe "nodejs::nodejs_from_package" +``` +Note that only apt (Ubuntu, Debian) appears to have up to date packages available. +Centos, RHEL, etc are non-functional (try `nodejs_from_binary` for those). + +#### Binary + +Install node from official prebuilt binaries: +```chef +node['nodejs']['install_method'] = 'binary' +include_recipe "nodejs" +# Or +include_recipe "nodejs::nodejs_from_binary" +``` + +#### Source + +Install node from sources: +```chef +node['nodejs']['install_method'] = 'source' +include_recipe "nodejs" +# Or +include_recipe "nodejs::nodejs_from_source" +``` + +## NPM + +Npm is included in nodejs installs by default. +By default, we are using it and call it `embedded`. +Adding recipe `nodejs::npm` assure you to have npm installed and let you choose install method with `node['nodejs']['npm']['install_method']` +```chef +include_recipe "nodejs::npm" +``` +_Warning:_ This recipe will include the `nodejs` recipe, which by default includes `nodejs::nodejs_from_package` if you did not set `node['nodejs']['install_method']`. + +## LWRP + +### nodejs_npm + +`nodejs_npm` let you install npm packages from various sources: +* npm registry: + * name: `attribute :package` + * version: `attribute :version` (optionnal) +* url: `attribute :url` + * for git use `git://{your_repo}` +* from a json (packages.json by default): `attribute :json` + * use `true` for default + * use a `String` to specify json file + +Packages can be installed globally (by default) or in a directory (by using `attribute :path`) + +You can append more specific options to npm command with `attribute :options` array : + * use an array of options (w/ dash), they will be added to npm call. + * ex: `['--production','--force']` or `['--force-latest']` + +This LWRP try to use npm bare as much as possible (no custom wrapper). + +### Packages + +```ruby +nodejs_npm "express" + +nodejs_npm "async" do + version "0.6.2" +end + +nodejs_npm "request" do + url "github mikeal/request" +end + +nodejs_npm "grunt" do + path "/home/random/grunt" + json true + user "random" +end +``` +[Working Examples](test/cookbooks/nodejs_test/recipes/npm.rb) + +Or add packages via attributes (which accept the same attributes as the LWRP above): + +```json +"nodejs": { + "npm_packages": [ + { + "name": "express" + }, + { + "name": "async", + "version": "0.6.2" + }, + { + "name": "request", + "url": "github mikeal/request" + } + { + "name": "grunt", + "path": "/home/random/grunt", + "json": true, + "user": "random" + } + ] +} +``` + +## AUTHORS + +* Marius Ducea (marius@promethost.com) +* Nathan L Smith (nlloyds@gmail.com) +* Guilhem Lettron (guilhem@lettron.fr) +* Barthelemy Vessemont (bvessemont@gmail.com) diff --git a/cookbooks/nodejs/attributes/default.rb b/cookbooks/nodejs/attributes/default.rb new file mode 100644 index 0000000..da31cce --- /dev/null +++ b/cookbooks/nodejs/attributes/default.rb @@ -0,0 +1,45 @@ +# +# Cookbook Name:: nodejs +# Attributes:: nodejs +# +# Copyright 2010-2012, Promet Solutions +# +# 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. +# + +case node['platform_family'] +when 'smartos', 'rhel', 'debian', 'fedora', 'mac_os_x' + default['nodejs']['install_method'] = 'package' +else + default['nodejs']['install_method'] = 'source' +end + +default['nodejs']['engine'] = 'node' # or iojs + +default['nodejs']['version'] = '0.10.26' + +if node['nodejs']['engine'] == 'iojs' + default['nodejs']['prefix_url'] = 'http://iojs.org/dist/' +else + default['nodejs']['prefix_url'] = 'http://nodejs.org/dist/' +end + +default['nodejs']['source']['url'] = nil # Auto generated +default['nodejs']['source']['checksum'] = 'ef5e4ea6f2689ed7f781355012b942a2347e0299da0804a58de8e6281c4b1daa' + +default['nodejs']['binary']['url'] = nil # Auto generated +default['nodejs']['binary']['checksum']['linux_x64'] = '305bf2983c65edea6dd2c9f3669b956251af03523d31cf0a0471504fd5920aac' +default['nodejs']['binary']['checksum']['linux_x86'] = '8fa2d952556c8b5aa37c077e2735c972c522510facaa4df76d4244be88f4dc0f' +default['nodejs']['binary']['checksum']['linux_arm-pi'] = '52a0f6ed9c0be1ea5f79de6527c481c1b803edbea6413a4fdc65a45ad401565d' + +default['nodejs']['make_threads'] = node['cpu'] ? node['cpu']['total'].to_i : 2 diff --git a/cookbooks/nodejs/attributes/npm.rb b/cookbooks/nodejs/attributes/npm.rb new file mode 100644 index 0000000..30bb81e --- /dev/null +++ b/cookbooks/nodejs/attributes/npm.rb @@ -0,0 +1,3 @@ +default['nodejs']['npm']['install_method'] = 'embedded' + +default['nodejs']['npm']['version'] = 'latest' diff --git a/cookbooks/nodejs/attributes/packages.rb b/cookbooks/nodejs/attributes/packages.rb new file mode 100644 index 0000000..0152692 --- /dev/null +++ b/cookbooks/nodejs/attributes/packages.rb @@ -0,0 +1,14 @@ +include_attribute 'nodejs::default' +include_attribute 'nodejs::repo' + +case node['nodejs']['engine'] +when 'node' + default['nodejs']['packages'] = value_for_platform_family( + 'debian' => node['nodejs']['install_repo'] ? ['nodejs'] : ['nodejs', 'npm', 'nodejs-dev'], + ['rhel', 'fedora'] => ['nodejs', 'nodejs-devel', 'npm'], + 'mac_os_x' => ['node'], + 'default' => ['nodejs'] + ) +when 'iojs' + default['nodejs']['packages'] = nil +end diff --git a/cookbooks/nodejs/attributes/repo.rb b/cookbooks/nodejs/attributes/repo.rb new file mode 100644 index 0000000..596509c --- /dev/null +++ b/cookbooks/nodejs/attributes/repo.rb @@ -0,0 +1,13 @@ +case node['nodejs']['engine'] +when 'node' + case node['platform_family'] + when 'debian' + default['nodejs']['install_repo'] = true + + default['nodejs']['repo'] = 'https://deb.nodesource.com/node' + default['nodejs']['keyserver'] = 'keyserver.ubuntu.com' + default['nodejs']['key'] = '1655a0ab68576280' + when 'rhel' + default['nodejs']['install_repo'] = true + end +end diff --git a/cookbooks/nodejs/libraries/matchers.rb b/cookbooks/nodejs/libraries/matchers.rb new file mode 100644 index 0000000..deaa654 --- /dev/null +++ b/cookbooks/nodejs/libraries/matchers.rb @@ -0,0 +1,9 @@ +if defined?(ChefSpec) + def install_nodejs_npm(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:nodejs_npm, :install, resource_name) + end + + def uninstall_nodejs_npm(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:nodejs_npm, :uninstall, resource_name) + end +end diff --git a/cookbooks/nodejs/libraries/nodejs_helper.rb b/cookbooks/nodejs/libraries/nodejs_helper.rb new file mode 100644 index 0000000..46964a0 --- /dev/null +++ b/cookbooks/nodejs/libraries/nodejs_helper.rb @@ -0,0 +1,33 @@ +module NodeJs + module Helper + def npm_dist + if node['nodejs']['npm']['url'] + return { 'url' => node['nodejs']['npm']['url'] } + else + + require 'open-uri' + require 'json' + result = JSON.parse(URI.parse("https://registry.npmjs.org/npm/#{node['nodejs']['npm']['version']}").read, :max_nesting => false) + ret = { 'url' => result['dist']['tarball'], 'version' => result['_npmVersion'], 'shasum' => result['dist']['shasum'] } + Chef::Log.debug("Npm dist #{ret}") + return ret + end + end + + def npm_list(path = nil) + require 'json' + if path + cmd = Mixlib::ShellOut.new('npm list -json', :cwd => path) + else + cmd = Mixlib::ShellOut.new('npm list -global -json') + end + JSON.parse(cmd.run_command.stdout, :max_nesting => false) + end + + def npm_package_installed?(package, version = nil, path = nil) + list = npm_list(path)['dependencies'] + # Return true if package installed and installed to good version + (!list.nil?) && list.key?(package) && (version ? list[package]['version'] == version : true) + end + end +end diff --git a/cookbooks/nodejs/metadata.json b/cookbooks/nodejs/metadata.json new file mode 100644 index 0000000..07de793 --- /dev/null +++ b/cookbooks/nodejs/metadata.json @@ -0,0 +1,42 @@ +{ + "name": "nodejs", + "version": "2.4.0", + "description": "Installs/Configures node.js & io.js", + "long_description": "# [nodejs-cookbook](https://github.com/redguide/nodejs)\n[![CK Version](http://img.shields.io/cookbook/v/nodejs.svg)](https://supermarket.getchef.com/cookbooks/nodejs) [![Build Status](https://img.shields.io/travis/redguide/nodejs.svg)](https://travis-ci.org/redguide/nodejs)\n[![Gitter chat](https://badges.gitter.im/redguide/nodejs.png)](https://gitter.im/redguide/nodejs)\n\n## DESCRIPTION\n\nInstalls node.js/io.js and manage npm\n\n## USAGE\n\nInclude the nodejs recipe to install node on your system based on the default installation method:\n```chef\ninclude_recipe \"nodejs\"\n```\n\n### Engine\n\nYou can select different engine by setting `node['nodejs']['engine']`\n```\nnode['nodejs']['engine'] => 'node' # default\nnode['nodejs']['engine'] => 'iojs'\n```\n\nYou can also use recipes `nodejs::nodejs` or `nodejs::iojs`.\n\n### Install methods\n\n#### Package\n\nInstall node from packages:\n\n```chef\nnode['nodejs']['install_method'] = 'package' # Not necessary because it's the default\ninclude_recipe \"nodejs\"\n# Or\ninclude_recipe \"nodejs::nodejs_from_package\"\n```\nNote that only apt (Ubuntu, Debian) appears to have up to date packages available. \nCentos, RHEL, etc are non-functional (try `nodejs_from_binary` for those).\n\n#### Binary\n\nInstall node from official prebuilt binaries:\n```chef\nnode['nodejs']['install_method'] = 'binary'\ninclude_recipe \"nodejs\"\n# Or\ninclude_recipe \"nodejs::nodejs_from_binary\"\n```\n\n#### Source\n\nInstall node from sources:\n```chef\nnode['nodejs']['install_method'] = 'source'\ninclude_recipe \"nodejs\"\n# Or\ninclude_recipe \"nodejs::nodejs_from_source\"\n```\n\n## NPM\n\nNpm is included in nodejs installs by default.\nBy default, we are using it and call it `embedded`.\nAdding recipe `nodejs::npm` assure you to have npm installed and let you choose install method with `node['nodejs']['npm']['install_method']`\n```chef\ninclude_recipe \"nodejs::npm\"\n```\n_Warning:_ This recipe will include the `nodejs` recipe, which by default includes `nodejs::nodejs_from_package` if you did not set `node['nodejs']['install_method']`.\n\n## LWRP\n\n### nodejs_npm\n\n`nodejs_npm` let you install npm packages from various sources:\n* npm registry:\n * name: `attribute :package`\n * version: `attribute :version` (optionnal)\n* url: `attribute :url`\n * for git use `git://{your_repo}`\n* from a json (packages.json by default): `attribute :json`\n * use `true` for default\n * use a `String` to specify json file\n \nPackages can be installed globally (by default) or in a directory (by using `attribute :path`)\n\nYou can append more specific options to npm command with `attribute :options` array : \n * use an array of options (w/ dash), they will be added to npm call.\n * ex: `['--production','--force']` or `['--force-latest']`\n \nThis LWRP try to use npm bare as much as possible (no custom wrapper).\n\n### Packages\n\n```ruby\nnodejs_npm \"express\"\n\nnodejs_npm \"async\" do\n version \"0.6.2\"\nend\n\nnodejs_npm \"request\" do\n url \"github mikeal/request\"\nend\n\nnodejs_npm \"grunt\" do\n path \"/home/random/grunt\"\n json true\n user \"random\"\nend\n```\n[Working Examples](test/cookbooks/nodejs_test/recipes/npm.rb)\n\nOr add packages via attributes (which accept the same attributes as the LWRP above):\n\n```json\n\"nodejs\": {\n \"npm_packages\": [\n {\n \"name\": \"express\"\n },\n {\n \"name\": \"async\",\n \"version\": \"0.6.2\"\n },\n {\n \"name\": \"request\",\n \"url\": \"github mikeal/request\"\n }\n {\n \"name\": \"grunt\",\n \"path\": \"/home/random/grunt\",\n \"json\": true,\n \"user\": \"random\"\n }\n ]\n}\n```\n\n## AUTHORS\n\n* Marius Ducea (marius@promethost.com)\n* Nathan L Smith (nlloyds@gmail.com)\n* Guilhem Lettron (guilhem@lettron.fr)\n* Barthelemy Vessemont (bvessemont@gmail.com)\n", + "maintainer": "redguide", + "maintainer_email": "guilhem@lettron.fr", + "license": "Apache 2.0", + "platforms": { + "debian": ">= 0.0.0", + "ubuntu": ">= 0.0.0", + "centos": ">= 0.0.0", + "redhat": ">= 0.0.0", + "smartos": ">= 0.0.0", + "mac_os_x": ">= 0.0.0" + }, + "dependencies": { + "yum-epel": ">= 0.0.0", + "build-essential": ">= 0.0.0", + "ark": ">= 0.0.0", + "apt": ">= 0.0.0", + "homebrew": ">= 0.0.0" + }, + "recommendations": { + }, + "suggestions": { + "application_nodejs": ">= 0.0.0" + }, + "conflicting": { + "node": ">= 0.0.0" + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + } +} \ No newline at end of file diff --git a/cookbooks/nodejs/providers/npm.rb b/cookbooks/nodejs/providers/npm.rb new file mode 100644 index 0000000..b5ca1c1 --- /dev/null +++ b/cookbooks/nodejs/providers/npm.rb @@ -0,0 +1,55 @@ +include NodeJs::Helper + +use_inline_resources if defined?(use_inline_resources) + +action :install do + execute "install NPM package #{new_resource.name}" do + cwd new_resource.path + command "npm install #{npm_options}" + user new_resource.user + group new_resource.group + environment 'HOME' => ::Dir.home(new_resource.user), 'USER' => new_resource.user if new_resource.user + not_if { package_installed? } + end +end + +action :uninstall do + execute "uninstall NPM package #{new_resource.package}" do + cwd new_resource.path + command "npm uninstall #{npm_options}" + user new_resource.user + group new_resource.group + environment 'HOME' => ::Dir.home(new_resource.user), 'USER' => new_resource.user if new_resource.user + only_if { package_installed? } + end +end + +def package_installed? + new_resource.package && npm_package_installed?(new_resource.package, new_resource.version, new_resource.path) +end + +def npm_options + options = '' + options << ' -global' unless new_resource.path + new_resource.options.each do |option| + options << " #{option}" + end + options << " #{npm_package}" +end + +def npm_package + if new_resource.json + return new_resource.json.is_a?(String) ? new_resource.json : nil + elsif new_resource.url + return new_resource.url + elsif new_resource.package + return new_resource.version ? "#{new_resource.package}@#{new_resource.version}" : new_resource.package + else + Chef::Log.error("No good options found to install #{new_resource.name}") + end +end + +def initialize(*args) + super + @run_context.include_recipe 'nodejs::npm' +end diff --git a/cookbooks/nodejs/recipes/default.rb b/cookbooks/nodejs/recipes/default.rb new file mode 100644 index 0000000..2d90414 --- /dev/null +++ b/cookbooks/nodejs/recipes/default.rb @@ -0,0 +1,23 @@ +# +# Author:: Marius Ducea (marius@promethost.com) +# Cookbook Name:: nodejs +# Recipe:: default +# +# Copyright 2010-2012, Promet Solutions +# +# 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. +# + +include_recipe 'nodejs::install' +include_recipe 'nodejs::npm' +include_recipe 'nodejs::npm_packages' diff --git a/cookbooks/nodejs/recipes/install.rb b/cookbooks/nodejs/recipes/install.rb new file mode 100644 index 0000000..962e77d --- /dev/null +++ b/cookbooks/nodejs/recipes/install.rb @@ -0,0 +1,21 @@ +# +# Author:: Marius Ducea (marius@promethost.com) +# Cookbook Name:: nodejs +# Recipe:: install +# +# Copyright 2010-2012, Promet Solutions +# +# 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. +# + +include_recipe "nodejs::nodejs_from_#{node['nodejs']['install_method']}" diff --git a/cookbooks/nodejs/recipes/iojs.rb b/cookbooks/nodejs/recipes/iojs.rb new file mode 100644 index 0000000..744731c --- /dev/null +++ b/cookbooks/nodejs/recipes/iojs.rb @@ -0,0 +1,23 @@ +# +# Author:: Marius Ducea (marius@promethost.com) +# Cookbook Name:: nodejs +# Recipe:: iojs +# +# Copyright 2010-2012, Promet Solutions +# +# 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. +# + +node.default['nodejs']['engine'] = 'iojs' + +include_recipe 'nodejs::install' diff --git a/cookbooks/nodejs/recipes/nodejs.rb b/cookbooks/nodejs/recipes/nodejs.rb new file mode 100644 index 0000000..1b3a20c --- /dev/null +++ b/cookbooks/nodejs/recipes/nodejs.rb @@ -0,0 +1,23 @@ +# +# Author:: Marius Ducea (marius@promethost.com) +# Cookbook Name:: nodejs +# Recipe:: nodejs +# +# Copyright 2010-2012, Promet Solutions +# +# 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. +# + +node.default['nodejs']['engine'] = 'node' + +include_recipe 'nodejs::install' diff --git a/cookbooks/nodejs/recipes/nodejs_from_binary.rb b/cookbooks/nodejs/recipes/nodejs_from_binary.rb new file mode 100644 index 0000000..23fb9d9 --- /dev/null +++ b/cookbooks/nodejs/recipes/nodejs_from_binary.rb @@ -0,0 +1,58 @@ +# +# Author:: Julian Wilde (jules@jules.com.au) +# Cookbook Name:: nodejs +# Recipe:: install_from_binary +# +# 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. +# + +Chef::Recipe.send(:include, NodeJs::Helper) + +node.force_override['nodejs']['install_method'] = 'binary' # ~FC019 + +# Shamelessly borrowed from http://docs.opscode.com/dsl_recipe_method_platform.html +# Surely there's a more canonical way to get arch? +if node['kernel']['machine'] =~ /armv6l/ + arch = 'arm-pi' # assume a raspberry pi +else + arch = node['kernel']['machine'] =~ /x86_64/ ? 'x64' : 'x86' +end + +# package_stub is for example: "node-v0.8.20-linux-x64.tar.gz" +version = "v#{node['nodejs']['version']}/" + +if node['nodejs']['engine'] == 'iojs' + filename = "iojs-v#{node['nodejs']['version']}-linux-#{arch}.tar.gz" + archive_name = 'iojs-binary' + binaries = ['bin/iojs', 'bin/node', 'bin/npm'] +else + filename = "node-v#{node['nodejs']['version']}-linux-#{arch}.tar.gz" + archive_name = 'nodejs-binary' + binaries = ['bin/node', 'bin/npm'] +end + +if node['nodejs']['binary']['url'] + nodejs_bin_url = node['nodejs']['binary']['url'] + checksum = node['nodejs']['binary']['checksum'] +else + nodejs_bin_url = ::URI.join(node['nodejs']['prefix_url'], version, filename).to_s + checksum = node['nodejs']['binary']['checksum']["linux_#{arch}"] +end + +ark archive_name do + url nodejs_bin_url + version node['nodejs']['version'] + checksum checksum + has_binaries binaries + action :install +end diff --git a/cookbooks/nodejs/recipes/nodejs_from_package.rb b/cookbooks/nodejs/recipes/nodejs_from_package.rb new file mode 100644 index 0000000..3481239 --- /dev/null +++ b/cookbooks/nodejs/recipes/nodejs_from_package.rb @@ -0,0 +1,35 @@ +# +# Author:: Nathan L Smith (nlloyds@gmail.com) +# Author:: Marius Ducea (marius@promethost.com) +# Cookbook Name:: nodejs +# Recipe:: package +# +# Copyright 2012, Cramer Development, Inc. +# Copyright 2013, Opscale +# +# 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. +# + +node.force_override['nodejs']['install_method'] = 'package' # ~FC019 + +include_recipe 'nodejs::repo' if node['nodejs']['install_repo'] + +unless node['nodejs']['packages'] + Chef::Log.error 'No package for nodejs' + Chef::Log.warn 'Please use the source or binary method to install node' + return +end + +node['nodejs']['packages'].each do |node_pkg| + package node_pkg +end diff --git a/cookbooks/nodejs/recipes/nodejs_from_source.rb b/cookbooks/nodejs/recipes/nodejs_from_source.rb new file mode 100644 index 0000000..eef1924 --- /dev/null +++ b/cookbooks/nodejs/recipes/nodejs_from_source.rb @@ -0,0 +1,52 @@ +# +# Author:: Marius Ducea (marius@promethost.com) +# Cookbook Name:: nodejs +# Recipe:: source +# +# Copyright 2010-2012, Promet Solutions +# +# 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. +# + +Chef::Recipe.send(:include, NodeJs::Helper) + +node.force_override['nodejs']['install_method'] = 'source' # ~FC019 + +include_recipe 'build-essential' + +case node['platform_family'] +when 'rhel', 'fedora' + package 'openssl-devel' +when 'debian' + package 'libssl-dev' +end + +version = "v#{node['nodejs']['version']}/" + +if node['nodejs']['engine'] == 'iojs' + filename = "iojs-v#{node['nodejs']['version']}.tar.gz" + archive_name = 'iojs-source' +else + filename = "node-v#{node['nodejs']['version']}.tar.gz" + archive_name = 'nodejs-source' +end + +nodejs_src_url = node['nodejs']['source']['url'] || ::URI.join(node['nodejs']['prefix_url'], version, filename).to_s + +ark archive_name do + url nodejs_src_url + version node['nodejs']['version'] + checksum node['nodejs']['source']['checksum'] + make_opts ["-j #{node['nodejs']['make_threads']}"] + action :install_with_make +end diff --git a/cookbooks/nodejs/recipes/npm.rb b/cookbooks/nodejs/recipes/npm.rb new file mode 100644 index 0000000..b8ba5be --- /dev/null +++ b/cookbooks/nodejs/recipes/npm.rb @@ -0,0 +1,28 @@ +# +# Author:: Marius Ducea (marius@promethost.com) +# Cookbook Name:: nodejs +# Recipe:: npm +# +# Copyright 2010-2012, Promet Solutions +# +# 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. +# + +case node['nodejs']['npm']['install_method'] +when 'embedded' + include_recipe 'nodejs::install' +when 'source' + include_recipe 'nodejs::npm_from_source' +else + Chef::Log.error('No install method found for npm') +end diff --git a/cookbooks/nodejs/recipes/npm_from_source.rb b/cookbooks/nodejs/recipes/npm_from_source.rb new file mode 100644 index 0000000..aa42184 --- /dev/null +++ b/cookbooks/nodejs/recipes/npm_from_source.rb @@ -0,0 +1,34 @@ +# +# Author:: Marius Ducea (marius@promethost.com) +# Cookbook Name:: nodejs +# Recipe:: npm +# +# Copyright 2010-2012, Promet Solutions +# +# 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. +# + +Chef::Recipe.send(:include, NodeJs::Helper) + +node.force_override['nodejs']['npm']['install_method'] = 'source' # ~FC019 + +include_recipe 'nodejs::install' + +dist = npm_dist + +ark 'npm' do + url dist['url'] + checksum dist['checksum'] + version dist['version'] + action :install_with_make +end diff --git a/cookbooks/nodejs/recipes/npm_packages.rb b/cookbooks/nodejs/recipes/npm_packages.rb new file mode 100644 index 0000000..b572801 --- /dev/null +++ b/cookbooks/nodejs/recipes/npm_packages.rb @@ -0,0 +1,10 @@ +node['nodejs']['npm_packages'].each do |pkg| + f = nodejs_npm pkg['name'] do + action :nothing + end + pkg.each do |key, value| + f.send(key, value) unless key == 'name' || key == 'action' + end + action = pkg.key?('action') ? pkg['action'] : :install + f.action(action) +end if node['nodejs'].key?('npm_packages') diff --git a/cookbooks/nodejs/recipes/repo.rb b/cookbooks/nodejs/recipes/repo.rb new file mode 100644 index 0000000..7d4bc3f --- /dev/null +++ b/cookbooks/nodejs/recipes/repo.rb @@ -0,0 +1,16 @@ +case node['platform_family'] +when 'debian' + include_recipe 'apt' + + package 'apt-transport-https' + + apt_repository 'node.js' do + uri node['nodejs']['repo'] + distribution node['lsb']['codename'] + components ['main'] + keyserver node['nodejs']['keyserver'] + key node['nodejs']['key'] + end +when 'rhel' + include_recipe 'yum-epel' +end diff --git a/cookbooks/nodejs/resources/npm.rb b/cookbooks/nodejs/resources/npm.rb new file mode 100644 index 0000000..f873f93 --- /dev/null +++ b/cookbooks/nodejs/resources/npm.rb @@ -0,0 +1,33 @@ +# +# Cookbook Name:: nodejs +# Resource:: npm +# +# Author:: Sergey Balbeko +# +# Copyright 2012, Sergey Balbeko +# +# 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. +# + +actions :install, :uninstall +default_action :install + +attribute :package, :name_attribute => true +attribute :version, :kind_of => String +attribute :path, :kind_of => String +attribute :url, :kind_of => String +attribute :json, :kind_of => [String, TrueClass] +attribute :options, :kind_of => Array, :default => [] + +attribute :user, :kind_of => String +attribute :group, :kind_of => String diff --git a/cookbooks/ohai/CHANGELOG.md b/cookbooks/ohai/CHANGELOG.md new file mode 100644 index 0000000..1a903a1 --- /dev/null +++ b/cookbooks/ohai/CHANGELOG.md @@ -0,0 +1,51 @@ +ohai Cookbook CHANGELOG +======================= +This file is used to list changes made in each version of the ohai cookbook. + + +v2.0.1 (2014-06-07) +------------------- +* [COOK-4683] Remove warnings about reopening resource + +Please note, this changes the name of a remote_directory resource. It is not expected that anyone would be explicitly notifying this resource but, please review [PR #16](https://github.com/opscode-cookbooks/ohai/pull/16/files) for more info. + + +v2.0.0 (2014-02-25) +------------------- +'[COOK-3865] - create lwrp ohai_hint' + + +v1.1.12 +------- +- Dummy release due to a Community Site upload failure + +v1.1.10 +------- +### Bug +- **[COOK-3091](https://tickets.opscode.com/browse/COOK-3091)** - Fix checking `Chef::Config[:config_file]` + +v1.1.8 +------ +- [COOK-1918] - Ohai cookbook to distribute plugins fails on windows +- [COOK-2096] - Ohai cookbook sets unix-only default path attribute + +v1.1.6 +------ +- [COOK-2057] - distribution from another cookbok fails if ohai attributes are loaded after the other cookbook + +v1.1.4 +------ +- [COOK-1128] - readme update, Replace reference to deprecated chef cookbook with one to chef-client + +v1.1.2 +------ +- [COOK-1424] - prevent plugin_path growth to infinity + +v1.1.0 +------ +- [COOK-1174] - custom_plugins is only conditionally available +- [COOK-1383] - allow plugins from other cookbooks + +v1.0.2 +------ +- [COOK-463] ohai cookbook default recipe should only reload plugins if there were updates diff --git a/cookbooks/ohai/README.md b/cookbooks/ohai/README.md new file mode 100644 index 0000000..1734390 --- /dev/null +++ b/cookbooks/ohai/README.md @@ -0,0 +1,68 @@ +ohai Cookbook +============= +Creates a configured plugin path for distributing custom Ohai plugins, and reloads them via Ohai within the context of a Chef Client run during the compile phase (if needed). + + +Attributes +---------- +- `node['ohai']['plugin_path']` - location to drop off plugins directory, default is `/etc/chef/ohai_plugins`. This is not FHS-compliant, an FHS location would be something like `/var/lib/ohai/plugins`, or `/var/lib/chef/ohai_plugins` or similar. + + Neither an FHS location or the default value of this attribute are in the default Ohai plugin path. Set the Ohai plugin path with the config setting "`Ohai::Config[:plugin_path]`" in the Chef config file (the `chef-client::config` recipe does this automatically for you!). The attribute is not set to the default plugin path that Ohai ships with because we don't want to risk destroying existing essential plugins for Ohai. + +- `node['ohai']['plugins']` - sources of plugins, defaults to the `files/default/plugins` directory of this cookbook. You can add additional cookbooks by adding the name of the cookbook as a key and the path of the files directory as the value. You have to make sure that you don't have any file conflicts between multiple cookbooks. The last one to write wins. + +- `node['ohai']['hints_path']` - location to drop off hints directory, default is `/etc/chef/ohai/hints`. + +Usage +----- +Put the recipe `ohai` at the start of the node's run list to make sure that custom plugins are loaded early on in the Chef run and data is available for later recipes. + +The execution of the custom plugins occurs within the recipe during the compile phase, so you can write new plugins and use the data they return in your Chef recipes. + +For information on how to write custom plugins for Ohai, please see the Chef wiki pages. + +http://wiki.opscode.com/display/chef/Writing+Ohai+Plugins + +*PLEASE NOTE* - This recipe reloads the Ohai plugins a 2nd time during the Chef run if: + +* The "`Ohai::Config[:plugin_path]`" config setting has *NOT* been properly set in the Chef config file +- The "`Ohai::Config[:plugin_path]`" config setting has been properly set in the Chef config file and there are updated plugins dropped off at "`node['ohai']['plugin_path']`". + +LWRP +---- + +### `ohai_hint` + +Create hints file. You can find usage examples at `test/cookbooks/ohai_test/recipes/*.rb`. + +#### Resource Attributes + + - `hint_name` - The name of hints file and key. Should be string, default is name of resource. + - `content` - Values of hints. It will be used as automatic attributes. Should be Hash, default is empty Hash class. + + +Example +------- +For an example implementation, inspect the ohai_plugin.rb recipe in the nginx community cookbook. + + +License & Authors +----------------- +- Author:: Joshua Timberman () +- Author:: Seth Chisamore () + +```text +Copyright:: 2010-2011, Opscode, Inc + +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/cookbooks/ohai/attributes/default.rb b/cookbooks/ohai/attributes/default.rb new file mode 100644 index 0000000..4b463bc --- /dev/null +++ b/cookbooks/ohai/attributes/default.rb @@ -0,0 +1,31 @@ +# +# Cookbook Name:: ohai +# Attribute:: default +# +# Copyright 2010, Opscode, Inc +# +# 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. +# + +# FHS location would be /var/lib/chef/ohai_plugins or similar. +case node["platform_family"] +when "windows" + default["ohai"]["plugin_path"] = "C:/chef/ohai_plugins" + default["ohai"]["hints_path"] = "C:/chef/ohai/hints" +else + default["ohai"]["plugin_path"] = "/etc/chef/ohai_plugins" + default["ohai"]["hints_path"] = "/etc/chef/ohai/hints" +end + +# The list of plugins and their respective file locations +default["ohai"]["plugins"]["ohai"] = "plugins" diff --git a/cookbooks/ohai/files/default/plugins/README b/cookbooks/ohai/files/default/plugins/README new file mode 100644 index 0000000..72f12e3 --- /dev/null +++ b/cookbooks/ohai/files/default/plugins/README @@ -0,0 +1 @@ +This directory contains custom plugins for Ohai. diff --git a/cookbooks/ohai/metadata.json b/cookbooks/ohai/metadata.json new file mode 100644 index 0000000..31065ad --- /dev/null +++ b/cookbooks/ohai/metadata.json @@ -0,0 +1,46 @@ +{ + "name": "ohai", + "version": "2.0.1", + "description": "Distributes a directory of custom ohai plugins", + "long_description": "ohai Cookbook\n=============\nCreates a configured plugin path for distributing custom Ohai plugins, and reloads them via Ohai within the context of a Chef Client run during the compile phase (if needed).\n\n\nAttributes\n----------\n- `node['ohai']['plugin_path']` - location to drop off plugins directory, default is `/etc/chef/ohai_plugins`. This is not FHS-compliant, an FHS location would be something like `/var/lib/ohai/plugins`, or `/var/lib/chef/ohai_plugins` or similar.\n\n Neither an FHS location or the default value of this attribute are in the default Ohai plugin path. Set the Ohai plugin path with the config setting \"`Ohai::Config[:plugin_path]`\" in the Chef config file (the `chef-client::config` recipe does this automatically for you!). The attribute is not set to the default plugin path that Ohai ships with because we don't want to risk destroying existing essential plugins for Ohai.\n\n- `node['ohai']['plugins']` - sources of plugins, defaults to the `files/default/plugins` directory of this cookbook. You can add additional cookbooks by adding the name of the cookbook as a key and the path of the files directory as the value. You have to make sure that you don't have any file conflicts between multiple cookbooks. The last one to write wins.\n\n- `node['ohai']['hints_path']` - location to drop off hints directory, default is `/etc/chef/ohai/hints`.\n\nUsage\n-----\nPut the recipe `ohai` at the start of the node's run list to make sure that custom plugins are loaded early on in the Chef run and data is available for later recipes.\n\nThe execution of the custom plugins occurs within the recipe during the compile phase, so you can write new plugins and use the data they return in your Chef recipes.\n\nFor information on how to write custom plugins for Ohai, please see the Chef wiki pages.\n\nhttp://wiki.opscode.com/display/chef/Writing+Ohai+Plugins\n\n*PLEASE NOTE* - This recipe reloads the Ohai plugins a 2nd time during the Chef run if:\n\n* The \"`Ohai::Config[:plugin_path]`\" config setting has *NOT* been properly set in the Chef config file\n- The \"`Ohai::Config[:plugin_path]`\" config setting has been properly set in the Chef config file and there are updated plugins dropped off at \"`node['ohai']['plugin_path']`\".\n\nLWRP\n----\n\n### `ohai_hint`\n\nCreate hints file. You can find usage examples at `test/cookbooks/ohai_test/recipes/*.rb`.\n\n#### Resource Attributes\n\n - `hint_name` - The name of hints file and key. Should be string, default is name of resource.\n - `content` - Values of hints. It will be used as automatic attributes. Should be Hash, default is empty Hash class.\n\n\nExample\n-------\nFor an example implementation, inspect the ohai_plugin.rb recipe in the nginx community cookbook.\n\n\nLicense & Authors\n-----------------\n- Author:: Joshua Timberman ()\n- Author:: Seth Chisamore ()\n\n```text\nCopyright:: 2010-2011, Opscode, Inc\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "maintainer": "Opscode, Inc", + "maintainer_email": "cookbooks@opscode.com", + "license": "Apache 2.0", + "platforms": { + }, + "dependencies": { + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + "ohai/plugin_path": { + "display_name": "Ohai Plugin Path", + "description": "Distribute plugins to this path.", + "type": "string", + "required": "optional", + "default": "/etc/chef/ohai_plugins" + }, + "ohai/plugins": { + "display_name": "Ohai Plugin Sources", + "description": "Read plugins from these cookbooks and paths", + "type": "hash", + "required": "optional", + "default": { + "ohai": "plugins" + } + } + }, + "groupings": { + }, + "recipes": { + "ohai::default": "Distributes a directory of custom ohai plugins" + } +} \ No newline at end of file diff --git a/cookbooks/ohai/metadata.rb b/cookbooks/ohai/metadata.rb new file mode 100644 index 0000000..82b7e63 --- /dev/null +++ b/cookbooks/ohai/metadata.rb @@ -0,0 +1,23 @@ +name "ohai" +maintainer "Opscode, Inc" +maintainer_email "cookbooks@opscode.com" +license "Apache 2.0" +description "Distributes a directory of custom ohai plugins" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version "2.0.1" + +recipe "ohai::default", "Distributes a directory of custom ohai plugins" + +attribute "ohai/plugin_path", + :display_name => "Ohai Plugin Path", + :description => "Distribute plugins to this path.", + :type => "string", + :required => "optional", + :default => "/etc/chef/ohai_plugins" + +attribute "ohai/plugins", + :display_name => "Ohai Plugin Sources", + :description => "Read plugins from these cookbooks and paths", + :type => "hash", + :required => "optional", + :default => {'ohai' => 'plugins'} diff --git a/cookbooks/ohai/providers/hint.rb b/cookbooks/ohai/providers/hint.rb new file mode 100644 index 0000000..391032a --- /dev/null +++ b/cookbooks/ohai/providers/hint.rb @@ -0,0 +1,35 @@ +def why_run_supported? + true +end + +def build_ohai_hint_path + ::File.join(node[:ohai][:hints_path], "#{new_resource.name}.json") +end + +use_inline_resources + +action :create do + if @current_resource.content != new_resource.content + directory node[:ohai][:hints_path] do + action :create + recursive true + end + + file build_ohai_hint_path do + action :create + content JSON.pretty_generate(new_resource.content) + end + end +end + + +def load_current_resource + @current_resource = Chef::Resource::OhaiHint.new(new_resource.name) + if ::File.exist?(build_ohai_hint_path) + @current_resource.content(JSON.parse(::File.read(build_ohai_hint_path))) + else + @current_resource.content(nil) + end + + @current_resource +end diff --git a/cookbooks/ohai/recipes/default.rb b/cookbooks/ohai/recipes/default.rb new file mode 100644 index 0000000..204addd --- /dev/null +++ b/cookbooks/ohai/recipes/default.rb @@ -0,0 +1,55 @@ +# +# Cookbook Name:: ohai +# Recipe:: default +# +# Copyright 2011, Opscode, Inc +# +# 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. +# + +reload_ohai = false +# Add plugin_path from node attributes if missing, and ensure a reload of +# ohai in that case +unless Ohai::Config[:plugin_path].include?(node['ohai']['plugin_path']) + Ohai::Config[:plugin_path] = [node['ohai']['plugin_path'], Ohai::Config[:plugin_path]].flatten.compact + reload_ohai ||= true +end +Chef::Log.info("ohai plugins will be at: #{node['ohai']['plugin_path']}") + +# This is done during the compile phase so new plugins can be used in +# resources later in the run. +node['ohai']['plugins'].each_pair do |source_cookbook, path| + + rd = remote_directory "#{node['ohai']['plugin_path']} for cookbook #{source_cookbook}" do + path node['ohai']['plugin_path'] + cookbook source_cookbook + source path + mode '0755' unless platform_family?('windows') + recursive true + purge false + action :nothing + end + + rd.run_action(:create) + reload_ohai ||= rd.updated? +end + +resource = ohai 'custom_plugins' do + action :nothing +end + +# Reload ohai if the client's plugin_path did not contain +# node['ohai']['plugin_path'], or new plugins were loaded +if reload_ohai + resource.run_action(:reload) +end diff --git a/cookbooks/ohai/resources/hint.rb b/cookbooks/ohai/resources/hint.rb new file mode 100644 index 0000000..1922a58 --- /dev/null +++ b/cookbooks/ohai/resources/hint.rb @@ -0,0 +1,6 @@ +actions :create, :delete +default_action :create + + +attribute :hint_name, :kind_of => String, :name_attribute => true +attribute :content, :kind_of => Hash, :default => {} diff --git a/cookbooks/omnibus_updater/CHANGELOG.md b/cookbooks/omnibus_updater/CHANGELOG.md new file mode 100644 index 0000000..d29acac --- /dev/null +++ b/cookbooks/omnibus_updater/CHANGELOG.md @@ -0,0 +1,84 @@ +v1.0.4 +====== +* file_cache_path path to store chef-client +* Avoid deleting chef-server packages if using the same cache dir +* Only backup the last old chef client file +* make sure directory exists before trying to write to it + +v1.0.2 +====== +* Maintenance updates +* Support for Fedora +* omnitrucker solaris update +* bug fixes + +v1.0.0 +====== +* Breaking change: `:always_download` is now defaulted to false +* Add solaris package install support (#37 thanks @jtimberman) +* Update notifies/subscribes usage to support older Chefs (#38 thanks @spheromak) + +v0.2.8 +====== +* Always download the package (thanks @miketheman for swiftly pointing out the issue!) + +v0.2.6 +====== +* Work with amazon linux (thanks @thommay) +* Disable updates on debian 5 (thanks @ianand0204) +* Only use major version on debian systems (thanks @kvs) +* Allow prevention of downgrades (thanks @buysse) +* Add support for restarting chef service after upgrade (thanks @andrewfraley) + +v0.2.4 +====== +* Only download omnibus package if version difference detected (#20 #22 #23) +* Provide attribute for always downloading package even if version matches + +v0.2.3 +====== +* Use chef internals for interactions with omnitruck to provide proper proxy support (#19) + +v0.2.0 +====== +* Use omnitruck client for url generation for package fetching +* Use `prerelease` in favor of `allow_release_clients` + +v0.1.2 +====== +* Fix regression on debian package path construction (thanks [ashmere](https://github.com/ashmere)) + +v0.1.1 +====== +* Search for proper version suffix if not provided (removes default '-1') +* Do not allow release clients by default when version search is enabled +* Push omnibus package installation to the end of run (reduces issue described in #10) +* Allow updater to be disabled via attribute (thanks [Teemu Matilainen](https://github.com/tmatilai)) + +v0.1.0 +====== +* Fix redhat related versioning issues +* Remove requirement for '-1' suffix on versions +* Initial support for automatic latest version install + +v0.0.5 +====== +* Add support for Ubuntu 12.10 +* Path fixes for non-64 bit packages (thanks [ashmere](https://github.com/ashmere)) + +v0.0.4 +====== +* Use new aws bucket by default +* Update file key building + +v0.0.3 +====== +* Path fix for debian omnibus packages (thanks [ashmere](https://github.com/ashmere)) + +v0.0.2 +====== +* Add robust check when uninstalling chef gem to prevent removal from omnibus + +v0.0.1 +====== +* Initial release diff --git a/cookbooks/omnibus_updater/README.md b/cookbooks/omnibus_updater/README.md new file mode 100644 index 0000000..08d1fd4 --- /dev/null +++ b/cookbooks/omnibus_updater/README.md @@ -0,0 +1,88 @@ +OmnibusUpdater +============== + +Update your omnibus! This cookbook can install the omnibus +Chef package into your system if you are currently running +via gem install, and it can keep your omnibus install up +to date. + +Usage +===== + +Add the recipe to your run list and specify what version should +be installed on the node: + +`knife node run_list add recipe[omnibus_updater]` + +In your role you'll likely want to set the version. It defaults +to nothing, and will install the latest.. + +``` +override_attributes( + :omnibus_updater => { + :version => '11.4.0' + } +) +``` + +It can also uninstall Chef from the system Ruby installation +if you tell it to: + +``` +override_attributes( + :omnibus_updater => { + :remove_chef_system_gem => true + } +) +``` + +Features +======== + +Latest Version +-------------- + +Force installation of the latest version regardless of value stored in version +attribute by setting the `force_latest` attribute. + +Chef Killing +------------ + +By default the omnibus updater will kill the chef instance by raising an exception. +You can turn this off using the `kill_chef_on_upgrade` attribute. It is not +recommended to turn this off. Internal chef libraries may change, move, or no +longer exist. The currently running instance can encounter unexpected states because +of this. To prevent this, the updater will attempt to kill the Chef instance so +that it can be restarted in a normal state. + +Restart chef-client Service +--------------------------- + +Use the `restart_chef_service` attribute to restart chef-client if you have it running as a service. + +Prerelease +-------- + +Prereleases can be installed via the auto-installation using `prerelease` attribute. + +Disable +------- + +If you want to disable the updater you can set the `disabled` +attribute to true. This might be useful if the cookbook is added +to a role but should then be skipped for example on a Chef server. + +Prevent Downgrade +----------------- + +If you want to prevent the updater from downgrading chef on a node, you +can set the `prevent_downgrade` attribute to true. This can be useful +for testing new versions manually. Note that the `always_download` +attribute takes precedence if set. + +Infos +===== + +* Repo: https://github.com/hw-cookbooks/omnibus_updater +* IRC: Freenode @ #heavywater +* Cookbook: http://ckbk.it/omnibus_updater diff --git a/cookbooks/omnibus_updater/attributes/default.rb b/cookbooks/omnibus_updater/attributes/default.rb new file mode 100644 index 0000000..f41a1bd --- /dev/null +++ b/cookbooks/omnibus_updater/attributes/default.rb @@ -0,0 +1,31 @@ +# +# Cookbook Name:: omnibus_updater +# Attributes:: default +# +# Copyright 2014, Heavy Water Ops, LLC +# +# 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. +# + +default[:omnibus_updater][:version] = nil +default[:omnibus_updater][:force_latest] = false +default[:omnibus_updater][:cache_dir] = "#{Chef::Config[:file_cache_path]}/omnibus_updater" +default[:omnibus_updater][:cache_omnibus_installer] = false +default[:omnibus_updater][:remove_chef_system_gem] = false +default[:omnibus_updater][:prerelease] = false +default[:omnibus_updater][:disabled] = false +default[:omnibus_updater][:kill_chef_on_upgrade] = true +default[:omnibus_updater][:always_download] = false +default[:omnibus_updater][:prevent_downgrade] = false +default[:omnibus_updater][:restart_chef_service] = false +default[:omnibus_updater][:checksum] = nil diff --git a/cookbooks/omnibus_updater/libraries/omnibus_checker.rb b/cookbooks/omnibus_updater/libraries/omnibus_checker.rb new file mode 100644 index 0000000..445cc07 --- /dev/null +++ b/cookbooks/omnibus_updater/libraries/omnibus_checker.rb @@ -0,0 +1,30 @@ +# +# Cookbook Name:: omnibus_updater +# Library:: omnibuschecker +# +# Copyright 2014, Heavy Water Ops, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +module OmnibusChecker + def is_omnibus? + Gem.bindir =~ %r{/opt/(opscode|chef)/} + end +end + +OmnibusChecker.send(:extend, OmnibusChecker) + +unless(Chef::Recipe.instance_methods.include?(:is_omnibus?)) + Chef::Recipe.send(:include, OmnibusChecker) +end diff --git a/cookbooks/omnibus_updater/libraries/omnitrucker.rb b/cookbooks/omnibus_updater/libraries/omnitrucker.rb new file mode 100644 index 0000000..92d1e8d --- /dev/null +++ b/cookbooks/omnibus_updater/libraries/omnitrucker.rb @@ -0,0 +1,92 @@ +# +# Cookbook Name:: omnibus_updater +# Library:: omnitrucker +# +# Copyright 2014, Heavy Water Ops, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +module OmnibusTrucker + class << self + URL_MAP = { + :p => :platform, :pv => :platform_version, :m => :machine, + :v => :version, :prerelease => :prerelease, + :nightlies => :nightlies + } + + def build_url(*opts) + args = node = nil + opts.each do |o| + if(o.kind_of?(Hash)) + args = o + elsif(o.kind_of?(Chef::Node)) + node = o + else + raise ArgumentError.new "Provided argument is not allowed: #{o.class}" + end + end + args ||= {} + if(node) + args = collect_attributes(node).merge(args) + end + url = args[:url] || "http://www.opscode.com/chef/download#{'-server' if args[:server]}" + u_args = URL_MAP.map do |u_k, a_k| + "#{u_k}=#{args[a_k]}" unless args[a_k].nil? + end.compact + "#{url}?#{u_args.join('&')}" + end + + def collect_attributes(node, args={}) + set = Mash[ + [:platform_family, :platform, :platform_version].map do |k| + [k, args[k] || node[k]] + end + ] + unless(@attrs) + if(set[:platform] == 'amazon') + @attrs = {:platform => 'el', :platform_version => 6} + elsif(set[:platform_family] == 'fedora') + @attrs = {:platform => 'el', :platform_version => 6} + elsif(set[:platform_family] == 'rhel') + @attrs = {:platform => 'el', :platform_version => set[:platform_version].to_i} + elsif(set[:platform] == 'debian') + @attrs = {:platform => set[:platform], :platform_version => set[:platform_version].to_i} + elsif(set[:platform_family] == 'mac_os_x') + @attrs = {:platform => set[:platform_family], :platform_version => [set[:platform_version].to_f, 10.7].min} + else + @attrs = {:platform => set[:platform], :platform_version => set[:platform_version]} + end + @attrs[:machine] = args[:machine] || node[:kernel][:machine] + @attrs[:machine] = "i386" if(set[:platform_family] == 'solaris2' && @attrs[:machine] == "i86pc") + end + @attrs + end + + def url(url_or_node, node = nil) + if(url_or_node.is_a?(Chef::Node)) + url = build_url(url_or_node) + node = url_or_node + else + url = url_or_node + raise "Node instance is required for Omnitruck.url!" unless node + end + request = Chef::REST::RESTRequest.new(:head, URI.parse(url), nil) + result = request.call + if(result.kind_of?(Net::HTTPRedirection)) + result['location'] + end + end + + end +end diff --git a/cookbooks/omnibus_updater/metadata.json b/cookbooks/omnibus_updater/metadata.json new file mode 100644 index 0000000..ead25c9 --- /dev/null +++ b/cookbooks/omnibus_updater/metadata.json @@ -0,0 +1,29 @@ +{ + "name": "omnibus_updater", + "description": "Chef omnibus package updater and installer", + "long_description": "OmnibusUpdater\n==============\n\nUpdate your omnibus! This cookbook can install the omnibus\nChef package into your system if you are currently running\nvia gem install, and it can keep your omnibus install up\nto date.\n\nUsage\n=====\n\nAdd the recipe to your run list and specify what version should\nbe installed on the node:\n\n`knife node run_list add recipe[omnibus_updater]`\n\nIn your role you'll likely want to set the version. It defaults\nto nothing, and will install the latest..\n\n```\noverride_attributes(\n :omnibus_updater => {\n :version => '11.4.0'\n }\n)\n```\n\nIt can also uninstall Chef from the system Ruby installation\nif you tell it to:\n\n```\noverride_attributes(\n :omnibus_updater => {\n :remove_chef_system_gem => true\n }\n)\n```\n\nFeatures\n========\n\nLatest Version\n--------------\n\nForce installation of the latest version regardless of value stored in version\nattribute by setting the `force_latest` attribute.\n\nChef Killing\n------------\n\nBy default the omnibus updater will kill the chef instance by raising an exception.\nYou can turn this off using the `kill_chef_on_upgrade` attribute. It is not\nrecommended to turn this off. Internal chef libraries may change, move, or no\nlonger exist. The currently running instance can encounter unexpected states because\nof this. To prevent this, the updater will attempt to kill the Chef instance so\nthat it can be restarted in a normal state.\n\nRestart chef-client Service\n---------------------------\n\nUse the `restart_chef_service` attribute to restart chef-client if you have it running as a service.\n\nPrerelease\n--------\n\nPrereleases can be installed via the auto-installation using `prerelease` attribute.\n\nDisable\n-------\n\nIf you want to disable the updater you can set the `disabled`\nattribute to true. This might be useful if the cookbook is added\nto a role but should then be skipped for example on a Chef server.\n\nPrevent Downgrade\n-----------------\n\nIf you want to prevent the updater from downgrading chef on a node, you \ncan set the `prevent_downgrade` attribute to true. This can be useful\nfor testing new versions manually. Note that the `always_download` \nattribute takes precedence if set.\n\nInfos\n=====\n\n* Repo: https://github.com/hw-cookbooks/omnibus_updater\n* IRC: Freenode @ #heavywater\n* Cookbook: http://ckbk.it/omnibus_updater\n", + "maintainer": "Chris Roberts", + "maintainer_email": "chrisroberts.code@gmail.com", + "license": "Apache 2.0", + "platforms": { + }, + "dependencies": { + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + }, + "version": "1.0.4" +} \ No newline at end of file diff --git a/cookbooks/omnibus_updater/metadata.rb b/cookbooks/omnibus_updater/metadata.rb new file mode 100644 index 0000000..644a420 --- /dev/null +++ b/cookbooks/omnibus_updater/metadata.rb @@ -0,0 +1,7 @@ +name "omnibus_updater" +maintainer "Chris Roberts" +maintainer_email "chrisroberts.code@gmail.com" +license "Apache 2.0" +description "Chef omnibus package updater and installer" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version "1.0.4" diff --git a/cookbooks/omnibus_updater/recipes/default.rb b/cookbooks/omnibus_updater/recipes/default.rb new file mode 100644 index 0000000..5989259 --- /dev/null +++ b/cookbooks/omnibus_updater/recipes/default.rb @@ -0,0 +1,31 @@ +# +# Cookbook Name:: omnibus_updater +# Recipe:: default +# +# Copyright 2014, Heavy Water Ops, LLC +# +# 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. +# + +if(node[:omnibus_updater][:disabled]) + Chef::Log.warn 'Omnibus updater disabled via `disabled` attribute' +elsif(node[:platform] == 'debian' && Gem::Version.new(node[:platform_version]) < Gem::Version.new('6.0.0')) + Chef::Log.warn 'Omnibus updater does not support Debian 5' +else + include_recipe 'omnibus_updater::downloader' + include_recipe 'omnibus_updater::installer' +end + +if(node[:omnibus_updater][:remove_chef_system_gem]) + include_recipe 'omnibus_updater::remove_chef_system_gem' +end diff --git a/cookbooks/omnibus_updater/recipes/downloader.rb b/cookbooks/omnibus_updater/recipes/downloader.rb new file mode 100644 index 0000000..5b3d2b8 --- /dev/null +++ b/cookbooks/omnibus_updater/recipes/downloader.rb @@ -0,0 +1,71 @@ +# +# Cookbook Name:: omnibus_updater +# Recipe:: downloader +# +# Copyright 2014, Heavy Water Ops, LLC +# +# 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. +# + +# NOTE: This recipe is here for others that just want the +# package, not the actual installation (lxc for example) + +if(node[:omnibus_updater][:direct_url]) + remote_path = node[:omnibus_updater][:direct_url] +else + version = node[:omnibus_updater][:version] || '' + remote_path = OmnibusTrucker.url( + OmnibusTrucker.build_url(node, + :version => node[:omnibus_updater][:force_latest] ? nil : version.sub(/\-.+$/, ''), + :prerelease => node[:omnibus_updater][:preview] + ), node + ) +end + +if(remote_path) + node.set[:omnibus_updater][:full_url] = remote_path + + directory node[:omnibus_updater][:cache_dir] do + recursive true + end + + remote_file "omnibus_remote[#{File.basename(remote_path)}]" do + path File.join(node[:omnibus_updater][:cache_dir], File.basename(remote_path)) + source remote_path + backup false + checksum node[:omnibus_updater][:checksum] if node[:omnibus_updater][:checksum] + action :create_if_missing + only_if do + unless(version = node[:omnibus_updater][:version]) + version = node[:omnibus_updater][:full_url].scan(%r{chef[_-](\d+\.\d+.\d+)}).flatten.first + end + if(node[:omnibus_updater][:always_download]) + # warn if there may be unexpected behavior + node[:omnibus_updater][:prevent_downgrade] && + Chef::Log.warn("omnibus_updater: prevent_downgrade is ignored when always_download is true") + Chef::Log.debug "Omnibus Updater remote path: #{remote_path}" + true + elsif(node[:omnibus_updater][:prevent_downgrade]) + # Return true if the found/specified version is newer + Gem::Version.new(version.to_s.sub(/\-.+$/, '')) > Gem::Version.new(Chef::VERSION) + else + # default is to install if the versions don't match + Chef::VERSION != version.to_s.sub(/\-.+$/, '') + end + end + end +else + Chef::Log.warn 'Failed to retrieve omnibus download URL' +end + +include_recipe 'omnibus_updater::old_package_cleaner' diff --git a/cookbooks/omnibus_updater/recipes/installer.rb b/cookbooks/omnibus_updater/recipes/installer.rb new file mode 100644 index 0000000..bfea578 --- /dev/null +++ b/cookbooks/omnibus_updater/recipes/installer.rb @@ -0,0 +1,70 @@ +# +# Cookbook Name:: omnibus_updater +# Recipe:: installer +# +# Copyright 2014, Heavy Water Ops, LLC +# +# 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. +# + +include_recipe 'omnibus_updater' +remote_path = node[:omnibus_updater][:full_url].to_s + +file '/tmp/nocheck' do + content 'conflict=nocheck\naction=nocheck' + only_if { node['os'] =~ /^solaris/ } +end + +service 'chef-client' do + action :nothing +end + +ruby_block 'omnibus chef killer' do + block do + raise 'New omnibus chef version installed. Killing Chef run!' + end + action :nothing + only_if do + node[:omnibus_updater][:kill_chef_on_upgrade] + end +end + +execute "omnibus_install[#{File.basename(remote_path)}]" do + case File.extname(remote_path) + when '.deb' + command "dpkg -i #{File.join(node[:omnibus_updater][:cache_dir], File.basename(remote_path))}" + when '.rpm' + command "rpm -Uvh --oldpackage #{File.join(node[:omnibus_updater][:cache_dir], File.basename(remote_path))}" + when '.sh' + command "/bin/sh #{File.join(node[:omnibus_updater][:cache_dir], File.basename(remote_path))}" + when '.solaris' + command "pkgadd -n -d #{File.join(node[:omnibus_updater][:cache_dir], File.basename(remote_path))} -a /tmp/nocheck chef" + else + raise "Unknown package type encountered for install: #{File.extname(remote_path)}" + end + action :nothing + if(node[:omnibus_updater][:restart_chef_service]) + notifies :restart, resources(:service => 'chef-client'), :immediately + end + notifies :create, resources(:ruby_block => 'omnibus chef killer'), :immediately +end + +ruby_block 'Omnibus Chef install notifier' do + block{ true } + action :nothing + subscribes :create, resources(:remote_file => "omnibus_remote[#{File.basename(remote_path)}]"), :immediately + notifies :run, resources(:execute => "omnibus_install[#{File.basename(remote_path)}]"), :delayed + only_if { node['chef_packages']['chef']['version'] != node['omnibus_updater']['version'] } +end + +include_recipe 'omnibus_updater::old_package_cleaner' diff --git a/cookbooks/omnibus_updater/recipes/old_package_cleaner.rb b/cookbooks/omnibus_updater/recipes/old_package_cleaner.rb new file mode 100644 index 0000000..0cfd111 --- /dev/null +++ b/cookbooks/omnibus_updater/recipes/old_package_cleaner.rb @@ -0,0 +1,34 @@ +# +# Cookbook Name:: omnibus_updater +# Recipe:: old_package_cleaner +# +# Copyright 2014, Heavy Water Ops, LLC +# +# 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. +# + +old_pkgs = + if(::File.exist?(node[:omnibus_updater][:cache_dir])) + Dir.glob(File.join(node[:omnibus_updater][:cache_dir], 'chef_*')).find_all do |file| + !file.include?(node[:omnibus_updater][:version].to_s) && !file.scan(/\.(rpm|deb)$/).empty? + end + else + [] + end + +old_pkgs.each do |filename| + file filename do + action :delete + backup 1 + end +end diff --git a/cookbooks/omnibus_updater/recipes/remove_chef_system_gem.rb b/cookbooks/omnibus_updater/recipes/remove_chef_system_gem.rb new file mode 100644 index 0000000..576b379 --- /dev/null +++ b/cookbooks/omnibus_updater/recipes/remove_chef_system_gem.rb @@ -0,0 +1,29 @@ +# +# Cookbook Name:: omnibus_updater +# Recipe:: remove_chef_system_gem +# +# Copyright 2014, Heavy Water Ops, LLC +# +# 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. +# + +gem_package 'chef' do + action :purge + only_if do + Chef::Provider::Package::Rubygems.new( + Chef::Resource::GemPackage.new('dummy_package') + ).gem_env.gem_paths.detect{|path| + path.start_with?('/opt/omnibus') || path.start_with?('/opt/chef') + }.nil? && node[:omnibus_updater][:remove_chef_system_gem] + end +end diff --git a/cookbooks/openssl/CHANGELOG.md b/cookbooks/openssl/CHANGELOG.md new file mode 100644 index 0000000..c297559 --- /dev/null +++ b/cookbooks/openssl/CHANGELOG.md @@ -0,0 +1,30 @@ +openssl Cookbook CHANGELOG +========================== +This file is used to list changes made in each version of the openssl cookbook. + +v4.0.0 (2014-02-19) +------------------- +- Reverting to Opscode module namespace + +v3.0.2 (2014-12-30) +------------------- +- Accidently released 2.0.2 as 3.0.2 + +v2.0.2 (2014-12-30) +------------------- +- Call cert.to_pem before recipe DSL + +v2.0.0 (2014-06-11) +------------------- + +- #1 - **[COOK-847](https://tickets.chef.io/browse/COOK-847)** - Add LWRP for generating self signed certs +- #4 - **[COOK-4715](https://tickets.chef.io/browse/COOK-4715)** - add upgrade recipe and complete test harness + +v1.1.0 +------ +### Improvement +- **[COOK-3222](https://tickets.chef.io/browse/COOK-3222)** - Allow setting length for `secure_password` + +v1.0.2 +------ +- Add name attribute to metadata diff --git a/cookbooks/openssl/README.md b/cookbooks/openssl/README.md new file mode 100644 index 0000000..6f0b13c --- /dev/null +++ b/cookbooks/openssl/README.md @@ -0,0 +1,115 @@ +openssl Cookbook +================ + +This cookbook provides a library method to generate secure random passwords in recipes using the Ruby OpenSSL library. + +It also provides an attribute-driven recipe for upgrading OpenSSL packages. + +Requirements +------------ + +The `secure_password` works on any platform with OpenSSL Ruby bindings installed, which are a requirement for Chef anyway. + +The upgrade recipe works on the following tested platforms: + +* Ubuntu 12.04, 14.04 +* Debian 7.4 +* CentOS 6.5 + +It may work on other platforms or versions of the above platforms with or without modification. + +[Chef Sugar](https://github.com/sethvargo/chef-sugar) was introduced as a dependency to provide helpers that make the default attribute settings (see Attributes) easier to reason about. + +Attributes +---------- + +* `node['openssl']['packages']` - An array of packages of openssl. The default attributes attempt to be smart about which packages are the default, but this may need to be changed by users of the `openssl::upgrade` recipe. +* `node['openssl']['restart_services']` - An array of service resources that use the `node['openssl']['packages']`. This is empty by default as Chef has no reliably reasonable way to detect which applications or services are compiled against these packages. *Note* These each need to be "`service`" resources specified somewhere in the recipes in the node's run list. + +Recipes +------- + +### upgrade + +The upgrade recipe iterates over the list of packages in the `node['openssl']['packages']` attribute and manages them with the `:upgrade` action. Each package will send `:restart` notification to service resources named by the `node['openssl']['restart_services']` attribute. + +Usage +----- + +Most often this will be used to generate a secure password for an attribute. In a recipe: + +```ruby +::Chef::Recipe.send(:include, Chef::OpenSSL::Password) +node.set_unless[:my_password] = secure_password +``` + +To use the `openssl::upgrade` recipe, set the attributes as mentioned above. For example, we have a "stats_collector" service that uses openssl. It has a recipe that looks like this: + +LWRP +==== + +This cookbook includes an LWRP for generating Self Signed Certificates + +## openssl_x509 +generate a pem formatted x509 cert + key + +### Attributes +`common_name` A String representing the `CN` ssl field +`org` A String representing the `O` ssl field +`org_unit` A String representing the `OU` ssl field +`country` A String representing the `C` ssl field +`expire` A Fixnum reprenting the number of days from _now_ to expire the cert +`key_file` Optional A string to the key file to use. If no key is present it will generate and store one. +`key_pass` A String that is the key's passphrase +`key_length` A Fixnum reprenting your desired Bit Length _Default: 2048_ +`owner` The owner of the files _Default: "root"_ +`group` The group of the files _Default: "root"_ +`mode` The mode to store the files in _Default: "0400"_ + +### Example usage + + openssl_x509 "/tmp/mycert.pem" do + common_name "www.f00bar.com" + org "Foo Bar" + org_unit "Lab" + country "US" + end + + +License and Author +================== + +Author:: Jesse Nelson () +Author:: Joshua Timberman () +======= + + +```ruby +node.default['openssl']['restart_services'] = ['stats_collector'] + +# other recipe code here... +service 'stats_collector' do + action [:enable, :start] +end + +include_recipe 'openssl::upgrade' +``` + +This will ensure that openssl is upgraded to the latest version so the `stats_collector` service won't be exploited (hopefully!). + +```text +Copyright:: 2009-2011, Chef Software, Inc +Copyright:: 2014, Chef Software, Inc + +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/cookbooks/openssl/attributes/default.rb b/cookbooks/openssl/attributes/default.rb new file mode 100644 index 0000000..d4945a3 --- /dev/null +++ b/cookbooks/openssl/attributes/default.rb @@ -0,0 +1,21 @@ +# +# Cookbook Name:: openssl +# Attributes:: default +# +# Copyright 2014, Chef Software, Inc. +# +# 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. +# + +default['openssl']['packages'] = [] +default['openssl']['restart_services'] = [] diff --git a/cookbooks/openssl/libraries/secure_password.rb b/cookbooks/openssl/libraries/secure_password.rb new file mode 100644 index 0000000..ee8ec7f --- /dev/null +++ b/cookbooks/openssl/libraries/secure_password.rb @@ -0,0 +1,37 @@ +# +# Cookbook Name:: openssl +# Library:: secure_password +# Author:: Joshua Timberman +# +# Copyright 2009, Chef Software, Inc. +# +# 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. +# + +require 'openssl' + +module Opscode + module OpenSSL + module Password + def secure_password(length = 20) + pw = String.new + + while pw.length < length + pw << ::OpenSSL::Random.random_bytes(1).gsub(/\W/, '') + end + + pw + end + end + end +end diff --git a/cookbooks/openssl/metadata.json b/cookbooks/openssl/metadata.json new file mode 100644 index 0000000..04077bb --- /dev/null +++ b/cookbooks/openssl/metadata.json @@ -0,0 +1,31 @@ +{ + "name": "openssl", + "version": "4.0.0", + "description": "Provides a library with a method for generating secure random passwords.", + "long_description": "openssl Cookbook\n================\n\nThis cookbook provides a library method to generate secure random passwords in recipes using the Ruby OpenSSL library.\n\nIt also provides an attribute-driven recipe for upgrading OpenSSL packages.\n\nRequirements\n------------\n\nThe `secure_password` works on any platform with OpenSSL Ruby bindings installed, which are a requirement for Chef anyway.\n\nThe upgrade recipe works on the following tested platforms:\n\n* Ubuntu 12.04, 14.04\n* Debian 7.4\n* CentOS 6.5\n\nIt may work on other platforms or versions of the above platforms with or without modification.\n\n[Chef Sugar](https://github.com/sethvargo/chef-sugar) was introduced as a dependency to provide helpers that make the default attribute settings (see Attributes) easier to reason about.\n\nAttributes\n----------\n\n* `node['openssl']['packages']` - An array of packages of openssl. The default attributes attempt to be smart about which packages are the default, but this may need to be changed by users of the `openssl::upgrade` recipe.\n* `node['openssl']['restart_services']` - An array of service resources that use the `node['openssl']['packages']`. This is empty by default as Chef has no reliably reasonable way to detect which applications or services are compiled against these packages. *Note* These each need to be \"`service`\" resources specified somewhere in the recipes in the node's run list.\n\nRecipes\n-------\n\n### upgrade\n\nThe upgrade recipe iterates over the list of packages in the `node['openssl']['packages']` attribute and manages them with the `:upgrade` action. Each package will send `:restart` notification to service resources named by the `node['openssl']['restart_services']` attribute.\n\nUsage\n-----\n\nMost often this will be used to generate a secure password for an attribute. In a recipe:\n\n```ruby\n::Chef::Recipe.send(:include, Chef::OpenSSL::Password)\nnode.set_unless[:my_password] = secure_password\n```\n\nTo use the `openssl::upgrade` recipe, set the attributes as mentioned above. For example, we have a \"stats_collector\" service that uses openssl. It has a recipe that looks like this:\n\nLWRP\n==== \n\nThis cookbook includes an LWRP for generating Self Signed Certificates\n\n## openssl_x509\ngenerate a pem formatted x509 cert + key \n\n### Attributes\n`common_name` A String representing the `CN` ssl field\n`org` A String representing the `O` ssl field\n`org_unit` A String representing the `OU` ssl field\n`country` A String representing the `C` ssl field\n`expire` A Fixnum reprenting the number of days from _now_ to expire the cert\n`key_file` Optional A string to the key file to use. If no key is present it will generate and store one. \n`key_pass` A String that is the key's passphrase\n`key_length` A Fixnum reprenting your desired Bit Length _Default: 2048_\n`owner` The owner of the files _Default: \"root\"_\n`group` The group of the files _Default: \"root\"_\n`mode` The mode to store the files in _Default: \"0400\"_\n\n### Example usage\n\n openssl_x509 \"/tmp/mycert.pem\" do\n common_name \"www.f00bar.com\"\n org \"Foo Bar\"\n org_unit \"Lab\"\n country \"US\"\n end\n\n \nLicense and Author\n==================\n\nAuthor:: Jesse Nelson ()\nAuthor:: Joshua Timberman ()\n=======\n\n\n```ruby\nnode.default['openssl']['restart_services'] = ['stats_collector']\n\n# other recipe code here...\nservice 'stats_collector' do\n action [:enable, :start]\nend\n\ninclude_recipe 'openssl::upgrade'\n```\n\nThis will ensure that openssl is upgraded to the latest version so the `stats_collector` service won't be exploited (hopefully!).\n\n```text\nCopyright:: 2009-2011, Chef Software, Inc\nCopyright:: 2014, Chef Software, Inc \n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "maintainer": "Chef Software, Inc.", + "maintainer_email": "cookbooks@chef.io", + "license": "Apache 2.0", + "platforms": { + }, + "dependencies": { + "chef-sugar": ">= 0.0.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + "openssl": "Empty, this cookbook provides a library, see README.md" + } +} \ No newline at end of file diff --git a/cookbooks/openssl/providers/x509.rb b/cookbooks/openssl/providers/x509.rb new file mode 100644 index 0000000..120e20c --- /dev/null +++ b/cookbooks/openssl/providers/x509.rb @@ -0,0 +1,94 @@ +# +# x509 self signed cert provider +# +# Author:: Jesse Nelson +# +require 'openssl' + +use_inline_resources + +attr_reader :key_file, :key, :cert, :ef + +action :create do + unless ::File.exists? new_resource.name + create_keys + cert_content = cert.to_pem + key_content = key.to_pem + + file new_resource.name do + action :create_if_missing + mode new_resource.mode + owner new_resource.owner + group new_resource.group + content cert_content + end + + file new_resource.key_file do + action :create_if_missing + mode new_resource.mode + owner new_resource.owner + group new_resource.group + content key_content + end + + end +end + +protected + + def key_file + unless new_resource.key_file + path, file= ::File.split(new_resource.name) + filename = ::File.basename(file, ::File.extname(file) ) + new_resource.key_file path + "/" + filename + ".key" + end + new_resource.key_file + end + + def key + @key ||= if ::File.exists? key_file + OpenSSL::PKey::RSA.new File.read(key_file), new_resource.key_pass + else + OpenSSL::PKey::RSA.new(new_resource.key_length) + end + @key + end + + def cert + @cert ||= OpenSSL::X509::Certificate.new + end + + def gen_cert + cert + cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject) + cert.not_before = Time.now + cert.not_after = Time.now + (new_resource.expire.to_i * 24 * 60 * 60) + cert.public_key = key.public_key + cert.serial = 0x0 + cert.version = 2 + end + + def subject + @subject ||= "/C=" + new_resource.country + + "/O=" + new_resource.org + + "/OU=" + new_resource.org_unit + + "/CN=" + new_resource.common_name + end + + def extensions + [ + ef.create_extension("basicConstraints","CA:TRUE", true), + ef.create_extension("subjectKeyIdentifier", "hash"), + ] + end + + def create_keys + gen_cert + @ef ||= OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = cert + ef.issuer_certificate = cert + cert.extensions = extensions + cert.add_extension ef.create_extension("authorityKeyIdentifier", + "keyid:always,issuer:always") + cert.sign key, OpenSSL::Digest::SHA1.new + end diff --git a/cookbooks/openssl/recipes/default.rb b/cookbooks/openssl/recipes/default.rb new file mode 100644 index 0000000..b0465e1 --- /dev/null +++ b/cookbooks/openssl/recipes/default.rb @@ -0,0 +1,18 @@ +# +# Cookbook Name:: openssl +# Recipe:: default +# +# Copyright 2009, Chef Software, Inc. +# +# 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/cookbooks/openssl/recipes/upgrade.rb b/cookbooks/openssl/recipes/upgrade.rb new file mode 100644 index 0000000..7698e7e --- /dev/null +++ b/cookbooks/openssl/recipes/upgrade.rb @@ -0,0 +1,39 @@ +# +# Cookbook Name:: openssl +# Recipe:: upgrade +# +# Copyright 2014, Chef Software, Inc. +# +# 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. +# +include_recipe 'chef-sugar' + +node.default['openssl']['packages'] = case + when debian_before_or_at_squeeze?, ubuntu_before_or_at_lucid? + %w{libssl0.9.8 openssl} + when debian_after_or_at_wheezy?, ubuntu_after_or_at_precise? + %w{libssl1.0.0 openssl} + when rhel? + %w{openssl} + else + [] + end + +node['openssl']['packages'].each do |ssl_pkg| + package ssl_pkg do + action :upgrade + node['openssl']['restart_services'].each do |ssl_svc| + notifies :restart, "service[#{ssl_svc}]" + end + end +end diff --git a/cookbooks/openssl/resources/x509.rb b/cookbooks/openssl/resources/x509.rb new file mode 100644 index 0000000..77d1485 --- /dev/null +++ b/cookbooks/openssl/resources/x509.rb @@ -0,0 +1,16 @@ + +actions [ :create ] +default_action :create + +attribute :name, :kind_of => String, :name_attribute => true +attribute :owner, :kind_of => String +attribute :group, :kind_of => String +attribute :expire, :kind_of => Fixnum +attribute :mode +attribute :org, :kind_of => String, :required => true +attribute :org_unit, :kind_of => String, :required => true +attribute :country, :kind_of => String, :required => true +attribute :common_name, :kind_of => String, :required => true +attribute :key_file, :kind_of => String, :default => nil +attribute :key_pass, :kind_of => String, :default => nil +attribute :key_length, :kind_of => Fixnum, :default => 2048 diff --git a/cookbooks/packagecloud/.gitignore b/cookbooks/packagecloud/.gitignore new file mode 100644 index 0000000..03daae1 --- /dev/null +++ b/cookbooks/packagecloud/.gitignore @@ -0,0 +1,19 @@ +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ +pkg/ + +# Berkshelf +.vagrant +/cookbooks +Berksfile.lock + +# Bundler +Gemfile.lock +bin/* +.bundle/* + +.kitchen diff --git a/cookbooks/packagecloud/.kitchen.yml b/cookbooks/packagecloud/.kitchen.yml new file mode 100644 index 0000000..bc6c500 --- /dev/null +++ b/cookbooks/packagecloud/.kitchen.yml @@ -0,0 +1,79 @@ +--- +driver_plugin: vagrant +driver_config: + require_chef_omnibus: true + +platforms: +- name: ubuntu-10.04 + driver_config: + box: opscode-ubuntu-10.04 + box_url: https://opscode-vm-bento.s3.amazonaws.com/vagrant/opscode_ubuntu-10.04_provisionerless.box + run_list: + - recipe[packagecloud_test::lucid_deps] + - recipe[packagecloud_test::deb] + - recipe[packagecloud_test::rubygems_private] + +- name: ubuntu-12.04 + driver_config: + box: opscode-ubuntu-12.04 + box_url: https://opscode-vm-bento.s3.amazonaws.com/vagrant/opscode_ubuntu-12.04_provisionerless.box + run_list: + - recipe[packagecloud_test::precise_deps] + - recipe[packagecloud_test::deb] + - recipe[packagecloud_test::rubygems_private] + +- name: ubuntu-14.04 + driver_config: + box: opscode-ubuntu-14.04 + box_url: http://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_ubuntu-14.04_chef-provisionerless.box + run_list: + - recipe[packagecloud_test::trusty_deps] + - recipe[packagecloud_test::deb] + - recipe[packagecloud_test::rubygems] + +- name: centos-without-epel-5.10 + driver_config: + box_url: http://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_centos-5.10_chef-provisionerless.box + run_list: + - recipe[packagecloud_test::rpm] + - recipe[packagecloud_test::rubygems] + +- name: centos-with-epel-5.10 + driver_config: + box_url: http://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_centos-5.10_chef-provisionerless.box + run_list: + - recipe[packagecloud_test::epel5] + - recipe[packagecloud_test::rpm] + - recipe[packagecloud_test::rubygems_private] + +- name: centos-6.5 + run_list: + - recipe[packagecloud_test::rpm] + - recipe[packagecloud_test::rubygems] + +- name: centos-7.0 + run_list: + - recipe[packagecloud_test::rpm] + - recipe[packagecloud_test::rubygems_private] + +- name: amazon-2014.09 + driver_plugin: ec2 + driver_config: + image_id: ami-b5a7ea85 + username: ec2-user + aws_access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %> + aws_secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> + aws_ssh_key_id: <%= ENV['AWS_KEYPAIR_NAME'] %> + ssh_key: <%= ENV['AWS_SSH_KEY_PATH'] %> + availability_zone: us-west-2a + region: us-west-2 + flavor_id: t2.micro + security_group_ids: sg-598e583c + run_list: + - recipe[packagecloud_test::rpm] + - recipe[packagecloud_test::rubygems] + +suites: +- name: default + run_list: + attributes: {} diff --git a/cookbooks/packagecloud/.rubocop.yml b/cookbooks/packagecloud/.rubocop.yml new file mode 100644 index 0000000..0fde6a5 --- /dev/null +++ b/cookbooks/packagecloud/.rubocop.yml @@ -0,0 +1,28 @@ +AllCops: + Include: + - Berksfile + - Gemfile + - Rakefile + - Thorfile + - Guardfile + Exclude: + - vendor/** + +ClassLength: + Enabled: false +Documentation: + Enabled: false +Encoding: + Enabled: false +HashSyntax: + Enabled: false +LineLength: + Enabled: false +MethodLength: + Enabled: false +SignalException: + Enabled: false +TrailingComma: + Enabled: false +WordArray: + Enabled: false diff --git a/cookbooks/packagecloud/.travis.yml b/cookbooks/packagecloud/.travis.yml new file mode 100644 index 0000000..7d2bad2 --- /dev/null +++ b/cookbooks/packagecloud/.travis.yml @@ -0,0 +1,7 @@ +language: ruby +rvm: + - 1.9.3 + - 2.0.0 +bundler_args: --without integration +script: + - bundle exec rake travis diff --git a/cookbooks/packagecloud/0001-chef-on-amazon-2014.patch b/cookbooks/packagecloud/0001-chef-on-amazon-2014.patch new file mode 100644 index 0000000..e653dfc --- /dev/null +++ b/cookbooks/packagecloud/0001-chef-on-amazon-2014.patch @@ -0,0 +1,65 @@ +From e705e0beb6cd93447dec04aea8bdda004fbb8ab7 Mon Sep 17 00:00:00 2001 +From: capotej +Date: Tue, 28 Oct 2014 12:47:17 -0700 +Subject: [PATCH] chef on amazon 2014 + +--- + .kitchen.yml | 23 ++++++++++++++++++++--- + Gemfile | 1 + + 2 files changed, 21 insertions(+), 3 deletions(-) + +diff --git a/.kitchen.yml b/.kitchen.yml +index a389d99..fbebe56 100644 +--- a/.kitchen.yml ++++ b/.kitchen.yml +@@ -25,7 +25,7 @@ platforms: + - name: ubuntu-14.04 + driver_config: + box: opscode-ubuntu-14.04 +- box_url: https://opscode-vm-bento.s3.amazonaws.com/vagrant/opscode_ubuntu-14.04_provisionerless.box ++ box_url: http://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_ubuntu-14.04_chef-provisionerless.box + run_list: + - recipe[packagecloud_test::trusty_deps] + - recipe[packagecloud_test::deb] +@@ -56,7 +56,24 @@ platforms: + - recipe[packagecloud_test::rpm] + - recipe[packagecloud_test::rubygems_private] + ++- name: amazon-2014.09 ++ driver_plugin: ec2 ++ driver_config: ++ image_id: ami-b5a7ea85 ++ username: ec2-user ++ aws_access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %> ++ aws_secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> ++ aws_ssh_key_id: <%= ENV['AWS_KEYPAIR_NAME'] %> ++ ssh_key: <%= ENV['AWS_SSH_KEY_PATH'] %> ++ availability_zone: us-west-2a ++ region: us-west-2 ++ flavor_id: t2.micro ++ security_group_ids: sg-598e583c ++ run_list: ++ - recipe[packagecloud_test::rpm] ++ - recipe[packagecloud_test::rubygems] ++ + suites: + - name: default +- run_list: +- attributes: {} ++run_list: ++attributes: {} +diff --git a/Gemfile b/Gemfile +index 9ce1223..9b9ee17 100644 +--- a/Gemfile ++++ b/Gemfile +@@ -2,6 +2,7 @@ source 'https://rubygems.org' + + gem 'rake' + gem 'berkshelf', '~> 3.1.4' ++gem 'kitchen-ec2' + + group :test do + gem 'foodcritic', '~> 4.0.0' +-- +1.9.2 + diff --git a/cookbooks/packagecloud/Berksfile b/cookbooks/packagecloud/Berksfile new file mode 100644 index 0000000..47c2da6 --- /dev/null +++ b/cookbooks/packagecloud/Berksfile @@ -0,0 +1,5 @@ +source 'https://api.berkshelf.com' + +metadata + +cookbook 'packagecloud_test', :path => 'test/fixtures/cookbooks/packagecloud_test' diff --git a/cookbooks/packagecloud/CHANGELOG.md b/cookbooks/packagecloud/CHANGELOG.md new file mode 100644 index 0000000..d5f156d --- /dev/null +++ b/cookbooks/packagecloud/CHANGELOG.md @@ -0,0 +1,12 @@ +packagecloud +=============== +This is the Changelog for the packagecloud cookbook + +v0.0.1 (2014-06-05) +------------------- +Initial release. + + +v0.0.1 (2014-06-05) +------------------- +Initial release! diff --git a/cookbooks/packagecloud/Gemfile b/cookbooks/packagecloud/Gemfile new file mode 100644 index 0000000..9b9ee17 --- /dev/null +++ b/cookbooks/packagecloud/Gemfile @@ -0,0 +1,15 @@ +source 'https://rubygems.org' + +gem 'rake' +gem 'berkshelf', '~> 3.1.4' +gem 'kitchen-ec2' + +group :test do + gem 'foodcritic', '~> 4.0.0' + gem 'rubocop', '~> 0.24.1' +end + +group :integration do + gem 'test-kitchen', '~> 1.2.1' + gem 'kitchen-vagrant', '~> 0.15.0' +end diff --git a/cookbooks/packagecloud/LICENSE b/cookbooks/packagecloud/LICENSE new file mode 100644 index 0000000..56c3277 --- /dev/null +++ b/cookbooks/packagecloud/LICENSE @@ -0,0 +1,13 @@ +Copyright (C) 2014 Computology, LLC. + +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/cookbooks/packagecloud/README.md b/cookbooks/packagecloud/README.md new file mode 100644 index 0000000..e16c92b --- /dev/null +++ b/cookbooks/packagecloud/README.md @@ -0,0 +1,64 @@ +# packagecloud cookbook + +This cookbook provides an LWRP for installing https://packagecloud.io repositories. + +## Usage + +Be sure to depend on `packagecloud` in `metadata.rb` so that the packagecloud +resource will be loaded. + +For public repos: + +```ruby +packagecloud_repo "computology/packagecloud-cookbook-test-public" do + type "deb" +end +``` + +For private repos, you need to supply a `master_token`: + +```ruby +packagecloud_repo "computology/packagecloud-cookbook-test-private" do + type "deb" + master_token "762748f7ae0bfdb086dd539575bdc8cffdca78c6a9af0db9" +end +``` + +For packagecloud:enterprise users, add `base_url` to your resource: +``` +packagecloud_repo "computology/packagecloud-cookbook-test-private" do + base_url "https://packages.example.com" + type "deb" + master_token "762748f7ae0bfdb086dd539575bdc8cffdca78c6a9af0db9" +end +``` + +Valid options for `type` include `deb`, `rpm`, and `gem`. + +## Interactions with other cookbooks + +On CentOS 5, the official chef yum cookbook overwrites the file +`/etc/yum.conf` setting some default values. When it does this, the `cachedir` +value is changed from the CentOS5 default to the default value in the +cookbook. The result of this change is that any packagecloud repository +installed *before* a repository installed with the yum cookbook will appear as +though it's gpg keys were not imported. + +There are a few potential workarounds for this: + +- Pass the "-y" flag to package resource using the `options` attribute. This + should cause yum to import the GPG key automatically if it was not imported + already. +- Move your packagecloud repos so that they are installed last, after any/all + repos installed via the yum cookbook. +- Set the cachedir option in the chef yum cookbook to the system default value + of `/var/cache/yum` using the `yum_globalconfig` resource. + +CentOS 6 and 7 are not affected as the default `cachedir` value provided by +the yum chef cookbook is set to the system default, unless you use the +`yum_globalconfig` resource to set a custom cachedir. If you do set a custom +`cachedir`, you should make sure to setup packagecloud repos after that +resource is set so that the GPG keys end up in the right place. + +## Credits +Computology, LLC. diff --git a/cookbooks/packagecloud/Rakefile b/cookbooks/packagecloud/Rakefile new file mode 100644 index 0000000..0300fef --- /dev/null +++ b/cookbooks/packagecloud/Rakefile @@ -0,0 +1,47 @@ +#!/usr/bin/env rake + +# Style tests. Rubocop and Foodcritic +namespace :style do + begin + require 'rubocop/rake_task' + desc 'Run Ruby style checks' + RuboCop::RakeTask.new(:ruby) + rescue LoadError + puts '>>>>> Rubocop gem not loaded, omitting tasks' unless ENV['CI'] + end + + begin + require 'foodcritic' + + desc 'Run Chef style checks' + FoodCritic::Rake::LintTask.new(:chef) do |t| + t.options = { + fail_tags: ['any'], + tags: ['~FC003'] + } + end + rescue LoadError + puts '>>>>> foodcritic gem not loaded, omitting tasks' unless ENV['CI'] + end +end + +desc 'Run all style checks' +task style: ['style:chef', 'style:ruby'] + +# Integration tests. Kitchen.ci +namespace :integration do + begin + require 'kitchen/rake_tasks' + + desc 'Run kitchen integration tests' + Kitchen::RakeTasks.new + rescue LoadError + puts '>>>>> Kitchen gem not loaded, omitting tasks' unless ENV['CI'] + end +end + +desc 'Run all tests on Travis' +task travis: ['style'] + +# Default +task default: ['style', 'integration:kitchen:all'] diff --git a/cookbooks/packagecloud/THANKS b/cookbooks/packagecloud/THANKS new file mode 100644 index 0000000..06242b2 --- /dev/null +++ b/cookbooks/packagecloud/THANKS @@ -0,0 +1,5 @@ +The following people have contributed to packagecloud chef cookbook (If you're not listed here and you should be, please let us know!): + +THANKS +------ +Guilhem Lettron (@guilhem) diff --git a/cookbooks/packagecloud/Thorfile b/cookbooks/packagecloud/Thorfile new file mode 100644 index 0000000..cb1aeae --- /dev/null +++ b/cookbooks/packagecloud/Thorfile @@ -0,0 +1,5 @@ +# encoding: utf-8 + +require 'bundler' +require 'bundler/setup' +require 'berkshelf/thor' diff --git a/cookbooks/packagecloud/Vagrantfile b/cookbooks/packagecloud/Vagrantfile new file mode 100644 index 0000000..e8bfb6c --- /dev/null +++ b/cookbooks/packagecloud/Vagrantfile @@ -0,0 +1,85 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +Vagrant.require_version ">= 1.5.0" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + # All Vagrant configuration is done here. The most common configuration + # options are documented and commented below. For a complete reference, + # please see the online documentation at vagrantup.com. + + config.vm.hostname = "packagecloud-berkshelf" + + # Set the version of chef to install using the vagrant-omnibus plugin + config.omnibus.chef_version = :latest + + # Every Vagrant virtual environment requires a box to build off of. + config.vm.box = "opscode_ubuntu-12.04_provisionerless" + + # The url from where the 'config.vm.box' box will be fetched if it + # doesn't already exist on the user's system. + config.vm.box_url = "https://opscode-vm-bento.s3.amazonaws.com/vagrant/opscode_ubuntu-12.04_provisionerless.box" + + # Assign this VM to a host-only network IP, allowing you to access it + # via the IP. Host-only networks can talk to the host machine as well as + # any other machines on the same network, but cannot be accessed (through this + # network interface) by any external networks. + config.vm.network :private_network, type: "dhcp" + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + # config.vm.provider :virtualbox do |vb| + # # Don't boot with headless mode + # vb.gui = true + # + # # Use VBoxManage to customize the VM. For example to change memory: + # vb.customize ["modifyvm", :id, "--memory", "1024"] + # end + # + # View the documentation for the provider you're using for more + # information on available options. + + # The path to the Berksfile to use with Vagrant Berkshelf + # config.berkshelf.berksfile_path = "./Berksfile" + + # Enabling the Berkshelf plugin. To enable this globally, add this configuration + # option to your ~/.vagrant.d/Vagrantfile file + config.berkshelf.enabled = true + + # An array of symbols representing groups of cookbook described in the Vagrantfile + # to exclusively install and copy to Vagrant's shelf. + # config.berkshelf.only = [] + + # An array of symbols representing groups of cookbook described in the Vagrantfile + # to skip installing and copying to Vagrant's shelf. + # config.berkshelf.except = [] + + config.vm.provision :chef_solo do |chef| + chef.json = { + mysql: { + server_root_password: 'rootpass', + server_debian_password: 'debpass', + server_repl_password: 'replpass' + } + } + + chef.run_list = [ + "recipe[packagecloud::default]" + ] + end +end diff --git a/cookbooks/packagecloud/attributes/default.rb b/cookbooks/packagecloud/attributes/default.rb new file mode 100644 index 0000000..3d34858 --- /dev/null +++ b/cookbooks/packagecloud/attributes/default.rb @@ -0,0 +1,7 @@ +default['packagecloud']['base_repo_path'] = "/install/repositories/" +default['packagecloud']['gpg_key_path'] = "/gpg.key" + +default['packagecloud']['default_type'] = value_for_platform_family( + 'debian' => 'deb', + ['rhel', 'fedora'] => 'rpm' +) diff --git a/cookbooks/packagecloud/chefignore b/cookbooks/packagecloud/chefignore new file mode 100644 index 0000000..fef04fc --- /dev/null +++ b/cookbooks/packagecloud/chefignore @@ -0,0 +1,98 @@ +# Put files/directories that should be ignored in this file when uploading +# or sharing to the community site. +# Lines that start with '# ' are comments. + +# OS generated files # +###################### +.DS_Store +Icon? +nohup.out +ehthumbs.db +Thumbs.db + +# SASS # +######## +.sass-cache + +# EDITORS # +########### +\#* +.#* +*~ +*.sw[a-z] +*.bak +REVISION +TAGS* +tmtags +*_flymake.* +*_flymake +*.tmproj +.project +.settings +mkmf.log + +## COMPILED ## +############## +a.out +*.o +*.pyc +*.so +*.com +*.class +*.dll +*.exe +*/rdoc/ + +# Testing # +########### +.watchr +.rspec +spec/* +spec/fixtures/* +test/* +features/* +Guardfile +Procfile + +# SCM # +####### +.git +*/.git +.gitignore +.gitmodules +.gitconfig +.gitattributes +.svn +*/.bzr/* +*/.hg/* +*/.svn/* + +# Berkshelf # +############# +cookbooks/* +tmp + +# Cookbooks # +############# +CONTRIBUTING +CHANGELOG* + +# Strainer # +############ +Colanderfile +Strainerfile +.colander +.strainer + +# Vagrant # +########### +.vagrant +Vagrantfile + +# Travis # +########## +.travis.yml + +# tmux # +########## +.tmux diff --git a/cookbooks/packagecloud/libraries/helper.rb b/cookbooks/packagecloud/libraries/helper.rb new file mode 100644 index 0000000..e548748 --- /dev/null +++ b/cookbooks/packagecloud/libraries/helper.rb @@ -0,0 +1,43 @@ +require 'net/https' + +module PackageCloud + module Helper + def get(uri, params) + uri.query = URI.encode_www_form(params) + req = Net::HTTP::Get.new(uri.request_uri) + + req.basic_auth uri.user, uri.password if uri.user + + http = Net::HTTP.new(uri.hostname, uri.port) + http.use_ssl = true + + resp = http.start { |h| h.request(req) } + + case resp + when Net::HTTPSuccess + resp + else + raise resp.inspect + end + end + + def post(uri, params) + req = Net::HTTP::Post.new(uri.request_uri) + req.form_data = params + + req.basic_auth uri.user, uri.password if uri.user + + http = Net::HTTP.new(uri.hostname, uri.port) + http.use_ssl = true + + resp = http.start { |h| h.request(req) } + + case resp + when Net::HTTPSuccess + resp + else + raise resp.inspect + end + end + end +end diff --git a/cookbooks/packagecloud/libraries/matcher.rb b/cookbooks/packagecloud/libraries/matcher.rb new file mode 100644 index 0000000..e518177 --- /dev/null +++ b/cookbooks/packagecloud/libraries/matcher.rb @@ -0,0 +1,7 @@ +if defined?(ChefSpec) + + def create_packagecloud_repo(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:packagecloud_repo, :add, resource_name) + end + +end diff --git a/cookbooks/packagecloud/metadata.json b/cookbooks/packagecloud/metadata.json new file mode 100644 index 0000000..58fa0f9 --- /dev/null +++ b/cookbooks/packagecloud/metadata.json @@ -0,0 +1,41 @@ +{ + "name": "packagecloud", + "description": "Installs/Configures packagecloud.io repositories.", + "long_description": "Installs/Configures packagecloud.io repositories.", + "maintainer": "Joe Damato", + "maintainer_email": "joe@packagecloud.io", + "license": "Apache 2.0", + "platforms": { + + }, + "dependencies": { + + }, + "recommendations": { + + }, + "suggestions": { + + }, + "conflicting": { + + }, + "providing": { + + }, + "replacing": { + + }, + "attributes": { + + }, + "groupings": { + + }, + "recipes": { + + }, + "version": "0.0.18", + "source_url": "", + "issues_url": "" +} diff --git a/cookbooks/packagecloud/metadata.rb b/cookbooks/packagecloud/metadata.rb new file mode 100644 index 0000000..eedc2b9 --- /dev/null +++ b/cookbooks/packagecloud/metadata.rb @@ -0,0 +1,7 @@ +name 'packagecloud' +maintainer 'Joe Damato' +maintainer_email 'joe@packagecloud.io' +license 'Apache 2.0' +description 'Installs/Configures packagecloud.io repositories.' +long_description 'Installs/Configures packagecloud.io repositories.' +version '0.0.18' diff --git a/cookbooks/packagecloud/providers/repo.rb b/cookbooks/packagecloud/providers/repo.rb new file mode 100644 index 0000000..045901d --- /dev/null +++ b/cookbooks/packagecloud/providers/repo.rb @@ -0,0 +1,207 @@ +include ::PackageCloud::Helper + +require 'uri' + +use_inline_resources if defined?(use_inline_resources) + +action :add do + case new_resource.type + when 'deb' + install_deb + when 'rpm' + install_rpm + when 'gem' + install_gem + else + raise "#{new_resource.type} is an unknown package type." + end +end + +def install_deb + base_url = new_resource.base_url + repo_url = construct_uri_with_options({base_url: base_url, repo: new_resource.repository, endpoint: node['platform']}) + + Chef::Log.debug("#{new_resource.name} deb repo url = #{repo_url}") + + package 'apt-transport-https' + + template "/etc/apt/sources.list.d/#{filename}.list" do + source 'apt.erb' + cookbook 'packagecloud' + mode '0644' + variables :base_url => read_token(repo_url).to_s, + :distribution => node['lsb']['codename'], + :component => 'main' + + notifies :run, "execute[apt-key-add-#{filename}]", :immediately + notifies :run, "execute[apt-get-update-#{filename}]", :immediately + end + + gpg_key_url = ::File.join(base_url, node['packagecloud']['gpg_key_path']) + + execute "apt-key-add-#{filename}" do + command "wget -qO - #{gpg_key_url} | apt-key add -" + action :nothing + end + + execute "apt-get-update-#{filename}" do + command "apt-get update -o Dir::Etc::sourcelist=\"sources.list.d/#{filename}.list\"" \ + " -o Dir::Etc::sourceparts=\"-\"" \ + " -o APT::Get::List-Cleanup=\"0\"" + action :nothing + end +end + +def install_rpm + given_base_url = new_resource.base_url + + base_repo_url = ::File.join(given_base_url, node['packagecloud']['base_repo_path']) + + base_url_endpoint = construct_uri_with_options({base_url: base_repo_url, repo: new_resource.repository, endpoint: 'rpm_base_url'}) + + gpg_filename = URI.parse(base_repo_url).host.gsub!('.', '_') + + if new_resource.master_token + base_url_endpoint.user = new_resource.master_token + base_url_endpoint.password = '' + end + + base_url = URI(get(base_url_endpoint, install_endpoint_params).body.chomp) + + Chef::Log.debug("#{new_resource.name} rpm base url = #{base_url}") + + package 'pygpgme' do + ignore_failure true + end + + log 'pygpgme_warning' do + message 'The pygpgme package could not be installed. This means GPG verification is not possible for any RPM installed on your system. ' \ + 'To fix this, add a repository with pygpgme. Usualy, the EPEL repository for your system will have this. ' \ + 'More information: https://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F and https://github.com/opscode-cookbooks/yum-epel' + + level :warn + not_if 'rpm -qa | grep -qw pygpgme' + end + + ruby_block 'disable repo_gpgcheck if no pygpgme' do + block do + template = run_context.resource_collection.find(:template => "/etc/yum.repos.d/#{filename}.repo") + template.variables[:repo_gpgcheck] = 0 + end + not_if 'rpm -qa | grep -qw pygpgme' + end + + remote_file "/etc/pki/rpm-gpg/RPM-GPG-KEY-#{gpg_filename}" do + source ::File.join(given_base_url, node['packagecloud']['gpg_key_path']) + mode '0644' + end + + template "/etc/yum.repos.d/#{filename}.repo" do + source 'yum.erb' + cookbook 'packagecloud' + mode '0644' + variables :base_url => read_token(base_url).to_s, + :gpg_filename => gpg_filename, + :name => filename, + :repo_gpgcheck => 1, + :description => filename, + :priority => new_resource.priority, + :metadata_expire => new_resource.metadata_expire + + notifies :run, "execute[yum-makecache-#{filename}]", :immediately + notifies :create, "ruby_block[yum-cache-reload-#{filename}]", :immediately + end + + # get the metadata for this repo only + execute "yum-makecache-#{filename}" do + command "yum -q makecache -y --disablerepo=* --enablerepo=#{filename}" + action :nothing + end + + # reload internal Chef yum cache + ruby_block "yum-cache-reload-#{filename}" do + block { Chef::Provider::Package::Yum::YumCache.instance.reload } + action :nothing + end +end + +def install_gem + base_url = new_resource.base_url + + repo_url = construct_uri_with_options({base_url: base_url, repo: new_resource.repository}) + repo_url = read_token(repo_url, true).to_s + + + execute "install packagecloud #{new_resource.name} repo as gem source" do + command "gem source --add #{repo_url}" + not_if "gem source --list | grep #{repo_url}" + end +end + +def read_token(repo_url, gems=false) + return repo_url unless new_resource.master_token + + base_url = new_resource.base_url + + base_repo_url = ::File.join(base_url, node['packagecloud']['base_repo_path']) + + uri = construct_uri_with_options({base_url: base_repo_url, repo: new_resource.repository, endpoint: 'tokens.text'}) + uri.user = new_resource.master_token + uri.password = '' + + resp = post(uri, install_endpoint_params) + + Chef::Log.debug("#{new_resource.name} TOKEN = #{resp.body.chomp}") + + if is_rhel5? && !gems + repo_url + else + repo_url.user = resp.body.chomp + repo_url.password = '' + repo_url + end +end + +def install_endpoint_params + dist = value_for_platform_family( + 'debian' => node['lsb']['codename'], + ['rhel', 'fedora'] => node['platform_version'], + ) + + if node['fqdn'].nil? + Chef::Log.fatal("This node's fqdn is set to nil, so a read token cannot be issued!" \ + "Please change your fqdn settings.") + end + + { :os => node['platform'], + :dist => dist, + :name => node['fqdn'] } +end + +def filename + new_resource.name.gsub(/[^0-9A-z.\-]/, '_') +end + +def is_rhel5? + platform_family?('rhel') && node['platform_version'].to_i == 5 +end + +def construct_uri_with_options(options) + required_options = [:base_url, :repo] + + required_options.each do |opt| + if !options[opt] + raise ArgumentError, + "A required option :#{opt} was not specified" + end + end + + options[:base_url] = append_trailing_slash(options[:base_url]) + options[:repo] = append_trailing_slash(options[:repo]) + + URI.join(options.delete(:base_url), options.inject([]) {|mem, opt| mem << opt[1]}.join) +end + +def append_trailing_slash(str) + str.end_with?("/") ? str : str << "/" +end diff --git a/cookbooks/packagecloud/resources/repo.rb b/cookbooks/packagecloud/resources/repo.rb new file mode 100644 index 0000000..6167e15 --- /dev/null +++ b/cookbooks/packagecloud/resources/repo.rb @@ -0,0 +1,10 @@ +actions :add +default_action :add + +attribute :repository, :kind_of => String, :name_attribute => true +attribute :master_token, :kind_of => String +attribute :type, :kind_of => String, :equal_to => ['deb', 'rpm', 'gem'], :default => node['packagecloud']['default_type'] +attribute :base_url, :kind_of => String, :default => "https://packagecloud.io" +attribute :gpg_key_url, :kind_of => String, :default => node['packagecloud']['gpg_key_url'] +attribute :priority, :kind_of => [Fixnum, TrueClass, FalseClass], :default => false +attribute :metadata_expire, :kind_of => String, :regex => [/^\d+[d|h|m]?$/], :default => nil diff --git a/cookbooks/packagecloud/templates/default/apt.erb b/cookbooks/packagecloud/templates/default/apt.erb new file mode 100644 index 0000000..a38981c --- /dev/null +++ b/cookbooks/packagecloud/templates/default/apt.erb @@ -0,0 +1,2 @@ +deb <%= @base_url %> <%= @distribution %> <%= @component %> +deb-src <%= @base_url %> <%= @distribution %> <%= @component %> diff --git a/cookbooks/packagecloud/templates/default/yum.erb b/cookbooks/packagecloud/templates/default/yum.erb new file mode 100644 index 0000000..f862018 --- /dev/null +++ b/cookbooks/packagecloud/templates/default/yum.erb @@ -0,0 +1,15 @@ +[<%= @name %>] +name=<%= @description %> +baseurl=<%= @base_url %> +repo_gpgcheck=<%= @repo_gpgcheck %> +<% if @priority -%> +priority=<%=@priority %> +<% end -%> +gpgcheck=0 +enabled=1 +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-<%= @gpg_filename %> +sslverify=1 +sslcacert=/etc/pki/tls/certs/ca-bundle.crt +<% if @metadata_expire %> +metadata_expire=<%= @metadata_expire %> +<% end %> diff --git a/cookbooks/partial_search/CHANGELOG.md b/cookbooks/partial_search/CHANGELOG.md new file mode 100644 index 0000000..5f12087 --- /dev/null +++ b/cookbooks/partial_search/CHANGELOG.md @@ -0,0 +1,31 @@ +partial_search Cookbook CHANGELOG +================================= +This file is used to list changes made in each version of the partial_search cookbook. + + +v1.0.8 (2014-02-25) +------------------- +- [COOK-4260] Update compatibility in README.md + + +v1.0.6 +------ +- **Hotfix** - Revert client-side caching bug + + +v1.0.4 +------ +### New Feature +- **[COOK-2584](https://tickets.opscode.com/browse/COOK-2584)** - Add client-side result cache + + +v1.0.2 +------ +### Bug + +- [COOK-3164]: `partial_search` should use + `Chef::Config[:chef_server_url]` instead of `search_url` + +v1.0.0 +------ +- Initial release diff --git a/cookbooks/partial_search/README.md b/cookbooks/partial_search/README.md new file mode 100644 index 0000000..9efe16e --- /dev/null +++ b/cookbooks/partial_search/README.md @@ -0,0 +1,82 @@ +Partial Search Cookbook +======================= +[Partial Search](http://docs.opscode.com/essentials_search.html#partial-search) +is a search API available on Chef Server. (see Notes below for version compatibility) +It can be used to reduce the network bandwidth and the memory used by +chef-client to process search results. + +This cookbook provides an experimental interface to the partial search +API by providing a `partial_search` method that can be used instead of +the `search` method in your recipes. + +Since Chef Client 11.10.0 the partial_search capability has been built-in +so it does not require this cookbook. + +The `partial_search` method allows you to retrieve just the attributes +of interest. For example, you can execute a search to return just the +name and IP addresses of the nodes in your infrastructure rather than +receiving an array of complete node objects and post-processing them. + + +Install +------- +Upload this cookbook and include it in the dependencies of any +cookbook where you would like to use `partial_search`. + + +Usage +----- +When you call `partial_search`, you need to specify the key paths of the +attributes you want returned. Key paths are specified as an array +of strings. Each key path is mapped to a short name of your +choosing. Consider the following example: + +```ruby +partial_search(:node, 'role:web', + :keys => { 'name' => [ 'name' ], + 'ip' => [ 'ipaddress' ], + 'kernel_version' => [ 'kernel', 'version' ] + } +).each do |result| + puts result['name'] + puts result['ip'] + puts result['kernel_version'] +end +``` + +In the example above, two attributes will be extracted (on the +server) from the nodes that match the search query. The result will +be a simple hash with keys 'name' and 'ip'. + + +Notes +----- +* We would like your feedback on this feature and the interface + provided by this cookbook. Please send comments to the chef-dev + mailing list. + +* The partial search API is available in the Open Source Chef Server since 11.0.4 + +* The partial search API is available in Enterprise Chef Server since 1.2.2 + + +License & Authors +----------------- +- Author:: Adam Jacob () +- Author:: John Keiser () + +```text +Copyright:: 2012-2013, Opscode, Inc. + +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/cookbooks/partial_search/libraries/partial_search.rb b/cookbooks/partial_search/libraries/partial_search.rb new file mode 100644 index 0000000..f506548 --- /dev/null +++ b/cookbooks/partial_search/libraries/partial_search.rb @@ -0,0 +1,147 @@ +# +# Author:: Adam Jacob () +# Author:: John Keiser () +# Copyright:: Copyright (c) 2012 Opscode, Inc. +# 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. +# + +require 'chef/config' +require 'uri' +require 'chef/rest' +# These are needed so that JSON can inflate search results +require 'chef/node' +require 'chef/role' +require 'chef/environment' +require 'chef/data_bag' +require 'chef/data_bag_item' + +class Chef + class PartialSearch + + attr_accessor :rest + + def initialize(url=nil) + @rest = ::Chef::REST.new(url || ::Chef::Config[:chef_server_url]) + end + + # Search Solr for objects of a given type, for a given query. If you give + # it a block, it will handle the paging for you dynamically. + def search(type, query='*:*', args={}, &block) + raise ArgumentError, "Type must be a string or a symbol!" unless (type.kind_of?(String) || type.kind_of?(Symbol)) + + sort = args.include?(:sort) ? args[:sort] : 'X_CHEF_id_CHEF_X asc' + start = args.include?(:start) ? args[:start] : 0 + rows = args.include?(:rows) ? args[:rows] : 1000 + query_string = "search/#{type}?q=#{escape(query)}&sort=#{escape(sort)}&start=#{escape(start)}&rows=#{escape(rows)}" + if args[:keys] + response = @rest.post_rest(query_string, args[:keys]) + response_rows = response['rows'].map { |row| row['data'] } + else + response = @rest.get_rest(query_string) + response_rows = response['rows'] + end + if block + response_rows.each { |o| block.call(o) unless o.nil?} + unless (response["start"] + response_rows.length) >= response["total"] + nstart = response["start"] + rows + args_hash = { + :keys => args[:keys], + :sort => sort, + :start => nstart, + :rows => rows + } + search(type, query, args_hash, &block) + end + true + else + [ response_rows, response["start"], response["total"] ] + end + end + + def list_indexes + response = @rest.get_rest("search") + end + + private + def escape(s) + s && URI.escape(s.to_s) + end + end +end + +# partial_search(type, query, options, &block) +# +# Searches for nodes, roles, etc. and returns the results. This method may +# perform more than one search request, if there are a large number of results. +# +# ==== Parameters +# * +type+: index type (:role, :node, :client, :environment, data bag name) +# * +query+: SOLR query. "*:*", "role:blah", "not role:blah", etc. Defaults to '*:*' +# * +options+: hash with options: +# ** +:start+: First row to return (:start => 50, :rows => 100 means "return the +# 50th through 150th result") +# ** +:rows+: Number of rows to return. Defaults to 1000. +# ** +:sort+: a SOLR sort specification. Defaults to 'X_CHEF_id_CHEF_X asc'. +# ** +:keys+: partial search keys. If this is not specified, the search will +# not be partial. +# +# ==== Returns +# +# This method returns an array of search results. Partial search results will +# be JSON hashes with the structure specified in the +keys+ option. Other +# results include +Chef::Node+, +Chef::Role+, +Chef::Client+, +Chef::Environment+, +# +Chef::DataBag+ and +Chef::DataBagItem+ objects, depending on the search type. +# +# If a block is specified, the block will be called with each result instead of +# returning an array. This method will not block if it returns +# +# If start or row is specified, and no block is given, the result will be a +# triple containing the list, the start and total: +# +# [ [ row1, row2, ... ], start, total ] +# +# ==== Example +# +# partial_search(:node, 'role:webserver', +# keys: { +# name: [ 'name' ], +# ip: [ 'amazon', 'ip', 'public' ] +# } +# ).each do |node| +# puts "#{node[:name]}: #{node[:ip]}" +# end +# +def partial_search(type, query='*:*', *args, &block) + # Support both the old (positional args) and new (hash args) styles of calling + if args.length == 1 && args[0].is_a?(Hash) + args_hash = args[0] + else + args_hash = {} + args_hash[:sort] = args[0] if args.length >= 1 + args_hash[:start] = args[1] if args.length >= 2 + args_hash[:rows] = args[2] if args.length >= 3 + end + # If you pass a block, or have the start or rows arguments, do raw result parsing + if Kernel.block_given? || args_hash[:start] || args_hash[:rows] + Chef::PartialSearch.new.search(type, query, args_hash, &block) + # Otherwise, do the iteration for the end user + else + results = Array.new + Chef::PartialSearch.new.search(type, query, args_hash) do |o| + results << o + end + results + end +end diff --git a/cookbooks/partial_search/metadata.json b/cookbooks/partial_search/metadata.json new file mode 100644 index 0000000..eca0a08 --- /dev/null +++ b/cookbooks/partial_search/metadata.json @@ -0,0 +1,29 @@ +{ + "name": "partial_search", + "version": "1.0.8", + "description": "Provides experimental interface to partial search API in Opscode Hosted Chef", + "long_description": "Partial Search Cookbook\n=======================\n[Partial Search](http://docs.opscode.com/essentials_search.html#partial-search)\nis a search API available on Chef Server. (see Notes below for version compatibility) \nIt can be used to reduce the network bandwidth and the memory used by\nchef-client to process search results.\n\nThis cookbook provides an experimental interface to the partial search\nAPI by providing a `partial_search` method that can be used instead of\nthe `search` method in your recipes.\n\nSince Chef Client 11.10.0 the partial_search capability has been built-in\nso it does not require this cookbook.\n\nThe `partial_search` method allows you to retrieve just the attributes\nof interest. For example, you can execute a search to return just the\nname and IP addresses of the nodes in your infrastructure rather than\nreceiving an array of complete node objects and post-processing them.\n\n\nInstall\n-------\nUpload this cookbook and include it in the dependencies of any\ncookbook where you would like to use `partial_search`.\n\n\nUsage\n-----\nWhen you call `partial_search`, you need to specify the key paths of the\nattributes you want returned. Key paths are specified as an array\nof strings. Each key path is mapped to a short name of your\nchoosing. Consider the following example:\n\n```ruby\npartial_search(:node, 'role:web',\n :keys => { 'name' => [ 'name' ],\n 'ip' => [ 'ipaddress' ],\n 'kernel_version' => [ 'kernel', 'version' ]\n }\n).each do |result|\n puts result['name']\n puts result['ip']\n puts result['kernel_version']\nend\n```\n\nIn the example above, two attributes will be extracted (on the\nserver) from the nodes that match the search query. The result will\nbe a simple hash with keys 'name' and 'ip'.\n\n\nNotes\n-----\n* We would like your feedback on this feature and the interface\n provided by this cookbook. Please send comments to the chef-dev\n mailing list.\n\n* The partial search API is available in the Open Source Chef Server since 11.0.4\n\n* The partial search API is available in Enterprise Chef Server since 1.2.2\n\n\nLicense & Authors\n-----------------\n- Author:: Adam Jacob ()\n- Author:: John Keiser ()\n\n```text\nCopyright:: 2012-2013, Opscode, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "maintainer": "Opscode, Inc.", + "maintainer_email": "cookbooks@opscode.com", + "license": "Apache 2.0", + "platforms": { + }, + "dependencies": { + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + } +} \ No newline at end of file diff --git a/cookbooks/partial_search/metadata.rb b/cookbooks/partial_search/metadata.rb new file mode 100644 index 0000000..23d01f0 --- /dev/null +++ b/cookbooks/partial_search/metadata.rb @@ -0,0 +1,7 @@ +name "partial_search" +maintainer "Opscode, Inc." +maintainer_email "cookbooks@opscode.com" +license "Apache 2.0" +description "Provides experimental interface to partial search API in Opscode Hosted Chef" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version "1.0.8" diff --git a/cookbooks/partial_search/recipes/default.rb b/cookbooks/partial_search/recipes/default.rb new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/cookbooks/partial_search/recipes/default.rb @@ -0,0 +1 @@ +# diff --git a/cookbooks/php/CHANGELOG.md b/cookbooks/php/CHANGELOG.md new file mode 100644 index 0000000..ece8dcb --- /dev/null +++ b/cookbooks/php/CHANGELOG.md @@ -0,0 +1,139 @@ +php Cookbook CHANGELOG +====================== +This file is used to list changes made in each version of the php cookbook. + +v1.5.0 (2014-10-06) +------------------- +- Adding package_options attribute, utilizing in package resource + +v1.4.6 (2014-03-19) +------------------- +- [COOK-4436] - Test this cookbook, not yum. Also test Fedora 20. +- [COOK-4427] - Add oracle as supported operating system + + +v1.4.4 (2014-03-12) +------------------- +- [COOK-4393] - Fix convergence bug in source install + + +v1.4.2 (2014-02-27) +------------------- +[COOK-4300] - Simplified and fixed pear/pecl logic. [Fixes #56 / #57] + + +v1.4.0 (2014-02-27) +------------------- +[COOK-3639] - Allow users to specify php.ini source template + + +v1.3.14 (2014-02-21) +-------------------- +### Bug +- **[COOK-4186](https://tickets.opscode.com/browse/COOK-4186)** - Upgrade_package concatenates an empty version string when version is not set or is empty. + + +v1.3.12 (2014-01-28) +-------------------- +Fix github issue 'Cannot find a resource for preferred_state' + + +v1.3.10 +------- +Fixing my stove + + +v1.3.8 +------ +Version bump to ensure artifact sanity + + +v1.3.6 +------ +Version bump for toolchain + + +v1.3.4 +------ +Adding platform_family check to include_recipe in source.rb + + +v1.3.2 +------ +Fixing style cops. Updating test harness + + +v1.3.0 +------ +### Bug +- **[COOK-3479](https://tickets.opscode.com/browse/COOK-3479)** - Added Windows support to PHP +- **[COOK-2909](https://tickets.opscode.com/browse/COOK-2909)** - Warnings about Chef::Exceptions::ShellCommandFailed is deprecated + + +v1.2.6 +------ +### Bug +- **[COOK-3628](https://tickets.opscode.com/browse/COOK-3628)** - Fix PHP download URL +- **[COOK-3568](https://tickets.opscode.com/browse/COOK-3568)** - Fix Test Kitchen tests +- **[COOK-3402](https://tickets.opscode.com/browse/COOK-3402)** - When the `ext_dir` setting is present, configure php properly for the source recipe +- **[COOK-2926](https://tickets.opscode.com/browse/COOK-2926)** - Fix pear package detection when installing specific version + + +v1.2.4 +------ +### Improvement +- **[COOK-3047](https://tickets.opscode.com/browse/COOK-3047)** - Sort directives in `php.ini` +- **[COOK-2928](https://tickets.opscode.com/browse/COOK-2928)** - Abstract `php.ini` directives into variables + +### Bug +- **[COOK-2378](https://tickets.opscode.com/browse/COOK-2378)** - Fix `php_pear` for libevent + +v1.2.2 +------ +### Bug +- [COOK-3050]: `lib_dir` declared in wrong place for redhat +- [COOK-3102]: remove fileinfo recipe from php cookbook + +### Improvement +- [COOK-3101]: use a method to abstract range of "el 5" versions in php recipes + +v1.2.0 +------ +### Improvement +- [COOK-2516]: Better support for SUSE distribution for php cookbook +- [COOK-3035]: update php::source to install 5.4.15 by default + +### Bug +- [COOK-2463]: PHP PEAR Provider Installs Most Recent Version, Without Respect to Preferred State +- [COOK-2514]: php_pear: does not handle more exotic version strings + +v1.1.8 +------ +- [COOK-1998] - Enable override of PHP packages in attributes + +v1.1.6 +------ +- [COOK-2324] - adds Oracle linux support + +v1.1.4 +------ +- [COOK-2106] - `php_pear` cannot find available packages + +v1.1.2 +------ +- [COOK-1803] - use better regexp to match package name +- [COOK-1926] - support Amazon linux + +v1.1.0 +------ +- [COOK-543] - php.ini template should be configurable +- [COOK-1067] - support for PECL zend extensions +- [COOK-1193] - update package names for EPEL 6 +- [COOK-1348] - rescue Mixlib::ShellOut::ShellCommandFailed (chef 0.10.10) +- [COOK-1465] - fix pear extension template + +v1.0.2 +------ +- [COOK-993] Add mhash-devel to centos php source libs +- [COOK-989] - bump version of php to 5.3.10 +- Also download the .tar.gz instead of .tar.bz2 as bzip2 may not be in the base OS (e.g., CentOS 6 minimal) diff --git a/cookbooks/php/README.md b/cookbooks/php/README.md new file mode 100644 index 0000000..df386de --- /dev/null +++ b/cookbooks/php/README.md @@ -0,0 +1,347 @@ +php Cookbook +============ +Installs and configures PHP 5.3 and the PEAR package management system. Also includes LWRPs for managing PEAR (and PECL) packages along with PECL channels. + +Requirements +------------ +### Platforms +- Debian, Ubuntu +- CentOS, Red Hat, Fedora, Amazon Linux +- Microsoft Windows + +### Cookbooks +- build-essential +- xml +- mysql + +These cookbooks are only used when building PHP from source. + + +Attributes +---------- +- `node['php']['install_method']` = method to install php with, default `package`. +- `node['php']['directives']` = Hash of directives and values to append to `php.ini`, default `{}`. + +The file also contains the following attribute types: + +* platform specific locations and settings. +* source installation settings + + +Resource/Provider +----------------- +This cookbook includes LWRPs for managing: + +- PEAR channels +- PEAR/PECL packages + +### `php_pear_channel` +[PEAR Channels](http://pear.php.net/manual/en/guide.users.commandline.channels.php) are alternative sources for PEAR packages. This LWRP provides and easy way to manage these channels. + +#### Actions +- :discover: Initialize a channel from its server. +- :add: Add a channel to the channel list, usually only used to add private channels. Public channels are usually added using the `:discover` action +- :update: Update an existing channel +- :remove: Remove a channel from the List + +#### Attribute Parameters +- channel_name: name attribute. The name of the channel to discover +- channel_xml: the channel.xml file of the channel you are adding + +#### Examples +```ruby +# discover the horde channel +php_pear_channel "pear.horde.org" do + action :discover +end + +# download xml then add the symfony channel +remote_file "#{Chef::Config[:file_cache_path]}/symfony-channel.xml" do + source "http://pear.symfony-project.com/channel.xml" + mode 0644 +end +php_pear_channel "symfony" do + channel_xml "#{Chef::Config[:file_cache_path]}/symfony-channel.xml" + action :add +end + +# update the main pear channel +php_pear_channel 'pear.php.net' do + action :update +end + +# update the main pecl channel +php_pear_channel 'pecl.php.net' do + action :update +end +``` + +### `php_pear` +[PEAR](http://pear.php.net/) is a framework and distribution system for reusable PHP components. [PECL](http://pecl.php.net/) is a repository for PHP Extensions. PECL contains C extensions for compiling into PHP. As C programs, PECL extensions run more efficiently than PEAR packages. PEARs and PECLs use the same packaging and distribution system. As such this LWRP is clever enough to abstract away the small differences and can be used for managing either. This LWRP also creates the proper module .ini file for each PECL extension at the correct location for each supported platform. + +#### Actions +- :install: Install a pear package - if version is provided, install that specific version +- :upgrade: Upgrade a pear package - if version is provided, upgrade to that specific version +- :remove: Remove a pear package +- :purge: Purge a pear package (this usually entails removing configuration files as well as the package itself). With pear packages this behaves the same as `:remove` + +#### Attribute Parameters +- package_name: name attribute. The name of the pear package to install +- version: the version of the pear package to install/upgrade. If no version is given latest is assumed. +- preferred_state: PEAR by default installs stable packages only, this allows you to install pear packages in a devel, alpha or beta state +- directives: extra extension directives (settings) for a pecl. on most platforms these usually get rendered into the extension's .ini file +- zend_extensions: extension filenames which should be loaded with zend_extension. +- options: Add additional options to the underlying pear package command + +#### Examples +```ruby +# upgrade a pear +php_pear "XML_RPC" do + action :upgrade +end + + +# install a specific version +php_pear "XML_RPC" do + version "1.5.4" + action :install +end + + +# install the mongodb pecl +php_pear "mongo" do + action :install +end + +# install the xdebug pecl +php_pear "xdebug" do + # Specify that xdebug.so must be loaded as a zend extension + zend_extensions ['xdebug.so'] + action :install +end + + +# install apc pecl with directives +php_pear "apc" do + action :install + directives(:shm_size => 128, :enable_cli => 1) +end + + +# install the beta version of Horde_Url +# from the horde channel +hc = php_pear_channel "pear.horde.org" do + action :discover +end +php_pear "Horde_Url" do + preferred_state "beta" + channel hc.channel_name + action :install +end + + +# install the YAML pear from the symfony project +sc = php_pear_channel "pear.symfony-project.com" do + action :discover +end +php_pear "YAML" do + channel sc.channel_name + action :install +end +``` + + +Recipes +------- +### default +Include the default recipe in a run list, to get `php`. By default `php` is installed from packages but this can be changed by using the `install_method` attribute. + +### package +This recipe installs PHP from packages. + +### source +This recipe installs PHP from source. + + +Deprecated Recipes +------------------ +The following recipes are deprecated and will be removed from a future version of this cookbook. + +- `module_apc` +- `module_curl` +- `module_fileinfo` +- `module_fpdf` +- `module_gd` +- `module_ldap` +- `module_memcache` +- `module_mysql` +- `module_pgsql` +- `module_sqlite3` + +The installation of the php modules in these recipes can now be accomplished by installing from a native package or via the new php_pear LWRP. For example, the functionality of the `module_memcache` recipe can be enabled in the following ways: + +```ruby +# using apt +package "php5-memcache" do + action :install +end + +# using pear LWRP +php_pear "memcache" do + action :install +end +``` + + +Usage +----- +Simply include the `php` recipe where ever you would like php installed. To install from source override the `node['php']['install_method']` attribute with in a role: + +```ruby +name "php" +description "Install php from source" +override_attributes( + "php" => { + "install_method" => "source" + } +) +run_list( + "recipe[php]" +) +``` + + +Development +----------- +This section details "quick development" steps. For a detailed explanation, see [[Contributing.md]]. + +1. Clone this repository from GitHub: + + $ git clone git@github.com:opscode-cookbooks/php.git + +2. Create a git branch + + $ git checkout -b my_bug_fix + +3. Install dependencies: + + $ bundle install + +4. Make your changes/patches/fixes, committing appropiately +5. **Write tests** +6. Run the tests: + - `bundle exec foodcritic -f any .` + - `bundle exec rspec` + - `bundle exec rubocop` + - `bundle exec kitchen test` + + In detail: + - Foodcritic will catch any Chef-specific style errors + - RSpec will run the unit tests + - Rubocop will check for Ruby-specific style errors + - Test Kitchen will run and converge the recipes + + +License & Authors +----------------- +- Author:: Seth Chisamore () +- Author:: Joshua Timberman () +- Author:: Julian C. Dunn () + +```text +Copyright:: 2013, Chef Software, Inc. + +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. +``` + +Note: This cookbook contains a modified copy of `go-phar.pear` for use on the +Microsoft Windows platform only to correct an (upstream bug)[http://pear.php.net/bugs/bug.php?id=16644]. The original +`go-pear.phar` is licensed under the (PHP License version 2.02)[http://www.php.net/license/2_02.txt]: + +``` +-------------------------------------------------------------------- + The PHP License, version 2.02 +Copyright (c) 1999 - 2002 The PHP Group. All rights reserved. +-------------------------------------------------------------------- + +Redistribution and use in source and binary forms, with or without +modification, is permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. The name "PHP" must not be used to endorse or promote products + derived from this software without prior permission from the + PHP Group. This does not apply to add-on libraries or tools + that work in conjunction with PHP. In such a case the PHP + name may be used to indicate that the product supports PHP. + + 4. The PHP Group may publish revised and/or new versions of the + license from time to time. Each version will be given a + distinguishing version number. + Once covered code has been published under a particular version + of the license, you may always continue to use it under the + terms of that version. You may also choose to use such covered + code under the terms of any subsequent version of the license + published by the PHP Group. No one other than the PHP Group has + the right to modify the terms applicable to covered code created + under this License. + + 5. Redistributions of any form whatsoever must retain the following + acknowledgment: + "This product includes PHP, freely available from + http://www.php.net/". + + 6. The software incorporates the Zend Engine, a product of Zend + Technologies, Ltd. ("Zend"). The Zend Engine is licensed to the + PHP Association (pursuant to a grant from Zend that can be + found at http://www.php.net/license/ZendGrant/) for + distribution to you under this license agreement, only as a + part of PHP. In the event that you separate the Zend Engine + (or any portion thereof) from the rest of the software, or + modify the Zend Engine, or any portion thereof, your use of the + separated or modified Zend Engine software shall not be governed + by this license, and instead shall be governed by the license + set forth at http://www.zend.com/license/ZendLicense/. + + + +THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND +ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP +DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------- + +This software consists of voluntary contributions made by many +individuals on behalf of the PHP Group. + +The PHP Group can be contacted via Email at group@php.net. + +For more information on the PHP Group and the PHP project, +please see . +``` diff --git a/cookbooks/php/attributes/default.rb b/cookbooks/php/attributes/default.rb new file mode 100644 index 0000000..2c67f7d --- /dev/null +++ b/cookbooks/php/attributes/default.rb @@ -0,0 +1,126 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: php +# Attribute:: default +# +# Copyright 2011, Opscode, Inc. +# +# 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. +# + +lib_dir = 'lib' +default['php']['install_method'] = 'package' +default['php']['directives'] = {} +default['php']['bin'] = 'php' + +default['php']['pear'] = 'pear' +default['php']['pecl'] = 'pecl' + +case node['platform_family'] +when 'rhel', 'fedora' + lib_dir = node['kernel']['machine'] =~ /x86_64/ ? 'lib64' : 'lib' + default['php']['conf_dir'] = '/etc' + default['php']['ext_conf_dir'] = '/etc/php.d' + default['php']['fpm_user'] = 'nobody' + default['php']['fpm_group'] = 'nobody' + default['php']['ext_dir'] = "/usr/#{lib_dir}/php/modules" + if node['platform_version'].to_f < 6 + default['php']['packages'] = %w{ php53 php53-devel php53-cli php-pear } + else + default['php']['packages'] = %w{ php php-devel php-cli php-pear } + end +when 'debian' + default['php']['conf_dir'] = '/etc/php5/cli' + default['php']['ext_conf_dir'] = '/etc/php5/conf.d' + default['php']['fpm_user'] = 'www-data' + default['php']['fpm_group'] = 'www-data' + default['php']['packages'] = %w{ php5-cgi php5 php5-dev php5-cli php-pear } +when 'suse' + default['php']['conf_dir'] = '/etc/php5/cli' + default['php']['ext_conf_dir'] = '/etc/php5/conf.d' + default['php']['fpm_user'] = 'wwwrun' + default['php']['fpm_group'] = 'www' + default['php']['packages'] = %w{ apache2-mod_php5 php5-pear } + lib_dir = node['kernel']['machine'] =~ /x86_64/ ? 'lib64' : 'lib' +when 'windows' + default['php']['windows']['msi_name'] = 'PHP 5.3.28' + default['php']['windows']['msi_source'] = 'http://windows.php.net/downloads/releases/php-5.3.28-nts-Win32-VC9-x86.msi' + default['php']['bin'] = 'php.exe' + default['php']['conf_dir'] = 'C:\Program Files (x86)\PHP' + default['php']['ext_conf_dir'] = node['php']['conf_dir'] + # These extensions are installed by default by the GUI MSI + default['php']['packages'] = %w{ cgi ScriptExecutable PEAR + iis4FastCGI ext_php_bz2 ext_php_curl + ext_php_exif ext_php_gd2 ext_php_gettext + ext_php_gmp ext_php_imap ext_php_mbstring + ext_php_mysql ext_php_mysqli ext_php_openssl + ext_php_pdo_mysql ext_php_pdo_odbc ext_php_pdo_sqlite + ext_php_pgsql ext_php_soap ext_php_sockets + ext_php_sqlite3 ext_php_tidy ext_php_xmlrpc + } + default['php']['package_options'] = "" # Use this to customise your yum or apt command + default['php']['pear'] = 'pear.bat' + default['php']['pecl'] = 'pecl.bat' +else + default['php']['conf_dir'] = '/etc/php5/cli' + default['php']['ext_conf_dir'] = '/etc/php5/conf.d' + default['php']['fpm_user'] = 'www-data' + default['php']['fpm_group'] = 'www-data' + default['php']['packages'] = %w{ php5-cgi php5 php5-dev php5-cli php-pear } +end + +default['php']['url'] = 'http://us1.php.net/get' +default['php']['version'] = '5.5.9' +default['php']['checksum'] = '378de162efdaeeb725ed38d7fe956c9f0b9084ff' +default['php']['prefix_dir'] = '/usr/local' + +default['php']['configure_options'] = %W{--prefix=#{php['prefix_dir']} + --with-libdir=#{lib_dir} + --with-config-file-path=#{php['conf_dir']} + --with-config-file-scan-dir=#{php['ext_conf_dir']} + --with-pear + --enable-fpm + --with-fpm-user=#{php['fpm_user']} + --with-fpm-group=#{php['fpm_group']} + --with-zlib + --with-openssl + --with-kerberos + --with-bz2 + --with-curl + --enable-ftp + --enable-zip + --enable-exif + --with-gd + --enable-gd-native-ttf + --with-gettext + --with-gmp + --with-mhash + --with-iconv + --with-imap + --with-imap-ssl + --enable-sockets + --enable-soap + --with-xmlrpc + --with-libevent-dir + --with-mcrypt + --enable-mbstring + --with-t1lib + --with-mysql + --with-mysqli=/usr/bin/mysql_config + --with-mysql-sock + --with-sqlite3 + --with-pdo-mysql + --with-pdo-sqlite} + +default['php']['ini']['template'] = "php.ini.erb" +default['php']['ini']['cookbook'] = "php" diff --git a/cookbooks/php/files/windows/go-pear.phar b/cookbooks/php/files/windows/go-pear.phar new file mode 100644 index 0000000..b3065c0 --- /dev/null +++ b/cookbooks/php/files/windows/go-pear.phar @@ -0,0 +1,98480 @@ + + * @author Greg Beaver + * @link http://www.synapticmedia.net Synaptic Media + * @version Id: Archive.php,v 1.52 2007/09/01 20:28:14 cellog Exp $ + * @package PHP_Archive + * @category PHP + */ + +class PHP_Archive +{ + const GZ = 0x00001000; + const BZ2 = 0x00002000; + const SIG = 0x00010000; + const SHA1 = 0x0002; + const MD5 = 0x0001; + /** + * Whether this archive is compressed with zlib + * + * @var bool + */ + private $_compressed; + /** + * @var string Real path to the .phar archive + */ + private $_archiveName = null; + /** + * Current file name in the phar + * @var string + */ + protected $currentFilename = null; + /** + * Length of current file in the phar + * @var string + */ + protected $internalFileLength = null; + /** + * Current file statistics (size, creation date, etc.) + * @var string + */ + protected $currentStat = null; + /** + * @var resource|null Pointer to open .phar + */ + protected $fp = null; + /** + * @var int Current Position of the pointer + */ + protected $position = 0; + + /** + * Map actual realpath of phars to meta-data about the phar + * + * Data is indexed by the alias that is used by internal files. In other + * words, if a file is included via: + * + * require_once 'phar://PEAR.phar/PEAR/Installer.php'; + * + * then the alias is "PEAR.phar" + * + * Information stored is a boolean indicating whether this .phar is compressed + * with zlib, another for bzip2, phar-specific meta-data, and + * the precise offset of internal files + * within the .phar, used with the {@link $_manifest} to load actual file contents + * @var array + */ + private static $_pharMapping = array(); + /** + * Map real file paths to alias used + * + * @var array + */ + private static $_pharFiles = array(); + /** + * File listing for the .phar + * + * The manifest is indexed per phar. + * + * Files within the .phar are indexed by their relative path within the + * .phar. Each file has this information in its internal array + * + * - 0 = uncompressed file size + * - 1 = timestamp of when file was added to phar + * - 2 = offset of file within phar relative to internal file's start + * - 3 = compressed file size (actual size in the phar) + * @var array + */ + private static $_manifest = array(); + /** + * Absolute offset of internal files within the .phar, indexed by absolute + * path to the .phar + * + * @var array + */ + private static $_fileStart = array(); + /** + * file name of the phar + * + * @var string + */ + private $_basename; + + + /** + * Default MIME types used for the web front controller + * + * @var array + */ + public static $defaultmimes = array( + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'arc' => 'application/octet-stream', + 'arj' => 'application/octet-stream', + 'art' => 'image/x-jg', + 'asf' => 'video/x-ms-asf', + 'asx' => 'video/x-ms-asf', + 'avi' => 'video/avi', + 'bin' => 'application/octet-stream', + 'bm' => 'image/bmp', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bzip2', + 'css' => 'text/css', + 'doc' => 'application/msword', + 'dot' => 'application/msword', + 'dv' => 'video/x-dv', + 'dvi' => 'application/x-dvi', + 'eps' => 'application/postscript', + 'exe' => 'application/octet-stream', + 'gif' => 'image/gif', + 'gz' => 'application/x-gzip', + 'gzip' => 'application/x-gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'jpe' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'js' => 'application/x-javascript', + 'log' => 'text/plain', + 'mid' => 'audio/x-midi', + 'mov' => 'video/quicktime', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg3', + 'mpg' => 'audio/mpeg', + 'pdf' => 'aplication/pdf', + 'png' => 'image/png', + 'rtf' => 'application/rtf', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'txt' => 'text/plain', + 'xml' => 'text/xml', + ); + + public static $defaultphp = array( + 'php' => true + ); + + public static $defaultphps = array( + 'phps' => true + ); + + public static $deny = array('/.+\.inc$/'); + + public static function viewSource($archive, $file) + { + // security, idea borrowed from PHK + if (!file_exists($archive . '.introspect')) { + header("HTTP/1.0 404 Not Found"); + return false; + } + if (self::_fileExists($archive, $_GET['viewsource'])) { + $source = highlight_file('phar://go-pear.phar/' . + $_GET['viewsource'], true); + header('Content-Type: text/html'); + header('Content-Length: ' . strlen($source)); + echo 'Source of ', + htmlspecialchars($_GET['viewsource']), ''; + echo '

Source of ', + htmlspecialchars($_GET['viewsource']), '

'; + if (isset($_GET['introspect'])) { + echo 'Return to ', htmlspecialchars($_GET['introspect']), '
'; + } + echo $source; + return false; + } else { + header("HTTP/1.0 404 Not Found"); + return false; + } + + } + + public static function introspect($archive, $dir) + { + // security, idea borrowed from PHK + if (!file_exists($archive . '.introspect')) { + header("HTTP/1.0 404 Not Found"); + return false; + } + if (!$dir) { + $dir = '/'; + } + $dir = self::processFile($dir); + if ($dir[0] != '/') { + $dir = '/' . $dir; + } + try { + $self = htmlspecialchars($_SERVER['PHP_SELF']); + $iterate = new DirectoryIterator('phar://go-pear.phar' . $dir); + echo 'Introspect ', htmlspecialchars($dir), + '

Introspect ', htmlspecialchars($dir), + '

    '; + if ($dir != '/') { + echo '
  • ..
  • '; + } + foreach ($iterate as $entry) { + if ($entry->isDot()) continue; + $name = self::processFile($entry->getPathname()); + $name = str_replace('phar://go-pear.phar/', '', $name); + if ($entry->isDir()) { + echo '
  • ', + htmlspecialchars($entry->getFilename()), '/ [directory]
  • '; + } else { + echo '
  • ', + htmlspecialchars($entry->getFilename()), '
  • '; + } + } + return false; + } catch (Exception $e) { + echo 'Directory not found: ', + htmlspecialchars($dir), '', + '

    Directory not found: ', htmlspecialchars($dir), '

    ', + '

    Try ', + 'This link

    '; + return false; + } + } + + public static function webFrontController($initfile) + { + if (isset($_SERVER) && isset($_SERVER['REQUEST_URI'])) { + $uri = parse_url($_SERVER['REQUEST_URI']); + $archive = realpath($_SERVER['SCRIPT_FILENAME']); + $subpath = str_replace('/' . basename($archive), '', $uri['path']); + if (!$subpath || $subpath == '/') { + if (isset($_GET['viewsource'])) { + return self::viewSource($archive, $_GET['viewsource']); + } + if (isset($_GET['introspect'])) { + return self::introspect($archive, $_GET['introspect']); + } + $subpath = '/' . $initfile; + } + if (!self::_fileExists($archive, substr($subpath, 1))) { + header("HTTP/1.0 404 Not Found"); + return false; + } + foreach (self::$deny as $pattern) { + if (preg_match($pattern, $subpath)) { + header("HTTP/1.0 404 Not Found"); + return false; + } + } + $inf = pathinfo(basename($subpath)); + if (!isset($inf['extension'])) { + header('Content-Type: text/plain'); + header('Content-Length: ' . + self::_filesize($archive, substr($subpath, 1))); + readfile('phar://go-pear.phar' . $subpath); + return false; + } + if (isset(self::$defaultphp[$inf['extension']])) { + include 'phar://go-pear.phar' . $subpath; + return false; + } + if (isset(self::$defaultmimes[$inf['extension']])) { + header('Content-Type: ' . self::$defaultmimes[$inf['extension']]); + header('Content-Length: ' . + self::_filesize($archive, substr($subpath, 1))); + readfile('phar://go-pear.phar' . $subpath); + return false; + } + if (isset(self::$defaultphps[$inf['extension']])) { + header('Content-Type: text/html'); + $c = highlight_file('phar://go-pear.phar' . $subpath, true); + header('Content-Length: ' . strlen($c)); + echo $c; + return false; + } + header('Content-Type: text/plain'); + header('Content-Length: ' . + self::_filesize($archive, substr($subpath, 1))); + readfile('phar://go-pear.phar' . $subpath); + } + } + + /** + * Detect end of stub + * + * @param string $buffer stub past '__HALT_'.'COMPILER();' + * @return end of stub, prior to length of manifest. + */ + private static final function _endOfStubLength($buffer) + { + $pos = 0; + if (!strlen($buffer)) { + return $pos; + } + if (($buffer[0] == ' ' || $buffer[0] == "\n") && @substr($buffer, 1, 2) == '') + { + $pos += 3; + if ($buffer[$pos] == "\r" && $buffer[$pos+1] == "\n") { + $pos += 2; + } + else if ($buffer[$pos] == "\n") { + $pos += 1; + } + } + return $pos; + } + + /** + * Allows loading an external Phar archive without include()ing it + * + * @param string $file phar package to load + * @param string $alias alias to use + * @throws Exception + */ + public static final function loadPhar($file, $alias = NULL) + { + $file = realpath($file); + if ($file) { + $fp = fopen($file, 'rb'); + $buffer = ''; + while (!feof($fp)) { + $buffer .= fread($fp, 8192); + // don't break phars + if ($pos = strpos($buffer, '__HALT_COMPI' . 'LER();')) { + $buffer .= fread($fp, 5); + fclose($fp); + $pos += 18; + $pos += self::_endOfStubLength(substr($buffer, $pos)); + return self::_mapPhar($file, $pos, $alias); + } + } + fclose($fp); + } + } + + /** + * Map a full real file path to an alias used to refer to the .phar + * + * This function can only be called from the initialization of the .phar itself. + * Any attempt to call from outside the .phar or to re-alias the .phar will fail + * as a security measure. + * @param string $alias + * @param int $dataoffset the value of 42313 + */ + public static final function mapPhar($alias = NULL, $dataoffset = NULL) + { + try { + $trace = debug_backtrace(); + $file = $trace[0]['file']; + // this ensures that this is safe + if (!in_array($file, get_included_files())) { + die('SECURITY ERROR: PHP_Archive::mapPhar can only be called from within ' . + 'the phar that initiates it'); + } + $file = realpath($file); + if (!isset($dataoffset)) { + $dataoffset = constant('__COMPILER_HALT_OFFSET'.'__'); + $fp = fopen($file, 'rb'); + fseek($fp, $dataoffset, SEEK_SET); + $dataoffset = $dataoffset + self::_endOfStubLength(fread($fp, 5)); + fclose($fp); + } + + self::_mapPhar($file, $dataoffset); + } catch (Exception $e) { + die($e->getMessage()); + } + } + + /** + * Sub-function, allows recovery from errors + * + * @param unknown_type $file + * @param unknown_type $dataoffset + */ + private static function _mapPhar($file, $dataoffset, $alias = NULL) + { + $file = realpath($file); + if (isset(self::$_manifest[$file])) { + return; + } + if (!is_array(self::$_pharMapping)) { + self::$_pharMapping = array(); + } + $fp = fopen($file, 'rb'); + // seek to __HALT_COMPILER_OFFSET__ + fseek($fp, $dataoffset); + $manifest_length = unpack('Vlen', fread($fp, 4)); + $manifest = ''; + $last = '1'; + while (strlen($last) && strlen($manifest) < $manifest_length['len']) { + $read = 8192; + if ($manifest_length['len'] - strlen($manifest) < 8192) { + $read = $manifest_length['len'] - strlen($manifest); + } + $last = fread($fp, $read); + $manifest .= $last; + } + if (strlen($manifest) < $manifest_length['len']) { + throw new Exception('ERROR: manifest length read was "' . + strlen($manifest) .'" should be "' . + $manifest_length['len'] . '"'); + } + $info = self::_unserializeManifest($manifest); + if ($info['alias']) { + $alias = $info['alias']; + $explicit = true; + } else { + if (!isset($alias)) { + $alias = $file; + } + $explicit = false; + } + self::$_manifest[$file] = $info['manifest']; + $compressed = $info['compressed']; + self::$_fileStart[$file] = ftell($fp); + fclose($fp); + if ($compressed & 0x00001000) { + if (!function_exists('gzinflate')) { + throw new Exception('Error: zlib extension is not enabled - gzinflate() function needed' . + ' for compressed .phars'); + } + } + if ($compressed & 0x00002000) { + if (!function_exists('bzdecompress')) { + throw new Exception('Error: bzip2 extension is not enabled - bzdecompress() function needed' . + ' for compressed .phars'); + } + } + if (isset(self::$_pharMapping[$alias])) { + throw new Exception('ERROR: PHP_Archive::mapPhar has already been called for alias "' . + $alias . '" cannot re-alias to "' . $file . '"'); + } + self::$_pharMapping[$alias] = array($file, $compressed, $dataoffset, $explicit, + $info['metadata']); + self::$_pharFiles[$file] = $alias; + } + + /** + * extract the manifest into an internal array + * + * @param string $manifest + * @return false|array + */ + private static function _unserializeManifest($manifest) + { + // retrieve the number of files in the manifest + $info = unpack('V', substr($manifest, 0, 4)); + $apiver = substr($manifest, 4, 2); + $apiver = bin2hex($apiver); + $apiver_dots = hexdec($apiver[0]) . '.' . hexdec($apiver[1]) . '.' . hexdec($apiver[2]); + $majorcompat = hexdec($apiver[0]); + $calcapi = explode('.', self::APIVersion()); + if ($calcapi[0] != $majorcompat) { + throw new Exception('Phar is incompatible API version ' . $apiver_dots . ', but ' . + 'PHP_Archive is API version '.self::APIVersion()); + } + if ($calcapi[0] === '0') { + if (self::APIVersion() != $apiver_dots) { + throw new Exception('Phar is API version ' . $apiver_dots . + ', but PHP_Archive is API version '.self::APIVersion(), E_USER_ERROR); + } + } + $flags = unpack('V', substr($manifest, 6, 4)); + $ret = array('compressed' => $flags & 0x00003000); + // signature is not verified by default in PHP_Archive, phar is better + $ret['hassignature'] = $flags & 0x00010000; + $aliaslen = unpack('V', substr($manifest, 10, 4)); + if ($aliaslen) { + $ret['alias'] = substr($manifest, 14, $aliaslen[1]); + } else { + $ret['alias'] = false; + } + $manifest = substr($manifest, 14 + $aliaslen[1]); + $metadatalen = unpack('V', substr($manifest, 0, 4)); + if ($metadatalen[1]) { + $ret['metadata'] = unserialize(substr($manifest, 4, $metadatalen[1])); + $manifest = substr($manifest, 4 + $metadatalen[1]); + } else { + $ret['metadata'] = null; + $manifest = substr($manifest, 4); + } + $offset = 0; + $start = 0; + for ($i = 0; $i < $info[1]; $i++) { + // length of the file name + $len = unpack('V', substr($manifest, $start, 4)); + $start += 4; + // file name + $savepath = substr($manifest, $start, $len[1]); + $start += $len[1]; + // retrieve manifest data: + // 0 = uncompressed file size + // 1 = timestamp of when file was added to phar + // 2 = compressed filesize + // 3 = crc32 + // 4 = flags + // 5 = metadata length + $ret['manifest'][$savepath] = array_values(unpack('Va/Vb/Vc/Vd/Ve/Vf', substr($manifest, $start, 24))); + $ret['manifest'][$savepath][3] = sprintf('%u', $ret['manifest'][$savepath][3] + & 0xffffffff); + if ($ret['manifest'][$savepath][5]) { + $ret['manifest'][$savepath][6] = unserialize(substr($manifest, $start + 24, + $ret['manifest'][$savepath][5])); + } else { + $ret['manifest'][$savepath][6] = null; + } + $ret['manifest'][$savepath][7] = $offset; + $offset += $ret['manifest'][$savepath][2]; + $start += 24 + $ret['manifest'][$savepath][5]; + } + return $ret; + } + + /** + * @param string + */ + private static function processFile($path) + { + if ($path == '.') { + return ''; + } + $std = str_replace("\\", "/", $path); + while ($std != ($std = ereg_replace("[^\/:?]+/\.\./", "", $std))) ; + $std = str_replace("/./", "", $std); + if (strlen($std) > 1 && $std[0] == '/') { + $std = substr($std, 1); + } + if (strncmp($std, "./", 2) == 0) { + return substr($std, 2); + } else { + return $std; + } + } + + /** + * Seek in the master archive to a matching file or directory + * @param string + */ + protected function selectFile($path, $allowdirs = true) + { + $std = self::processFile($path); + if (isset(self::$_manifest[$this->_archiveName][$path])) { + $this->_setCurrentFile($path); + return true; + } + if (!$allowdirs) { + return 'Error: "' . $path . '" is not a file in phar "' . $this->_basename . '"'; + } + foreach (self::$_manifest[$this->_archiveName] as $file => $info) { + if (empty($std) || + //$std is a directory + strncmp($std.'/', $path, strlen($std)+1) == 0) { + $this->currentFilename = $this->internalFileLength = $this->currentStat = null; + return true; + } + } + return 'Error: "' . $path . '" not found in phar "' . $this->_basename . '"'; + } + + private function _setCurrentFile($path) + { + $this->currentStat = array( + 2 => 0100444, // file mode, readable by all, writeable by none + 4 => 0, // uid + 5 => 0, // gid + 7 => self::$_manifest[$this->_archiveName][$path][0], // size + 9 => self::$_manifest[$this->_archiveName][$path][1], // creation time + ); + $this->currentFilename = $path; + $this->internalFileLength = self::$_manifest[$this->_archiveName][$path][2]; + // seek to offset of file header within the .phar + if (is_resource(@$this->fp)) { + fseek($this->fp, self::$_fileStart[$this->_archiveName] + self::$_manifest[$this->_archiveName][$path][7]); + } + } + + private static function _fileExists($archive, $path) + { + return isset(self::$_manifest[$archive]) && + isset(self::$_manifest[$archive][$path]); + } + + private static function _filesize($archive, $path) + { + return self::$_manifest[$archive][$path][0]; + } + + /** + * Seek to a file within the master archive, and extract its contents + * @param string + * @return array|string an array containing an error message string is returned + * upon error, otherwise the file contents are returned + */ + public function extractFile($path) + { + $this->fp = @fopen($this->_archiveName, "rb"); + if (!$this->fp) { + return array('Error: cannot open phar "' . $this->_archiveName . '"'); + } + if (($e = $this->selectFile($path, false)) === true) { + $data = ''; + $count = $this->internalFileLength; + while ($count) { + if ($count < 8192) { + $data .= @fread($this->fp, $count); + $count = 0; + } else { + $count -= 8192; + $data .= @fread($this->fp, 8192); + } + } + @fclose($this->fp); + if (self::$_manifest[$this->_archiveName][$path][4] & self::GZ) { + $data = gzinflate($data); + } elseif (self::$_manifest[$this->_archiveName][$path][4] & self::BZ2) { + $data = bzdecompress($data); + } + if (!isset(self::$_manifest[$this->_archiveName][$path]['ok'])) { + if (strlen($data) != $this->currentStat[7]) { + return array("Not valid internal .phar file (size error {$size} != " . + $this->currentStat[7] . ")"); + } + if (self::$_manifest[$this->_archiveName][$path][3] != sprintf("%u", crc32($data) & 0xffffffff)) { + return array("Not valid internal .phar file (checksum error)"); + } + self::$_manifest[$this->_archiveName][$path]['ok'] = true; + } + return $data; + } else { + @fclose($this->fp); + return array($e); + } + } + + /** + * Parse urls like phar:///fullpath/to/my.phar/file.txt + * + * @param string $file + * @return false|array + */ + static protected function parseUrl($file) + { + if (substr($file, 0, 7) != 'phar://') { + return false; + } + $file = substr($file, 7); + + $ret = array('scheme' => 'phar'); + $pos_p = strpos($file, '.phar.php'); + $pos_z = strpos($file, '.phar.gz'); + $pos_b = strpos($file, '.phar.bz2'); + if ($pos_p) { + if ($pos_z) { + return false; + } + $ret['host'] = substr($file, 0, $pos_p + strlen('.phar.php')); + $ret['path'] = substr($file, strlen($ret['host'])); + } elseif ($pos_z) { + $ret['host'] = substr($file, 0, $pos_z + strlen('.phar.gz')); + $ret['path'] = substr($file, strlen($ret['host'])); + } elseif ($pos_b) { + $ret['host'] = substr($file, 0, $pos_z + strlen('.phar.bz2')); + $ret['path'] = substr($file, strlen($ret['host'])); + } elseif (($pos_p = strpos($file, ".phar")) !== false) { + $ret['host'] = substr($file, 0, $pos_p + strlen('.phar')); + $ret['path'] = substr($file, strlen($ret['host'])); + } else { + return false; + } + if (!$ret['path']) { + $ret['path'] = '/'; + } + return $ret; + } + + /** + * Locate the .phar archive in the include_path and detect the file to open within + * the archive. + * + * Possible parameters are phar://pharname.phar/filename_within_phar.ext + * @param string a file within the archive + * @return string the filename within the .phar to retrieve + */ + public function initializeStream($file) + { + $file = self::processFile($file); + $info = @parse_url($file); + if (!$info) { + $info = self::parseUrl($file); + } + if (!$info) { + return false; + } + if (!isset($info['host'])) { + // malformed internal file + return false; + } + if (!isset(self::$_pharFiles[$info['host']]) && + !isset(self::$_pharMapping[$info['host']])) { + try { + self::loadPhar($info['host']); + // use alias from here out + $info['host'] = self::$_pharFiles[$info['host']]; + } catch (Exception $e) { + return false; + } + } + if (!isset($info['path'])) { + return false; + } elseif (strlen($info['path']) > 1) { + $info['path'] = substr($info['path'], 1); + } + if (isset(self::$_pharMapping[$info['host']])) { + $this->_basename = $info['host']; + $this->_archiveName = self::$_pharMapping[$info['host']][0]; + $this->_compressed = self::$_pharMapping[$info['host']][1]; + } elseif (isset(self::$_pharFiles[$info['host']])) { + $this->_archiveName = $info['host']; + $this->_basename = self::$_pharFiles[$info['host']]; + $this->_compressed = self::$_pharMapping[$this->_basename][1]; + } + $file = $info['path']; + return $file; + } + + /** + * Open the requested file - PHP streams API + * + * @param string $file String provided by the Stream wrapper + * @access private + */ + public function stream_open($file) + { + return $this->_streamOpen($file); + } + + /** + * @param string filename to opne, or directory name + * @param bool if true, a directory will be matched, otherwise only files + * will be matched + * @uses trigger_error() + * @return bool success of opening + * @access private + */ + private function _streamOpen($file, $searchForDir = false) + { + $path = $this->initializeStream($file); + if (!$path) { + trigger_error('Error: Unknown phar in "' . $file . '"', E_USER_ERROR); + } + if (is_array($this->file = $this->extractFile($path))) { + trigger_error($this->file[0], E_USER_ERROR); + return false; + } + if ($path != $this->currentFilename) { + if (!$searchForDir) { + trigger_error("Cannot open '$file', is a directory", E_USER_ERROR); + return false; + } else { + $this->file = ''; + return true; + } + } + + if (!is_null($this->file) && $this->file !== false) { + return true; + } else { + return false; + } + } + + /** + * Read the data - PHP streams API + * + * @param int + * @access private + */ + public function stream_read($count) + { + $ret = substr($this->file, $this->position, $count); + $this->position += strlen($ret); + return $ret; + } + + /** + * Whether we've hit the end of the file - PHP streams API + * @access private + */ + function stream_eof() + { + return $this->position >= $this->currentStat[7]; + } + + /** + * For seeking the stream - PHP streams API + * @param int + * @param SEEK_SET|SEEK_CUR|SEEK_END + * @access private + */ + public function stream_seek($pos, $whence) + { + switch ($whence) { + case SEEK_SET: + if ($pos < 0) { + return false; + } + $this->position = $pos; + break; + case SEEK_CUR: + if ($pos + $this->currentStat[7] < 0) { + return false; + } + $this->position += $pos; + break; + case SEEK_END: + if ($pos + $this->currentStat[7] < 0) { + return false; + } + $this->position = $pos + $this->currentStat[7]; + break; + default: + return false; + } + return true; + } + + /** + * The current position in the stream - PHP streams API + * @access private + */ + public function stream_tell() + { + return $this->position; + } + + /** + * The result of an fstat call, returns mod time from creation, and file size - + * PHP streams API + * @uses _stream_stat() + * @access private + */ + public function stream_stat() + { + return $this->_stream_stat(); + } + + /** + * Retrieve statistics on a file or directory within the .phar + * @param string file/directory to stat + * @access private + */ + public function _stream_stat($file = null) + { + $std = $file ? self::processFile($file) : $this->currentFilename; + if ($file) { + if (isset(self::$_manifest[$this->_archiveName][$file])) { + $this->_setCurrentFile($file); + $isdir = false; + } else { + do { + $isdir = false; + if ($file == '/') { + break; + } + foreach (self::$_manifest[$this->_archiveName] as $path => $info) { + if (strpos($path, $file) === 0) { + if (strlen($path) > strlen($file) && + $path[strlen($file)] == '/') { + break 2; + } + } + } + // no files exist and no directories match this string + return false; + } while (false); + $isdir = true; + } + } else { + $isdir = false; // open streams must be files + } + $mode = $isdir ? 0040444 : 0100444; + // 040000 = dir, 010000 = file + // everything is readable, nothing is writeable + return array( + 0, 0, $mode, 0, 0, 0, 0, 0, 0, 0, 0, 0, // non-associative indices + 'dev' => 0, 'ino' => 0, + 'mode' => $mode, + 'nlink' => 0, 'uid' => 0, 'gid' => 0, 'rdev' => 0, 'blksize' => 0, 'blocks' => 0, + 'size' => $this->currentStat[7], + 'atime' => $this->currentStat[9], + 'mtime' => $this->currentStat[9], + 'ctime' => $this->currentStat[9], + ); + } + + /** + * Stat a closed file or directory - PHP streams API + * @param string + * @param int + * @access private + */ + public function url_stat($url, $flags) + { + $path = $this->initializeStream($url); + return $this->_stream_stat($path); + } + + /** + * Open a directory in the .phar for reading - PHP streams API + * @param string directory name + * @access private + */ + public function dir_opendir($path) + { + $info = @parse_url($path); + if (!$info) { + $info = self::parseUrl($path); + if (!$info) { + trigger_error('Error: "' . $path . '" is a file, and cannot be opened with opendir', + E_USER_ERROR); + return false; + } + } + $path = !empty($info['path']) ? + $info['host'] . $info['path'] : $info['host'] . '/'; + $path = $this->initializeStream('phar://' . $path); + if (isset(self::$_manifest[$this->_archiveName][$path])) { + trigger_error('Error: "' . $path . '" is a file, and cannot be opened with opendir', + E_USER_ERROR); + return false; + } + if ($path == false) { + trigger_error('Error: Unknown phar in "' . $file . '"', E_USER_ERROR); + return false; + } + $this->fp = @fopen($this->_archiveName, "rb"); + if (!$this->fp) { + trigger_error('Error: cannot open phar "' . $this->_archiveName . '"'); + return false; + } + $this->_dirFiles = array(); + foreach (self::$_manifest[$this->_archiveName] as $file => $info) { + if ($path == '/') { + if (strpos($file, '/')) { + $a = explode('/', $file); + $this->_dirFiles[array_shift($a)] = true; + } else { + $this->_dirFiles[$file] = true; + } + } elseif (strpos($file, $path) === 0) { + $fname = substr($file, strlen($path) + 1); + if (strpos($fname, '/')) { + // this is a directory + $a = explode('/', $fname); + $this->_dirFiles[array_shift($a)] = true; + } elseif ($file[strlen($path)] == '/') { + // this is a file + $this->_dirFiles[$fname] = true; + } + } + } + @fclose($this->fp); + if (!count($this->_dirFiles)) { + return false; + } + @uksort($this->_dirFiles, 'strnatcmp'); + return true; + } + + /** + * Read the next directory entry - PHP streams API + * @access private + */ + public function dir_readdir() + { + $ret = key($this->_dirFiles); + @next($this->_dirFiles); + if (!$ret) { + return false; + } + return $ret; + } + + /** + * Close a directory handle opened with opendir() - PHP streams API + * @access private + */ + public function dir_closedir() + { + $this->_dirFiles = array(); + return true; + } + + /** + * Rewind to the first directory entry - PHP streams API + * @access private + */ + public function dir_rewinddir() + { + @reset($this->_dirFiles); + return true; + } + + /** + * API version of this class + * @return string + */ + public static final function APIVersion() + { + return '1.0.0'; + } + + /** + * Retrieve Phar-specific metadata for a Phar archive + * + * @param string $phar full path to Phar archive, or alias + * @return null|mixed The value that was serialized for the Phar + * archive's metadata + * @throws Exception + */ + public static function getPharMetadata($phar) + { + if (isset(self::$_pharFiles[$phar])) { + $phar = self::$_pharFiles[$phar]; + } + if (!isset(self::$_pharMapping[$phar])) { + throw new Exception('Unknown Phar archive: "' . $phar . '"'); + } + return self::$_pharMapping[$phar][4]; + } + + /** + * Retrieve File-specific metadata for a Phar archive file + * + * @param string $phar full path to Phar archive, or alias + * @param string $file relative path to file within Phar archive + * @return null|mixed The value that was serialized for the Phar + * archive's metadata + * @throws Exception + */ + public static function getFileMetadata($phar, $file) + { + if (!isset(self::$_pharFiles[$phar])) { + if (!isset(self::$_pharMapping[$phar])) { + throw new Exception('Unknown Phar archive: "' . $phar . '"'); + } + $phar = self::$_pharMapping[$phar][0]; + } + if (!isset(self::$_manifest[$phar])) { + throw new Exception('Unknown Phar: "' . $phar . '"'); + } + $file = self::processFile($file); + if (!isset(self::$_manifest[$phar][$file])) { + throw new Exception('Unknown file "' . $file . '" within Phar "'. $phar . '"'); + } + return self::$_manifest[$phar][$file][6]; + } + + /** + * @return list of supported signature algorithmns. + */ + public static function getsupportedsignatures() + { + $ret = array('MD5', 'SHA-1'); + if (extension_loaded('hash')) { + $ret[] = 'SHA-256'; + $ret[] = 'SHA-512'; + } + return $ret; + } +}} +if (!class_exists('Phar')) { + PHP_Archive::mapPhar(null, 42313 ); +} else { + try { + Phar::mapPhar(); + } catch (Exception $e) { + echo $e->getMessage(); + } +} +if (class_exists('PHP_Archive') && !in_array('phar', stream_get_wrappers())) { + stream_wrapper_register('phar', 'PHP_Archive'); +} + +@ini_set('memory_limit', -1); +if (extension_loaded('phar')) {if (isset($_SERVER) && isset($_SERVER['REQUEST_URI'])) { + $uri = parse_url($_SERVER['REQUEST_URI']); + $archive = realpath($_SERVER['SCRIPT_FILENAME']); + $subpath = str_replace('/' . basename($archive), '', $uri['path']); + $mimetypes = array ( + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'arc' => 'application/octet-stream', + 'arj' => 'application/octet-stream', + 'art' => 'image/x-jg', + 'asf' => 'video/x-ms-asf', + 'asx' => 'video/x-ms-asf', + 'avi' => 'video/avi', + 'bin' => 'application/octet-stream', + 'bm' => 'image/bmp', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bzip2', + 'css' => 'text/css', + 'doc' => 'application/msword', + 'dot' => 'application/msword', + 'dv' => 'video/x-dv', + 'dvi' => 'application/x-dvi', + 'eps' => 'application/postscript', + 'exe' => 'application/octet-stream', + 'gif' => 'image/gif', + 'gz' => 'application/x-gzip', + 'gzip' => 'application/x-gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'jpe' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'js' => 'application/x-javascript', + 'log' => 'text/plain', + 'mid' => 'audio/x-midi', + 'mov' => 'video/quicktime', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg3', + 'mpg' => 'audio/mpeg', + 'pdf' => 'aplication/pdf', + 'png' => 'image/png', + 'rtf' => 'application/rtf', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'txt' => 'text/plain', + 'xml' => 'text/xml', +); + $phpfiles = array ( + 'php' => true, +); + $phpsfiles = array ( + 'phps' => true, +); + $deny = array ( + 0 => '/.+\\.inc$/', +); + $subpath = str_replace('/' . basename($archive), '', $uri['path']); + if (!$subpath || $subpath == '/') { + $subpath = '/PEAR.php'; + } + if ($subpath[0] != '/') { + $subpath = '/' . $subpath; + } + if (!@file_exists('phar://' . $archive . $subpath)) { + header("HTTP/1.0 404 Not Found"); + exit; + } + + foreach ($deny as $pattern) { + if (preg_match($pattern, $subpath)) { + header("HTTP/1.0 404 Not Found"); + exit; + } + } + $inf = pathinfo(basename($subpath)); + if (!isset($inf['extension'])) { + header('Content-Type: text/plain'); + header('Content-Length: ' . filesize('phar://' . $archive . $subpath)); + readfile('phar://' . $archive . $subpath); + exit; + } + if (isset($phpfiles[$inf['extension']])) { + include 'phar://' . $archive . '/' . $subpath; + exit; + } + if (isset($mimetypes[$inf['extension']])) { + header('Content-Type: ' . $mimetypes[$inf['extension']]); + header('Content-Length: ' . filesize('phar://' . $archive . $subpath)); + readfile('phar://' . $archive . $subpath); + exit; + } + if (isset($phpsfiles[$inf['extension']])) { + header('Content-Type: text/html'); + $c = highlight_file('phar://' . $archive . $subpath, true); + header('Content-Length: ' . strlen($c)); + echo $c; + exit; + } + header('Content-Type: text/plain'); + header('Content-Length: ' . filesize('phar://' . $archive . '/' . $subpath)); + readfile('phar://' . $archive . '/' . $subpath); + exit; +}} else {if (!empty($_SERVER['REQUEST_URI'])) {PHP_Archive::webFrontController('PEAR.php');exit;}} + + + +require_once 'phar://go-pear.phar/index.php'; +__HALT_COMPILER();aF go-pear.pharArchive/Tar.phpýõ/£JýõXÇgÛmConsole/Getopt.phpS2/£JS2 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * @category File_Formats + * @package Archive_Tar + * @author Vincent Blavet + * @copyright 1997-2008 The Authors + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Tar.php 287963 2009-09-02 08:18:55Z mrook $ + * @link http://pear.php.net/package/Archive_Tar + */ + +require_once 'phar://go-pear.phar/' . 'PEAR.php'; + + +define ('ARCHIVE_TAR_ATT_SEPARATOR', 90001); +define ('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); + +/** +* Creates a (compressed) Tar archive +* +* @author Vincent Blavet +* @version $Revision: 287963 $ +* @license http://www.opensource.org/licenses/bsd-license.php New BSD License +* @package Archive_Tar +*/ +class Archive_Tar extends PEAR +{ + /** + * @var string Name of the Tar + */ + var $_tarname=''; + + /** + * @var boolean if true, the Tar file will be gzipped + */ + var $_compress=false; + + /** + * @var string Type of compression : 'none', 'gz' or 'bz2' + */ + var $_compress_type='none'; + + /** + * @var string Explode separator + */ + var $_separator=' '; + + /** + * @var file descriptor + */ + var $_file=0; + + /** + * @var string Local Tar name of a remote Tar (http:// or ftp://) + */ + var $_temp_tarname=''; + + // {{{ constructor + /** + * Archive_Tar Class constructor. This flavour of the constructor only + * declare a new Archive_Tar object, identifying it by the name of the + * tar file. + * If the compress argument is set the tar will be read or created as a + * gzip or bz2 compressed TAR file. + * + * @param string $p_tarname The name of the tar archive to create + * @param string $p_compress can be null, 'gz' or 'bz2'. This + * parameter indicates if gzip or bz2 compression + * is required. For compatibility reason the + * boolean value 'true' means 'gz'. + * @access public + */ + function Archive_Tar($p_tarname, $p_compress = null) + { + $this->PEAR(); + $this->_compress = false; + $this->_compress_type = 'none'; + if (($p_compress === null) || ($p_compress == '')) { + if (@file_exists($p_tarname)) { + if ($fp = @fopen($p_tarname, "rb")) { + // look for gzip magic cookie + $data = fread($fp, 2); + fclose($fp); + if ($data == "\37\213") { + $this->_compress = true; + $this->_compress_type = 'gz'; + // No sure it's enought for a magic code .... + } elseif ($data == "BZ") { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } + } + } else { + // probably a remote file or some file accessible + // through a stream interface + if (substr($p_tarname, -2) == 'gz') { + $this->_compress = true; + $this->_compress_type = 'gz'; + } elseif ((substr($p_tarname, -3) == 'bz2') || + (substr($p_tarname, -2) == 'bz')) { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } + } + } else { + if (($p_compress === true) || ($p_compress == 'gz')) { + $this->_compress = true; + $this->_compress_type = 'gz'; + } else if ($p_compress == 'bz2') { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } else { + $this->_error("Unsupported compression type '$p_compress'\n". + "Supported types are 'gz' and 'bz2'.\n"); + return false; + } + } + $this->_tarname = $p_tarname; + if ($this->_compress) { // assert zlib or bz2 extension support + if ($this->_compress_type == 'gz') + $extname = 'zlib'; + else if ($this->_compress_type == 'bz2') + $extname = 'bz2'; + + if (!extension_loaded($extname)) { + PEAR::loadExtension($extname); + } + if (!extension_loaded($extname)) { + $this->_error("The extension '$extname' couldn't be found.\n". + "Please make sure your version of PHP was built ". + "with '$extname' support.\n"); + return false; + } + } + } + // }}} + + // {{{ destructor + function _Archive_Tar() + { + $this->_close(); + // ----- Look for a local copy to delete + if ($this->_temp_tarname != '') + @unlink($this->_temp_tarname); + $this->_PEAR(); + } + // }}} + + // {{{ create() + /** + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If a file with the same name exist and is writable, it is replaced + * by the new tar. + * The method return false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * For each directory added in the archive, the files and + * sub-directories are also added. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * @return true on success, false on error. + * @see createModify() + * @access public + */ + function create($p_filelist) + { + return $this->createModify($p_filelist, '', ''); + } + // }}} + + // {{{ add() + /** + * This method add the files / directories that are listed in $p_filelist in + * the archive. If the archive does not exist it is created. + * The method return false and a PEAR error text. + * The files and directories listed are only added at the end of the archive, + * even if a file with the same name is already archived. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * @return true on success, false on error. + * @see createModify() + * @access public + */ + function add($p_filelist) + { + return $this->addModify($p_filelist, '', ''); + } + // }}} + + // {{{ extract() + function extract($p_path='') + { + return $this->extractModify($p_path, ''); + } + // }}} + + // {{{ listContent() + function listContent() + { + $v_list_detail = array(); + + if ($this->_openRead()) { + if (!$this->_extractList('', $v_list_detail, "list", '', '')) { + unset($v_list_detail); + $v_list_detail = 0; + } + $this->_close(); + } + + return $v_list_detail; + } + // }}} + + // {{{ createModify() + /** + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If the file already exists and is writable, it is replaced by the + * new tar. It is a create and not an add. If the file exists and is + * read-only or is a directory it is not replaced. The method return + * false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * See also addModify() method for file adding properties. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated by + * a single blank space. + * @param string $p_add_dir A string which contains a path to be added + * to the memorized path of each element in + * the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of each + * element in the list, when relevant. + * @return boolean true on success, false on error. + * @access public + * @see addModify() + */ + function createModify($p_filelist, $p_add_dir, $p_remove_dir='') + { + $v_result = true; + + if (!$this->_openWrite()) + return false; + + if ($p_filelist != '') { + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_cleanFile(); + $this->_error('Invalid file list'); + return false; + } + + $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir); + } + + if ($v_result) { + $this->_writeFooter(); + $this->_close(); + } else + $this->_cleanFile(); + + return $v_result; + } + // }}} + + // {{{ addModify() + /** + * This method add the files / directories listed in $p_filelist at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * If a file/dir is already in the archive it will only be added at the + * end of the archive. There is no update of the existing archived + * file/dir. However while extracting the archive, the last file will + * replace the first one. This results in a none optimization of the + * archive size. + * If a file/dir does not exist the file/dir is ignored. However an + * error text is send to PEAR error. + * If a file/dir is not readable the file/dir is ignored. However an + * error text is send to PEAR error. + * + * @param array $p_filelist An array of filenames and directory + * names, or a single string with names + * separated by a single blank space. + * @param string $p_add_dir A string which contains a path to be + * added to the memorized path of each + * element in the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of + * each element in the list, when + * relevant. + * @return true on success, false on error. + * @access public + */ + function addModify($p_filelist, $p_add_dir, $p_remove_dir='') + { + $v_result = true; + + if (!$this->_isArchive()) + $v_result = $this->createModify($p_filelist, $p_add_dir, + $p_remove_dir); + else { + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_error('Invalid file list'); + return false; + } + + $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir); + } + + return $v_result; + } + // }}} + + // {{{ addString() + /** + * This method add a single string as a file at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * + * @param string $p_filename A string which contains the full + * filename path that will be associated + * with the string. + * @param string $p_string The content of the file added in + * the archive. + * @return true on success, false on error. + * @access public + */ + function addString($p_filename, $p_string) + { + $v_result = true; + + if (!$this->_isArchive()) { + if (!$this->_openWrite()) { + return false; + } + $this->_close(); + } + + if (!$this->_openAppend()) + return false; + + // Need to check the get back to the temporary file ? .... + $v_result = $this->_addString($p_filename, $p_string); + + $this->_writeFooter(); + + $this->_close(); + + return $v_result; + } + // }}} + + // {{{ extractModify() + /** + * This method extract all the content of the archive in the directory + * indicated by $p_path. When relevant the memorized path of the + * files/dir can be modified by removing the $p_remove_path path at the + * beginning of the file/dir path. + * While extracting a file, if the directory path does not exists it is + * created. + * While extracting a file, if the file already exists it is replaced + * without looking for last modification date. + * While extracting a file, if the file already exists and is write + * protected, the extraction is aborted. + * While extracting a file, if a directory with the same name already + * exists, the extraction is aborted. + * While extracting a directory, if a file with the same name already + * exists, the extraction is aborted. + * While extracting a file/directory if the destination directory exist + * and is write protected, or does not exist but can not be created, + * the extraction is aborted. + * If after extraction an extracted file does not show the correct + * stored file size, the extraction is aborted. + * When the extraction is aborted, a PEAR error text is set and false + * is returned. However the result can be a partial extraction that may + * need to be manually cleaned. + * + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @return boolean true on success, false on error. + * @access public + * @see extractList() + */ + function extractModify($p_path, $p_remove_path) + { + $v_result = true; + $v_list_detail = array(); + + if ($v_result = $this->_openRead()) { + $v_result = $this->_extractList($p_path, $v_list_detail, + "complete", 0, $p_remove_path); + $this->_close(); + } + + return $v_result; + } + // }}} + + // {{{ extractInString() + /** + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or NULL on error. + * @param string $p_filename The path of the file to extract in a string. + * @return a string with the file content or NULL. + * @access public + */ + function extractInString($p_filename) + { + if ($this->_openRead()) { + $v_result = $this->_extractInString($p_filename); + $this->_close(); + } else { + $v_result = NULL; + } + + return $v_result; + } + // }}} + + // {{{ extractList() + /** + * This method extract from the archive only the files indicated in the + * $p_filelist. These files are extracted in the current directory or + * in the directory indicated by the optional $p_path parameter. + * If indicated the $p_remove_path can be used in the same way as it is + * used in extractModify() method. + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated + * by a single blank space. + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @return true on success, false on error. + * @access public + * @see extractModify() + */ + function extractList($p_filelist, $p_path='', $p_remove_path='') + { + $v_result = true; + $v_list_detail = array(); + + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_error('Invalid string list'); + return false; + } + + if ($v_result = $this->_openRead()) { + $v_result = $this->_extractList($p_path, $v_list_detail, "partial", + $v_list, $p_remove_path); + $this->_close(); + } + + return $v_result; + } + // }}} + + // {{{ setAttribute() + /** + * This method set specific attributes of the archive. It uses a variable + * list of parameters, in the format attribute code + attribute values : + * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); + * @param mixed $argv variable list of attributes and values + * @return true on success, false on error. + * @access public + */ + function setAttribute() + { + $v_result = true; + + // ----- Get the number of variable list of arguments + if (($v_size = func_num_args()) == 0) { + return true; + } + + // ----- Get the arguments + $v_att_list = &func_get_args(); + + // ----- Read the attributes + $i=0; + while ($i<$v_size) { + + // ----- Look for next option + switch ($v_att_list[$i]) { + // ----- Look for options that request a string value + case ARCHIVE_TAR_ATT_SEPARATOR : + // ----- Check the number of parameters + if (($i+1) >= $v_size) { + $this->_error('Invalid number of parameters for ' + .'attribute ARCHIVE_TAR_ATT_SEPARATOR'); + return false; + } + + // ----- Get the value + $this->_separator = $v_att_list[$i+1]; + $i++; + break; + + default : + $this->_error('Unknow attribute code '.$v_att_list[$i].''); + return false; + } + + // ----- Next attribute + $i++; + } + + return $v_result; + } + // }}} + + // {{{ _error() + function _error($p_message) + { + // ----- To be completed + $this->raiseError($p_message); + } + // }}} + + // {{{ _warning() + function _warning($p_message) + { + // ----- To be completed + $this->raiseError($p_message); + } + // }}} + + // {{{ _isArchive() + function _isArchive($p_filename=NULL) + { + if ($p_filename == NULL) { + $p_filename = $this->_tarname; + } + clearstatcache(); + return @is_file($p_filename) && !@is_link($p_filename); + } + // }}} + + // {{{ _openWrite() + function _openWrite() + { + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($this->_tarname, "wb9"); + else if ($this->_compress_type == 'bz2') + $this->_file = @bzopen($this->_tarname, "w"); + else if ($this->_compress_type == 'none') + $this->_file = @fopen($this->_tarname, "wb"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in write mode \'' + .$this->_tarname.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _openRead() + function _openRead() + { + if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') { + + // ----- Look if a local copy need to be done + if ($this->_temp_tarname == '') { + $this->_temp_tarname = uniqid('tar').'.tmp'; + if (!$v_file_from = @fopen($this->_tarname, 'rb')) { + $this->_error('Unable to open in read mode \'' + .$this->_tarname.'\''); + $this->_temp_tarname = ''; + return false; + } + if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { + $this->_error('Unable to open in write mode \'' + .$this->_temp_tarname.'\''); + $this->_temp_tarname = ''; + return false; + } + while ($v_data = @fread($v_file_from, 1024)) + @fwrite($v_file_to, $v_data); + @fclose($v_file_from); + @fclose($v_file_to); + } + + // ----- File to open if the local copy + $v_filename = $this->_temp_tarname; + + } else + // ----- File to open if the normal Tar file + $v_filename = $this->_tarname; + + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($v_filename, "rb"); + else if ($this->_compress_type == 'bz2') + $this->_file = @bzopen($v_filename, "r"); + else if ($this->_compress_type == 'none') + $this->_file = @fopen($v_filename, "rb"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in read mode \''.$v_filename.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _openReadWrite() + function _openReadWrite() + { + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($this->_tarname, "r+b"); + else if ($this->_compress_type == 'bz2') { + $this->_error('Unable to open bz2 in read/write mode \'' + .$this->_tarname.'\' (limitation of bz2 extension)'); + return false; + } else if ($this->_compress_type == 'none') + $this->_file = @fopen($this->_tarname, "r+b"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in read/write mode \'' + .$this->_tarname.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _close() + function _close() + { + //if (isset($this->_file)) { + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') + @gzclose($this->_file); + else if ($this->_compress_type == 'bz2') + @bzclose($this->_file); + else if ($this->_compress_type == 'none') + @fclose($this->_file); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + $this->_file = 0; + } + + // ----- Look if a local copy need to be erase + // Note that it might be interesting to keep the url for a time : ToDo + if ($this->_temp_tarname != '') { + @unlink($this->_temp_tarname); + $this->_temp_tarname = ''; + } + + return true; + } + // }}} + + // {{{ _cleanFile() + function _cleanFile() + { + $this->_close(); + + // ----- Look for a local copy + if ($this->_temp_tarname != '') { + // ----- Remove the local copy but not the remote tarname + @unlink($this->_temp_tarname); + $this->_temp_tarname = ''; + } else { + // ----- Remove the local tarname file + @unlink($this->_tarname); + } + $this->_tarname = ''; + + return true; + } + // }}} + + // {{{ _writeBlock() + function _writeBlock($p_binary_data, $p_len=null) + { + if (is_resource($this->_file)) { + if ($p_len === null) { + if ($this->_compress_type == 'gz') + @gzputs($this->_file, $p_binary_data); + else if ($this->_compress_type == 'bz2') + @bzwrite($this->_file, $p_binary_data); + else if ($this->_compress_type == 'none') + @fputs($this->_file, $p_binary_data); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'gz') + @gzputs($this->_file, $p_binary_data, $p_len); + else if ($this->_compress_type == 'bz2') + @bzwrite($this->_file, $p_binary_data, $p_len); + else if ($this->_compress_type == 'none') + @fputs($this->_file, $p_binary_data, $p_len); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + } + } + return true; + } + // }}} + + // {{{ _readBlock() + function _readBlock() + { + $v_block = null; + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') + $v_block = @gzread($this->_file, 512); + else if ($this->_compress_type == 'bz2') + $v_block = @bzread($this->_file, 512); + else if ($this->_compress_type == 'none') + $v_block = @fread($this->_file, 512); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + } + return $v_block; + } + // }}} + + // {{{ _jumpBlock() + function _jumpBlock($p_len=null) + { + if (is_resource($this->_file)) { + if ($p_len === null) + $p_len = 1; + + if ($this->_compress_type == 'gz') { + @gzseek($this->_file, gztell($this->_file)+($p_len*512)); + } + else if ($this->_compress_type == 'bz2') { + // ----- Replace missing bztell() and bzseek() + for ($i=0; $i<$p_len; $i++) + $this->_readBlock(); + } else if ($this->_compress_type == 'none') + @fseek($this->_file, ftell($this->_file)+($p_len*512)); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + } + return true; + } + // }}} + + // {{{ _writeFooter() + function _writeFooter() + { + if (is_resource($this->_file)) { + // ----- Write the last 0 filled block for end of archive + $v_binary_data = pack('a1024', ''); + $this->_writeBlock($v_binary_data); + } + return true; + } + // }}} + + // {{{ _addList() + function _addList($p_list, $p_add_dir, $p_remove_dir) + { + $v_result=true; + $v_header = array(); + + // ----- Remove potential windows directory separator + $p_add_dir = $this->_translateWinPath($p_add_dir); + $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); + + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if (sizeof($p_list) == 0) + return true; + + foreach ($p_list as $v_filename) { + if (!$v_result) { + break; + } + + // ----- Skip the current tar name + if ($v_filename == $this->_tarname) + continue; + + if ($v_filename == '') + continue; + + if (!file_exists($v_filename)) { + $this->_warning("File '$v_filename' does not exist"); + continue; + } + + // ----- Add the file or directory header + if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) + return false; + + if (@is_dir($v_filename) && !@is_link($v_filename)) { + if (!($p_hdir = opendir($v_filename))) { + $this->_warning("Directory '$v_filename' can not be read"); + continue; + } + while (false !== ($p_hitem = readdir($p_hdir))) { + if (($p_hitem != '.') && ($p_hitem != '..')) { + if ($v_filename != ".") + $p_temp_list[0] = $v_filename.'/'.$p_hitem; + else + $p_temp_list[0] = $p_hitem; + + $v_result = $this->_addList($p_temp_list, + $p_add_dir, + $p_remove_dir); + } + } + + unset($p_temp_list); + unset($p_hdir); + unset($p_hitem); + } + } + + return $v_result; + } + // }}} + + // {{{ _addFile() + function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false);; + $v_stored_filename = $p_filename; + if (strcmp($p_filename, $p_remove_dir) == 0) { + return true; + } + if ($p_remove_dir != '') { + if (substr($p_remove_dir, -1) != '/') + $p_remove_dir .= '/'; + + if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) + $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); + } + $v_stored_filename = $this->_translateWinPath($v_stored_filename); + if ($p_add_dir != '') { + if (substr($p_add_dir, -1) == '/') + $v_stored_filename = $p_add_dir.$v_stored_filename; + else + $v_stored_filename = $p_add_dir.'/'.$v_stored_filename; + } + + $v_stored_filename = $this->_pathReduction($v_stored_filename); + + if ($this->_isArchive($p_filename)) { + if (($v_file = @fopen($p_filename, "rb")) == 0) { + $this->_warning("Unable to open file '".$p_filename + ."' in binary read mode"); + return true; + } + + if (!$this->_writeHeader($p_filename, $v_stored_filename)) + return false; + + while (($v_buffer = fread($v_file, 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + fclose($v_file); + + } else { + // ----- Only header for dir + if (!$this->_writeHeader($p_filename, $v_stored_filename)) + return false; + } + + return true; + } + // }}} + + // {{{ _addString() + function _addString($p_filename, $p_string) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false);; + + if (!$this->_writeHeaderBlock($p_filename, strlen($p_string), + time(), 384, "", 0, 0)) + return false; + + $i=0; + while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') { + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + return true; + } + // }}} + + // {{{ _writeHeader() + function _writeHeader($p_filename, $p_stored_filename) + { + if ($p_stored_filename == '') + $p_stored_filename = $p_filename; + $v_reduce_filename = $this->_pathReduction($p_stored_filename); + + if (strlen($v_reduce_filename) > 99) { + if (!$this->_writeLongHeader($v_reduce_filename)) + return false; + } + + $v_info = lstat($p_filename); + $v_uid = sprintf("%07s", DecOct($v_info[4])); + $v_gid = sprintf("%07s", DecOct($v_info[5])); + $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777)); + + $v_mtime = sprintf("%011s", DecOct($v_info['mtime'])); + + $v_linkname = ''; + + if (@is_link($p_filename)) { + $v_typeflag = '2'; + $v_linkname = readlink($p_filename); + $v_size = sprintf("%011s", DecOct(0)); + } elseif (@is_dir($p_filename)) { + $v_typeflag = "5"; + $v_size = sprintf("%011s", DecOct(0)); + } else { + $v_typeflag = '0'; + clearstatcache(); + $v_size = sprintf("%011s", DecOct($v_info['size'])); + } + + $v_magic = 'ustar '; + + $v_version = ' '; + + if (function_exists('posix_getpwuid')) + { + $userinfo = posix_getpwuid($v_info[4]); + $groupinfo = posix_getgrgid($v_info[5]); + + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } + else + { + $v_uname = ''; + $v_gname = ''; + } + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12a12", + $v_reduce_filename, $v_perms, $v_uid, + $v_gid, $v_size, $v_mtime); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + return true; + } + // }}} + + // {{{ _writeHeaderBlock() + function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0, + $p_type='', $p_uid=0, $p_gid=0) + { + $p_filename = $this->_pathReduction($p_filename); + + if (strlen($p_filename) > 99) { + if (!$this->_writeLongHeader($p_filename)) + return false; + } + + if ($p_type == "5") { + $v_size = sprintf("%011s", DecOct(0)); + } else { + $v_size = sprintf("%011s", DecOct($p_size)); + } + + $v_uid = sprintf("%07s", DecOct($p_uid)); + $v_gid = sprintf("%07s", DecOct($p_gid)); + $v_perms = sprintf("%07s", DecOct($p_perms & 000777)); + + $v_mtime = sprintf("%11s", DecOct($p_mtime)); + + $v_linkname = ''; + + $v_magic = 'ustar '; + + $v_version = ' '; + + if (function_exists('posix_getpwuid')) + { + $userinfo = posix_getpwuid($p_uid); + $groupinfo = posix_getgrgid($p_gid); + + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } + else + { + $v_uname = ''; + $v_gname = ''; + } + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12A12", + $p_filename, $v_perms, $v_uid, $v_gid, + $v_size, $v_mtime); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $p_type, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + return true; + } + // }}} + + // {{{ _writeLongHeader() + function _writeLongHeader($p_filename) + { + $v_size = sprintf("%11s ", DecOct(strlen($p_filename))); + + $v_typeflag = 'L'; + + $v_linkname = ''; + + $v_magic = ''; + + $v_version = ''; + + $v_uname = ''; + + $v_gname = ''; + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12a12", + '././@LongLink', 0, 0, 0, $v_size, 0); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + // ----- Write the filename as content of the block + $i=0; + while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + return true; + } + // }}} + + // {{{ _readHeader() + function _readHeader($v_binary_data, &$v_header) + { + if (strlen($v_binary_data)==0) { + $v_header['filename'] = ''; + return true; + } + + if (strlen($v_binary_data) != 512) { + $v_header['filename'] = ''; + $this->_error('Invalid block size : '.strlen($v_binary_data)); + return false; + } + + if (!is_array($v_header)) { + $v_header = array(); + } + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum+=ord(substr($v_binary_data,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156; $i<512; $i++) + $v_checksum+=ord(substr($v_binary_data,$i,1)); + + $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" + ."a8checksum/a1typeflag/a100link/a6magic/a2version/" + ."a32uname/a32gname/a8devmajor/a8devminor", + $v_binary_data); + + // ----- Extract the checksum + $v_header['checksum'] = OctDec(trim($v_data['checksum'])); + if ($v_header['checksum'] != $v_checksum) { + $v_header['filename'] = ''; + + // ----- Look for last block (empty block) + if (($v_checksum == 256) && ($v_header['checksum'] == 0)) + return true; + + $this->_error('Invalid checksum for file "'.$v_data['filename'] + .'" : '.$v_checksum.' calculated, ' + .$v_header['checksum'].' expected'); + return false; + } + + // ----- Extract the properties + $v_header['filename'] = $v_data['filename']; + if ($this->_maliciousFilename($v_header['filename'])) { + $this->_error('Malicious .tar detected, file "' . $v_header['filename'] . + '" will not install in desired directory tree'); + return false; + } + $v_header['mode'] = OctDec(trim($v_data['mode'])); + $v_header['uid'] = OctDec(trim($v_data['uid'])); + $v_header['gid'] = OctDec(trim($v_data['gid'])); + $v_header['size'] = OctDec(trim($v_data['size'])); + $v_header['mtime'] = OctDec(trim($v_data['mtime'])); + if (($v_header['typeflag'] = $v_data['typeflag']) == "5") { + $v_header['size'] = 0; + } + $v_header['link'] = trim($v_data['link']); + /* ----- All these fields are removed form the header because + they do not carry interesting info + $v_header[magic] = trim($v_data[magic]); + $v_header[version] = trim($v_data[version]); + $v_header[uname] = trim($v_data[uname]); + $v_header[gname] = trim($v_data[gname]); + $v_header[devmajor] = trim($v_data[devmajor]); + $v_header[devminor] = trim($v_data[devminor]); + */ + + return true; + } + // }}} + + // {{{ _maliciousFilename() + /** + * Detect and report a malicious file name + * + * @param string $file + * @return bool + * @access private + */ + function _maliciousFilename($file) + { + if (strpos($file, '/../') !== false) { + return true; + } + if (strpos($file, '../') === 0) { + return true; + } + return false; + } + // }}} + + // {{{ _readLongHeader() + function _readLongHeader(&$v_header) + { + $v_filename = ''; + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_content = $this->_readBlock(); + $v_filename .= $v_content; + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_filename .= trim($v_content); + } + + // ----- Read the next header + $v_binary_data = $this->_readBlock(); + + if (!$this->_readHeader($v_binary_data, $v_header)) + return false; + + $v_filename = trim($v_filename); + $v_header['filename'] = $v_filename; + if ($this->_maliciousFilename($v_filename)) { + $this->_error('Malicious .tar detected, file "' . $v_filename . + '" will not install in desired directory tree'); + return false; + } + + return true; + } + // }}} + + // {{{ _extractInString() + /** + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or NULL on error. + * @param string $p_filename The path of the file to extract in a string. + * @return a string with the file content or NULL. + * @access private + */ + function _extractInString($p_filename) + { + $v_result_str = ""; + + While (strlen($v_binary_data = $this->_readBlock()) != 0) + { + if (!$this->_readHeader($v_binary_data, $v_header)) + return NULL; + + if ($v_header['filename'] == '') + continue; + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) + return NULL; + } + + if ($v_header['filename'] == $p_filename) { + if ($v_header['typeflag'] == "5") { + $this->_error('Unable to extract in string a directory ' + .'entry {'.$v_header['filename'].'}'); + return NULL; + } else { + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_result_str .= $this->_readBlock(); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_result_str .= substr($v_content, 0, + ($v_header['size'] % 512)); + } + return $v_result_str; + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + } + + return NULL; + } + // }}} + + // {{{ _extractList() + function _extractList($p_path, &$p_list_detail, $p_mode, + $p_file_list, $p_remove_path) + { + $v_result=true; + $v_nb = 0; + $v_extract_all = true; + $v_listing = false; + + $p_path = $this->_translateWinPath($p_path, false); + if ($p_path == '' || (substr($p_path, 0, 1) != '/' + && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) { + $p_path = "./".$p_path; + } + $p_remove_path = $this->_translateWinPath($p_remove_path); + + // ----- Look for path to remove format (should end by /) + if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) + $p_remove_path .= '/'; + $p_remove_path_size = strlen($p_remove_path); + + switch ($p_mode) { + case "complete" : + $v_extract_all = TRUE; + $v_listing = FALSE; + break; + case "partial" : + $v_extract_all = FALSE; + $v_listing = FALSE; + break; + case "list" : + $v_extract_all = FALSE; + $v_listing = TRUE; + break; + default : + $this->_error('Invalid extract mode ('.$p_mode.')'); + return false; + } + + clearstatcache(); + + while (strlen($v_binary_data = $this->_readBlock()) != 0) + { + $v_extract_file = FALSE; + $v_extraction_stopped = 0; + + if (!$this->_readHeader($v_binary_data, $v_header)) + return false; + + if ($v_header['filename'] == '') { + continue; + } + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) + return false; + } + + if ((!$v_extract_all) && (is_array($p_file_list))) { + // ----- By default no unzip if the file is not found + $v_extract_file = false; + + for ($i=0; $i strlen($p_file_list[$i])) + && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) + == $p_file_list[$i])) { + $v_extract_file = TRUE; + break; + } + } + + // ----- It is a file, so compare the file names + elseif ($p_file_list[$i] == $v_header['filename']) { + $v_extract_file = TRUE; + break; + } + } + } else { + $v_extract_file = TRUE; + } + + // ----- Look if this file need to be extracted + if (($v_extract_file) && (!$v_listing)) + { + if (($p_remove_path != '') + && (substr($v_header['filename'], 0, $p_remove_path_size) + == $p_remove_path)) + $v_header['filename'] = substr($v_header['filename'], + $p_remove_path_size); + if (($p_path != './') && ($p_path != '/')) { + while (substr($p_path, -1) == '/') + $p_path = substr($p_path, 0, strlen($p_path)-1); + + if (substr($v_header['filename'], 0, 1) == '/') + $v_header['filename'] = $p_path.$v_header['filename']; + else + $v_header['filename'] = $p_path.'/'.$v_header['filename']; + } + if (file_exists($v_header['filename'])) { + if ( (@is_dir($v_header['filename'])) + && ($v_header['typeflag'] == '')) { + $this->_error('File '.$v_header['filename'] + .' already exists as a directory'); + return false; + } + if ( ($this->_isArchive($v_header['filename'])) + && ($v_header['typeflag'] == "5")) { + $this->_error('Directory '.$v_header['filename'] + .' already exists as a file'); + return false; + } + if (!is_writeable($v_header['filename'])) { + $this->_error('File '.$v_header['filename'] + .' already exists and is write protected'); + return false; + } + if (filemtime($v_header['filename']) > $v_header['mtime']) { + // To be completed : An error or silent no replace ? + } + } + + // ----- Check the directory availability and create it if necessary + elseif (($v_result + = $this->_dirCheck(($v_header['typeflag'] == "5" + ?$v_header['filename'] + :dirname($v_header['filename'])))) != 1) { + $this->_error('Unable to create path for '.$v_header['filename']); + return false; + } + + if ($v_extract_file) { + if ($v_header['typeflag'] == "5") { + if (!@file_exists($v_header['filename'])) { + if (!@mkdir($v_header['filename'], 0777)) { + $this->_error('Unable to create directory {' + .$v_header['filename'].'}'); + return false; + } + } + } elseif ($v_header['typeflag'] == "2") { + if (@file_exists($v_header['filename'])) { + @unlink($v_header['filename']); + } + if (!@symlink($v_header['link'], $v_header['filename'])) { + $this->_error('Unable to extract symbolic link {' + .$v_header['filename'].'}'); + return false; + } + } else { + if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { + $this->_error('Error while opening {'.$v_header['filename'] + .'} in write binary mode'); + return false; + } else { + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, 512); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); + } + + @fclose($v_dest_file); + + // ----- Change the file mode, mtime + @touch($v_header['filename'], $v_header['mtime']); + if ($v_header['mode'] & 0111) { + // make file executable, obey umask + $mode = fileperms($v_header['filename']) | (~umask() & 0111); + @chmod($v_header['filename'], $mode); + } + } + + // ----- Check the file size + clearstatcache(); + if (filesize($v_header['filename']) != $v_header['size']) { + $this->_error('Extracted file '.$v_header['filename'] + .' does not have the correct file size \'' + .filesize($v_header['filename']) + .'\' ('.$v_header['size'] + .' expected). Archive may be corrupted.'); + return false; + } + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + + /* TBC : Seems to be unused ... + if ($this->_compress) + $v_end_of_file = @gzeof($this->_file); + else + $v_end_of_file = @feof($this->_file); + */ + + if ($v_listing || $v_extract_file || $v_extraction_stopped) { + // ----- Log extracted files + if (($v_file_dir = dirname($v_header['filename'])) + == $v_header['filename']) + $v_file_dir = ''; + if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) + $v_file_dir = '/'; + + $p_list_detail[$v_nb++] = $v_header; + if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { + return true; + } + } + } + + return true; + } + // }}} + + // {{{ _openAppend() + function _openAppend() + { + if (filesize($this->_tarname) == 0) + return $this->_openWrite(); + + if ($this->_compress) { + $this->_close(); + + if (!@rename($this->_tarname, $this->_tarname.".tmp")) { + $this->_error('Error while renaming \''.$this->_tarname + .'\' to temporary file \''.$this->_tarname + .'.tmp\''); + return false; + } + + if ($this->_compress_type == 'gz') + $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb"); + elseif ($this->_compress_type == 'bz2') + $v_temp_tar = @bzopen($this->_tarname.".tmp", "r"); + + if ($v_temp_tar == 0) { + $this->_error('Unable to open file \''.$this->_tarname + .'.tmp\' in binary read mode'); + @rename($this->_tarname.".tmp", $this->_tarname); + return false; + } + + if (!$this->_openWrite()) { + @rename($this->_tarname.".tmp", $this->_tarname); + return false; + } + + if ($this->_compress_type == 'gz') { + while (!@gzeof($v_temp_tar)) { + $v_buffer = @gzread($v_temp_tar, 512); + if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + // do not copy end blocks, we will re-make them + // after appending + continue; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + @gzclose($v_temp_tar); + } + elseif ($this->_compress_type == 'bz2') { + while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + continue; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + @bzclose($v_temp_tar); + } + + if (!@unlink($this->_tarname.".tmp")) { + $this->_error('Error while deleting temporary file \'' + .$this->_tarname.'.tmp\''); + } + + } else { + // ----- For not compressed tar, just add files before the last + // one or two 512 bytes block + if (!$this->_openReadWrite()) + return false; + + clearstatcache(); + $v_size = filesize($this->_tarname); + + // We might have zero, one or two end blocks. + // The standard is two, but we should try to handle + // other cases. + fseek($this->_file, $v_size - 1024); + if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + fseek($this->_file, $v_size - 1024); + } + elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + fseek($this->_file, $v_size - 512); + } + } + + return true; + } + // }}} + + // {{{ _append() + function _append($p_filelist, $p_add_dir='', $p_remove_dir='') + { + if (!$this->_openAppend()) + return false; + + if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) + $this->_writeFooter(); + + $this->_close(); + + return true; + } + // }}} + + // {{{ _dirCheck() + + /** + * Check if a directory exists and create it (including parent + * dirs) if not. + * + * @param string $p_dir directory to check + * + * @return bool TRUE if the directory exists or was created + */ + function _dirCheck($p_dir) + { + clearstatcache(); + if ((@is_dir($p_dir)) || ($p_dir == '')) + return true; + + $p_parent_dir = dirname($p_dir); + + if (($p_parent_dir != $p_dir) && + ($p_parent_dir != '') && + (!$this->_dirCheck($p_parent_dir))) + return false; + + if (!@mkdir($p_dir, 0777)) { + $this->_error("Unable to create directory '$p_dir'"); + return false; + } + + return true; + } + + // }}} + + // {{{ _pathReduction() + + /** + * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar", + * rand emove double slashes. + * + * @param string $p_dir path to reduce + * + * @return string reduced path + * + * @access private + * + */ + function _pathReduction($p_dir) + { + $v_result = ''; + + // ----- Look for not empty path + if ($p_dir != '') { + // ----- Explode path by directory names + $v_list = explode('/', $p_dir); + + // ----- Study directories from last to first + for ($i=sizeof($v_list)-1; $i>=0; $i--) { + // ----- Look for current path + if ($v_list[$i] == ".") { + // ----- Ignore this directory + // Should be the first $i=0, but no check is done + } + else if ($v_list[$i] == "..") { + // ----- Ignore it and ignore the $i-1 + $i--; + } + else if ( ($v_list[$i] == '') + && ($i!=(sizeof($v_list)-1)) + && ($i!=0)) { + // ----- Ignore only the double '//' in path, + // but not the first and last / + } else { + $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/' + .$v_result:''); + } + } + } + $v_result = strtr($v_result, '\\', '/'); + return $v_result; + } + + // }}} + + // {{{ _translateWinPath() + function _translateWinPath($p_path, $p_remove_disk_letter=true) + { + if (defined('OS_WINDOWS') && OS_WINDOWS) { + // ----- Look for potential disk letter + if ( ($p_remove_disk_letter) + && (($v_position = strpos($p_path, ':')) != false)) { + $p_path = substr($p_path, $v_position+1); + } + // ----- Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) { + $p_path = strtr($p_path, '\\', '/'); + } + } + return $p_path; + } + // }}} + +} +?> + + * @license http://www.php.net/license/3_0.txt PHP 3.0 + * @version CVS: $Id: Getopt.php 267328 2008-10-14 18:53:25Z andrei $ + * @link http://pear.php.net/package/Console_Getopt + */ + +require_once 'phar://go-pear.phar/' . 'PEAR.php'; + +/** + * Command-line options parsing class. + * + * @category Console + * @package Console_Getopt + * @author Andrei Zmievski + * @license http://www.php.net/license/3_0.txt PHP 3.0 + * @link http://pear.php.net/package/Console_Getopt + */ +class Console_Getopt +{ + + /** + * Parses the command-line options. + * + * The first parameter to this function should be the list of command-line + * arguments without the leading reference to the running program. + * + * The second parameter is a string of allowed short options. Each of the + * option letters can be followed by a colon ':' to specify that the option + * requires an argument, or a double colon '::' to specify that the option + * takes an optional argument. + * + * The third argument is an optional array of allowed long options. The + * leading '--' should not be included in the option name. Options that + * require an argument should be followed by '=', and options that take an + * option argument should be followed by '=='. + * + * The return value is an array of two elements: the list of parsed + * options and the list of non-option command-line arguments. Each entry in + * the list of parsed options is a pair of elements - the first one + * specifies the option, and the second one specifies the option argument, + * if there was one. + * + * Long and short options can be mixed. + * + * Most of the semantics of this function are based on GNU getopt_long(). + * + * @param array $args an array of command-line arguments + * @param string $short_options specifies the list of allowed short options + * @param array $long_options specifies the list of allowed long options + * + * @return array two-element array containing the list of parsed options and + * the non-option arguments + * @access public + */ + function getopt2($args, $short_options, $long_options = null) + { + return Console_Getopt::doGetopt(2, $args, $short_options, $long_options); + } + + /** + * This function expects $args to start with the script name (POSIX-style). + * Preserved for backwards compatibility. + * + * @param array $args an array of command-line arguments + * @param string $short_options specifies the list of allowed short options + * @param array $long_options specifies the list of allowed long options + * + * @see getopt2() + * @return array two-element array containing the list of parsed options and + * the non-option arguments + */ + function getopt($args, $short_options, $long_options = null) + { + return Console_Getopt::doGetopt(1, $args, $short_options, $long_options); + } + + /** + * The actual implementation of the argument parsing code. + * + * @param int $version Version to use + * @param array $args an array of command-line arguments + * @param string $short_options specifies the list of allowed short options + * @param array $long_options specifies the list of allowed long options + * + * @return array + */ + function doGetopt($version, $args, $short_options, $long_options = null) + { + // in case you pass directly readPHPArgv() as the first arg + if (PEAR::isError($args)) { + return $args; + } + if (empty($args)) { + return array(array(), array()); + } + $opts = array(); + $non_opts = array(); + + settype($args, 'array'); + + if ($long_options) { + sort($long_options); + } + + /* + * Preserve backwards compatibility with callers that relied on + * erroneous POSIX fix. + */ + if ($version < 2) { + if (isset($args[0]{0}) && $args[0]{0} != '-') { + array_shift($args); + } + } + + reset($args); + while (list($i, $arg) = each($args)) { + + /* The special element '--' means explicit end of + options. Treat the rest of the arguments as non-options + and end the loop. */ + if ($arg == '--') { + $non_opts = array_merge($non_opts, array_slice($args, $i + 1)); + break; + } + + if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' + && !$long_options)) { + $non_opts = array_merge($non_opts, array_slice($args, $i)); + break; + } elseif (strlen($arg) > 1 && $arg{1} == '-') { + $error = Console_Getopt::_parseLongOption(substr($arg, 2), + $long_options, $opts, + $args); + if (PEAR::isError($error)) { + return $error; + } + } elseif ($arg == '-') { + // - is stdin + $non_opts = array_merge($non_opts, array_slice($args, $i)); + break; + } else { + $error = Console_Getopt::_parseShortOption(substr($arg, 1), + $short_options, $opts, + $args); + if (PEAR::isError($error)) { + return $error; + } + } + } + + return array($opts, $non_opts); + } + + /** + * Parse short option + * + * @param string $arg Argument + * @param string[] $short_options Available short options + * @param string[][] &$opts + * @param string[] &$args + * + * @access private + * @return void + */ + function _parseShortOption($arg, $short_options, &$opts, &$args) + { + for ($i = 0; $i < strlen($arg); $i++) { + $opt = $arg{$i}; + $opt_arg = null; + + /* Try to find the short option in the specifier string. */ + if (($spec = strstr($short_options, $opt)) === false + || $arg{$i} == ':') { + $msg = "Console_Getopt: unrecognized option -- $opt"; + return PEAR::raiseError($msg); + } + + if (strlen($spec) > 1 && $spec{1} == ':') { + if (strlen($spec) > 2 && $spec{2} == ':') { + if ($i + 1 < strlen($arg)) { + /* Option takes an optional argument. Use the remainder of + the arg string if there is anything left. */ + $opts[] = array($opt, substr($arg, $i + 1)); + break; + } + } else { + /* Option requires an argument. Use the remainder of the arg + string if there is anything left. */ + if ($i + 1 < strlen($arg)) { + $opts[] = array($opt, substr($arg, $i + 1)); + break; + } else if (list(, $opt_arg) = each($args)) { + /* Else use the next argument. */; + if (Console_Getopt::_isShortOpt($opt_arg) + || Console_Getopt::_isLongOpt($opt_arg)) { + $msg = "option requires an argument --$opt"; + return PEAR::raiseError("Console_Getopt:" . $msg); + } + } else { + $msg = "option requires an argument --$opt"; + return PEAR::raiseError("Console_Getopt:" . $msg); + } + } + } + + $opts[] = array($opt, $opt_arg); + } + } + + /** + * Checks if an argument is a short option + * + * @param string $arg Argument to check + * + * @access private + * @return bool + */ + function _isShortOpt($arg) + { + return strlen($arg) == 2 && $arg[0] == '-' + && preg_match('/[a-zA-Z]/', $arg[1]); + } + + /** + * Checks if an argument is a long option + * + * @param string $arg Argument to check + * + * @access private + * @return bool + */ + function _isLongOpt($arg) + { + return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' && + preg_match('/[a-zA-Z]+$/', substr($arg, 2)); + } + + /** + * Parse long option + * + * @param string $arg Argument + * @param string[] $long_options Available long options + * @param string[][] &$opts + * @param string[] &$args + * + * @access private + * @return void|PEAR_Error + */ + function _parseLongOption($arg, $long_options, &$opts, &$args) + { + @list($opt, $opt_arg) = explode('=', $arg, 2); + + $opt_len = strlen($opt); + + for ($i = 0; $i < count($long_options); $i++) { + $long_opt = $long_options[$i]; + $opt_start = substr($long_opt, 0, $opt_len); + + $long_opt_name = str_replace('=', '', $long_opt); + + /* Option doesn't match. Go on to the next one. */ + if ($long_opt_name != $opt) { + continue; + } + + $opt_rest = substr($long_opt, $opt_len); + + /* Check that the options uniquely matches one of the allowed + options. */ + if ($i + 1 < count($long_options)) { + $next_option_rest = substr($long_options[$i + 1], $opt_len); + } else { + $next_option_rest = ''; + } + + if ($opt_rest != '' && $opt{0} != '=' && + $i + 1 < count($long_options) && + $opt == substr($long_options[$i+1], 0, $opt_len) && + $next_option_rest != '' && + $next_option_rest{0} != '=') { + + $msg = "Console_Getopt: option --$opt is ambiguous"; + return PEAR::raiseError($msg); + } + + if (substr($long_opt, -1) == '=') { + if (substr($long_opt, -2) != '==') { + /* Long option requires an argument. + Take the next argument if one wasn't specified. */; + if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) { + $msg = "Console_Getopt: option requires an argument --$opt"; + return PEAR::raiseError($msg); + } + + if (Console_Getopt::_isShortOpt($opt_arg) + || Console_Getopt::_isLongOpt($opt_arg)) { + $msg = "Console_Getopt: option requires an argument --$opt"; + return PEAR::raiseError($msg); + } + } + } else if ($opt_arg) { + $msg = "Console_Getopt: option --$opt doesn't allow an argument"; + return PEAR::raiseError($msg); + } + + $opts[] = array('--' . $opt, $opt_arg); + return; + } + + return PEAR::raiseError("Console_Getopt: unrecognized option --$opt"); + } + + /** + * Safely read the $argv PHP array across different PHP configurations. + * Will take care on register_globals and register_argc_argv ini directives + * + * @access public + * @return mixed the $argv PHP array or PEAR error if not registered + */ + function readPHPArgv() + { + global $argv; + if (!is_array($argv)) { + if (!@is_array($_SERVER['argv'])) { + if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) { + $msg = "Could not read cmd args (register_argc_argv=Off?)"; + return PEAR::raiseError("Console_Getopt: " . $msg); + } + return $GLOBALS['HTTP_SERVER_VARS']['argv']; + } + return $_SERVER['argv']; + } + return $argv; + } + +} + +?> +run(); +?> + * @author Gregory Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Guess.php 278521 2009-04-09 22:24:12Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since PEAR 0.1 + */ + +// {{{ uname examples + +// php_uname() without args returns the same as 'uname -a', or a PHP-custom +// string for Windows. +// PHP versions prior to 4.3 return the uname of the host where PHP was built, +// as of 4.3 it returns the uname of the host running the PHP code. +// +// PC RedHat Linux 7.1: +// Linux host.example.com 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown +// +// PC Debian Potato: +// Linux host 2.4.17 #2 SMP Tue Feb 12 15:10:04 CET 2002 i686 unknown +// +// PC FreeBSD 3.3: +// FreeBSD host.example.com 3.3-STABLE FreeBSD 3.3-STABLE #0: Mon Feb 21 00:42:31 CET 2000 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.3: +// FreeBSD host.example.com 4.3-RELEASE FreeBSD 4.3-RELEASE #1: Mon Jun 25 11:19:43 EDT 2001 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.5: +// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb 6 23:59:23 CET 2002 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.5 w/uname from GNU shellutils: +// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb i386 unknown +// +// HP 9000/712 HP-UX 10: +// HP-UX iq B.10.10 A 9000/712 2008429113 two-user license +// +// HP 9000/712 HP-UX 10 w/uname from GNU shellutils: +// HP-UX host B.10.10 A 9000/712 unknown +// +// IBM RS6000/550 AIX 4.3: +// AIX host 3 4 000003531C00 +// +// AIX 4.3 w/uname from GNU shellutils: +// AIX host 3 4 000003531C00 unknown +// +// SGI Onyx IRIX 6.5 w/uname from GNU shellutils: +// IRIX64 host 6.5 01091820 IP19 mips +// +// SGI Onyx IRIX 6.5: +// IRIX64 host 6.5 01091820 IP19 +// +// SparcStation 20 Solaris 8 w/uname from GNU shellutils: +// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc +// +// SparcStation 20 Solaris 8: +// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc SUNW,SPARCstation-20 +// +// Mac OS X (Darwin) +// Darwin home-eden.local 7.5.0 Darwin Kernel Version 7.5.0: Thu Aug 5 19:26:16 PDT 2004; root:xnu/xnu-517.7.21.obj~3/RELEASE_PPC Power Macintosh +// +// Mac OS X early versions +// + +// }}} + +/* TODO: + * - define endianness, to allow matchSignature("bigend") etc. + */ + +/** + * Retrieves information about the current operating system + * + * This class uses php_uname() to grok information about the current OS + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Gregory Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class OS_Guess +{ + var $sysname; + var $nodename; + var $cpu; + var $release; + var $extra; + + function OS_Guess($uname = null) + { + list($this->sysname, + $this->release, + $this->cpu, + $this->extra, + $this->nodename) = $this->parseSignature($uname); + } + + function parseSignature($uname = null) + { + static $sysmap = array( + 'HP-UX' => 'hpux', + 'IRIX64' => 'irix', + ); + static $cpumap = array( + 'i586' => 'i386', + 'i686' => 'i386', + 'ppc' => 'powerpc', + ); + if ($uname === null) { + $uname = php_uname(); + } + $parts = preg_split('/\s+/', trim($uname)); + $n = count($parts); + + $release = $machine = $cpu = ''; + $sysname = $parts[0]; + $nodename = $parts[1]; + $cpu = $parts[$n-1]; + $extra = ''; + if ($cpu == 'unknown') { + $cpu = $parts[$n - 2]; + } + + switch ($sysname) { + case 'AIX' : + $release = "$parts[3].$parts[2]"; + break; + case 'Windows' : + switch ($parts[1]) { + case '95/98': + $release = '9x'; + break; + default: + $release = $parts[1]; + break; + } + $cpu = 'i386'; + break; + case 'Linux' : + $extra = $this->_detectGlibcVersion(); + // use only the first two digits from the kernel version + $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]); + break; + case 'Mac' : + $sysname = 'darwin'; + $nodename = $parts[2]; + $release = $parts[3]; + if ($cpu == 'Macintosh') { + if ($parts[$n - 2] == 'Power') { + $cpu = 'powerpc'; + } + } + break; + case 'Darwin' : + if ($cpu == 'Macintosh') { + if ($parts[$n - 2] == 'Power') { + $cpu = 'powerpc'; + } + } + $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]); + break; + default: + $release = preg_replace('/-.*/', '', $parts[2]); + break; + } + + if (isset($sysmap[$sysname])) { + $sysname = $sysmap[$sysname]; + } else { + $sysname = strtolower($sysname); + } + if (isset($cpumap[$cpu])) { + $cpu = $cpumap[$cpu]; + } + return array($sysname, $release, $cpu, $extra, $nodename); + } + + function _detectGlibcVersion() + { + static $glibc = false; + if ($glibc !== false) { + return $glibc; // no need to run this multiple times + } + $major = $minor = 0; + include_once 'phar://go-pear.phar/' . "System.php"; + // Use glibc's header file to + // get major and minor version number: + if (@file_exists('/usr/include/features.h') && + @is_readable('/usr/include/features.h')) { + if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) { + $features_file = fopen('/usr/include/features.h', 'rb'); + while (!feof($features_file)) { + $line = fgets($features_file, 8192); + if (!$line || (strpos($line, '#define') === false)) { + continue; + } + if (strpos($line, '__GLIBC__')) { + // major version number #define __GLIBC__ version + $line = preg_split('/\s+/', $line); + $glibc_major = trim($line[2]); + if (isset($glibc_minor)) { + break; + } + continue; + } + + if (strpos($line, '__GLIBC_MINOR__')) { + // got the minor version number + // #define __GLIBC_MINOR__ version + $line = preg_split('/\s+/', $line); + $glibc_minor = trim($line[2]); + if (isset($glibc_major)) { + break; + } + continue; + } + } + fclose($features_file); + if (!isset($glibc_major) || !isset($glibc_minor)) { + return $glibc = ''; + } + return $glibc = 'glibc' . trim($glibc_major) . "." . trim($glibc_minor) ; + } // no cpp + + $tmpfile = System::mktemp("glibctest"); + $fp = fopen($tmpfile, "w"); + fwrite($fp, "#include \n__GLIBC__ __GLIBC_MINOR__\n"); + fclose($fp); + $cpp = popen("/usr/bin/cpp $tmpfile", "r"); + while ($line = fgets($cpp, 1024)) { + if ($line{0} == '#' || trim($line) == '') { + continue; + } + + if (list($major, $minor) = explode(' ', trim($line))) { + break; + } + } + pclose($cpp); + unlink($tmpfile); + } // features.h + + if (!($major && $minor) && @is_link('/lib/libc.so.6')) { + // Let's try reading the libc.so.6 symlink + if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib/libc.so.6')), $matches)) { + list($major, $minor) = explode('.', $matches[1]); + } + } + + if (!($major && $minor)) { + return $glibc = ''; + } + + return $glibc = "glibc{$major}.{$minor}"; + } + + function getSignature() + { + if (empty($this->extra)) { + return "{$this->sysname}-{$this->release}-{$this->cpu}"; + } + return "{$this->sysname}-{$this->release}-{$this->cpu}-{$this->extra}"; + } + + function getSysname() + { + return $this->sysname; + } + + function getNodename() + { + return $this->nodename; + } + + function getCpu() + { + return $this->cpu; + } + + function getRelease() + { + return $this->release; + } + + function getExtra() + { + return $this->extra; + } + + function matchSignature($match) + { + $fragments = is_array($match) ? $match : explode('-', $match); + $n = count($fragments); + $matches = 0; + if ($n > 0) { + $matches += $this->_matchFragment($fragments[0], $this->sysname); + } + if ($n > 1) { + $matches += $this->_matchFragment($fragments[1], $this->release); + } + if ($n > 2) { + $matches += $this->_matchFragment($fragments[2], $this->cpu); + } + if ($n > 3) { + $matches += $this->_matchFragment($fragments[3], $this->extra); + } + return ($matches == $n); + } + + function _matchFragment($fragment, $value) + { + if (strcspn($fragment, '*?') < strlen($fragment)) { + $reg = '/^' . str_replace(array('*', '?', '/'), array('.*', '.', '\\/'), $fragment) . '\\z/'; + return preg_match($reg, $value); + } + return ($fragment == '*' || !strcasecmp($fragment, $value)); + } + +} +/* + * Local Variables: + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: PEAR.php 286670 2009-08-02 14:16:06Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/**#@+ + * ERROR constants + */ +define('PEAR_ERROR_RETURN', 1); +define('PEAR_ERROR_PRINT', 2); +define('PEAR_ERROR_TRIGGER', 4); +define('PEAR_ERROR_DIE', 8); +define('PEAR_ERROR_CALLBACK', 16); +/** + * WARNING: obsolete + * @deprecated + */ +define('PEAR_ERROR_EXCEPTION', 32); +/**#@-*/ +define('PEAR_ZE2', (function_exists('version_compare') && + version_compare(zend_version(), "2-dev", "ge"))); + +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); + define('OS_UNIX', false); + define('PEAR_OS', 'Windows'); +} else { + define('OS_WINDOWS', false); + define('OS_UNIX', true); + define('PEAR_OS', 'Unix'); // blatant assumption +} + +$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; +$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; +$GLOBALS['_PEAR_destructor_object_list'] = array(); +$GLOBALS['_PEAR_shutdown_funcs'] = array(); +$GLOBALS['_PEAR_error_handler_stack'] = array(); + +@ini_set('track_errors', true); + +/** + * Base class for other PEAR classes. Provides rudimentary + * emulation of destructors. + * + * If you want a destructor in your class, inherit PEAR and make a + * destructor method called _yourclassname (same name as the + * constructor, but with a "_" prefix). Also, in your constructor you + * have to call the PEAR constructor: $this->PEAR();. + * The destructor method will be called without parameters. Note that + * at in some SAPI implementations (such as Apache), any output during + * the request shutdown (in which destructors are called) seems to be + * discarded. If you need to get any debug information from your + * destructor, use error_log(), syslog() or something similar. + * + * IMPORTANT! To use the emulated destructors you need to create the + * objects by reference: $obj =& new PEAR_child; + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Greg Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @see PEAR_Error + * @since Class available since PHP 4.0.2 + * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear + */ +class PEAR +{ + // {{{ properties + + /** + * Whether to enable internal debug messages. + * + * @var bool + * @access private + */ + var $_debug = false; + + /** + * Default error mode for this object. + * + * @var int + * @access private + */ + var $_default_error_mode = null; + + /** + * Default error options used for this object when error mode + * is PEAR_ERROR_TRIGGER. + * + * @var int + * @access private + */ + var $_default_error_options = null; + + /** + * Default error handler (callback) for this object, if error mode is + * PEAR_ERROR_CALLBACK. + * + * @var string + * @access private + */ + var $_default_error_handler = ''; + + /** + * Which class to use for error objects. + * + * @var string + * @access private + */ + var $_error_class = 'PEAR_Error'; + + /** + * An array of expected errors. + * + * @var array + * @access private + */ + var $_expected_errors = array(); + + // }}} + + // {{{ constructor + + /** + * Constructor. Registers this object in + * $_PEAR_destructor_object_list for destructor emulation if a + * destructor object exists. + * + * @param string $error_class (optional) which class to use for + * error objects, defaults to PEAR_Error. + * @access public + * @return void + */ + function PEAR($error_class = null) + { + $classname = strtolower(get_class($this)); + if ($this->_debug) { + print "PEAR constructor called, class=$classname\n"; + } + if ($error_class !== null) { + $this->_error_class = $error_class; + } + while ($classname && strcasecmp($classname, "pear")) { + $destructor = "_$classname"; + if (method_exists($this, $destructor)) { + global $_PEAR_destructor_object_list; + $_PEAR_destructor_object_list[] = &$this; + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + break; + } else { + $classname = get_parent_class($classname); + } + } + } + + // }}} + // {{{ destructor + + /** + * Destructor (the emulated type of...). Does nothing right now, + * but is included for forward compatibility, so subclass + * destructors should always call it. + * + * See the note in the class desciption about output from + * destructors. + * + * @access public + * @return void + */ + function _PEAR() { + if ($this->_debug) { + printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); + } + } + + // }}} + // {{{ getStaticProperty() + + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @access public + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + function &getStaticProperty($class, $var) + { + static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + + return $properties[$class][$var]; + } + + // }}} + // {{{ registerShutdownFunc() + + /** + * Use this function to register a shutdown method for static + * classes. + * + * @access public + * @param mixed $func The function name (or array of class/method) to call + * @param mixed $args The arguments to pass to the function + * @return void + */ + function registerShutdownFunc($func, $args = array()) + { + // if we are called statically, there is a potential + // that no shutdown func is registered. Bug #6445 + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); + } + + // }}} + // {{{ isError() + + /** + * Tell whether a value is a PEAR error. + * + * @param mixed $data the value to test + * @param int $code if $data is an error object, return true + * only if $code is a string and + * $obj->getMessage() == $code or + * $code is an integer and $obj->getCode() == $code + * @access public + * @return bool true if parameter is an error + */ + function isError($data, $code = null) + { + if (!is_a($data, 'PEAR_Error')) { + return false; + } + + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() == $code; + } + + return $data->getCode() == $code; + } + + // }}} + // {{{ setErrorHandling() + + /** + * Sets how errors generated by this object should be handled. + * Can be invoked both in objects and statically. If called + * statically, setErrorHandling sets the default behaviour for all + * PEAR objects. If called in an object, setErrorHandling sets + * the default behaviour for that object. + * + * @param int $mode + * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. + * + * @param mixed $options + * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one + * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * + * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected + * to be the callback function or method. A callback + * function is a string with the name of the function, a + * callback method is an array of two elements: the element + * at index 0 is the object, and the element at index 1 is + * the name of the method to call in the object. + * + * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is + * a printf format string used when printing the error + * message. + * + * @access public + * @return void + * @see PEAR_ERROR_RETURN + * @see PEAR_ERROR_PRINT + * @see PEAR_ERROR_TRIGGER + * @see PEAR_ERROR_DIE + * @see PEAR_ERROR_CALLBACK + * @see PEAR_ERROR_EXCEPTION + * + * @since PHP 4.0.5 + */ + + function setErrorHandling($mode = null, $options = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $setmode = &$this->_default_error_mode; + $setoptions = &$this->_default_error_options; + } else { + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + } + + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + } + + // }}} + // {{{ expectError() + + /** + * This method is used to tell which errors you expect to get. + * Expected errors are always returned with error mode + * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, + * and this method pushes a new element onto it. The list of + * expected errors are in effect until they are popped off the + * stack with the popExpect() method. + * + * Note that this method can not be called statically + * + * @param mixed $code a single error code or an array of error codes to expect + * + * @return int the new depth of the "expected errors" stack + * @access public + */ + function expectError($code = '*') + { + if (is_array($code)) { + array_push($this->_expected_errors, $code); + } else { + array_push($this->_expected_errors, array($code)); + } + return sizeof($this->_expected_errors); + } + + // }}} + // {{{ popExpect() + + /** + * This method pops one element off the expected error codes + * stack. + * + * @return array the list of error codes that were popped + */ + function popExpect() + { + return array_pop($this->_expected_errors); + } + + // }}} + // {{{ _checkDelExpect() + + /** + * This method checks unsets an error code if available + * + * @param mixed error code + * @return bool true if the error code was unset, false otherwise + * @access private + * @since PHP 4.3.0 + */ + function _checkDelExpect($error_code) + { + $deleted = false; + + foreach ($this->_expected_errors AS $key => $error_array) { + if (in_array($error_code, $error_array)) { + unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); + $deleted = true; + } + + // clean up empty arrays + if (0 == count($this->_expected_errors[$key])) { + unset($this->_expected_errors[$key]); + } + } + return $deleted; + } + + // }}} + // {{{ delExpect() + + /** + * This method deletes all occurences of the specified element from + * the expected error codes stack. + * + * @param mixed $error_code error code that should be deleted + * @return mixed list of error codes that were deleted or error + * @access public + * @since PHP 4.3.0 + */ + function delExpect($error_code) + { + $deleted = false; + if ((is_array($error_code) && (0 != count($error_code)))) { + // $error_code is a non-empty array here; + // we walk through it trying to unset all + // values + foreach($error_code as $key => $error) { + if ($this->_checkDelExpect($error)) { + $deleted = true; + } else { + $deleted = false; + } + } + return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } elseif (!empty($error_code)) { + // $error_code comes alone, trying to unset it + if ($this->_checkDelExpect($error_code)) { + return true; + } else { + return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } + } + + // $error_code is empty + return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME + } + + // }}} + // {{{ raiseError() + + /** + * This method is a wrapper that returns an instance of the + * configured error class with this object's default error + * handling applied. If the $mode and $options parameters are not + * specified, the object's defaults are used. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. + * + * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter + * specifies the PHP-internal error level (one of + * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * If $mode is PEAR_ERROR_CALLBACK, this + * parameter specifies the callback function or + * method. In other error modes this parameter + * is ignored. + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @param string $error_class The returned error object will be + * instantiated from this class, if specified. + * + * @param bool $skipmsg If true, raiseError will only pass error codes, + * the error message parameter will be dropped. + * + * @access public + * @return object a PEAR error object + * @see PEAR::setErrorHandling + * @since PHP 4.0.5 + */ + function &raiseError($message = null, + $code = null, + $mode = null, + $options = null, + $userinfo = null, + $error_class = null, + $skipmsg = false) + { + // The error is yet a PEAR error object + if (is_object($message)) { + $code = $message->getCode(); + $userinfo = $message->getUserInfo(); + $error_class = $message->getType(); + $message->error_message_prefix = ''; + $message = $message->getMessage(); + } + + if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) { + if ($exp[0] == "*" || + (is_int(reset($exp)) && in_array($code, $exp)) || + (is_string(reset($exp)) && in_array($message, $exp))) { + $mode = PEAR_ERROR_RETURN; + } + } + + // No mode given, try global ones + if ($mode === null) { + // Class error handler + if (isset($this) && isset($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + // Global error handler + } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { + $mode = $GLOBALS['_PEAR_default_error_mode']; + $options = $GLOBALS['_PEAR_default_error_options']; + } + } + + if ($error_class !== null) { + $ec = $error_class; + } elseif (isset($this) && isset($this->_error_class)) { + $ec = $this->_error_class; + } else { + $ec = 'PEAR_Error'; + } + + if (intval(PHP_VERSION) < 5) { + // little non-eval hack to fix bug #12147 + include 'phar://go-pear.phar/' . 'PEAR/FixPHP5PEARWarnings.php'; + return $a; + } + + if ($skipmsg) { + $a = new $ec($code, $mode, $options, $userinfo); + } else { + $a = new $ec($message, $code, $mode, $options, $userinfo); + } + + return $a; + } + + // }}} + // {{{ throwError() + + /** + * Simpler form of raiseError with fewer options. In most cases + * message, code and userinfo are enough. + * + * @param string $message + * + */ + function &throwError($message = null, + $code = null, + $userinfo = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $a = &$this->raiseError($message, $code, null, null, $userinfo); + return $a; + } + + $a = &PEAR::raiseError($message, $code, null, null, $userinfo); + return $a; + } + + // }}} + function staticPushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + $stack[] = array($def_mode, $def_options); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $def_mode = $mode; + $def_options = $options; + break; + + case PEAR_ERROR_CALLBACK: + $def_mode = $mode; + // class/object method callback + if (is_callable($options)) { + $def_options = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + $stack[] = array($mode, $options); + return true; + } + + function staticPopErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + return true; + } + + // {{{ pushErrorHandling() + + /** + * Push a new error handler on top of the error handler options stack. With this + * you can easily override the actual error handler for some code and restore + * it later with popErrorHandling. + * + * @param mixed $mode (same as setErrorHandling) + * @param mixed $options (same as setErrorHandling) + * + * @return bool Always true + * + * @see PEAR::setErrorHandling + */ + function pushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + if (isset($this) && is_a($this, 'PEAR')) { + $def_mode = &$this->_default_error_mode; + $def_options = &$this->_default_error_options; + } else { + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + } + $stack[] = array($def_mode, $def_options); + + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + $stack[] = array($mode, $options); + return true; + } + + // }}} + // {{{ popErrorHandling() + + /** + * Pop the last error handler used + * + * @return bool Always true + * + * @see PEAR::pushErrorHandling + */ + function popErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + return true; + } + + // }}} + // {{{ loadExtension() + + /** + * OS independant PHP extension load. Remember to take care + * on the correct extension name for case sensitive OSes. + * + * @param string $ext The extension name + * @return bool Success or not on the dl() call + */ + function loadExtension($ext) + { + if (!extension_loaded($ext)) { + // if either returns true dl() will produce a FATAL error, stop that + if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { + return false; + } + + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; + } + + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + } + + return true; + } + + // }}} +} + +if (PEAR_ZE2) { + include_once 'phar://go-pear.phar/' . 'PEAR5.php'; +} + +// {{{ _PEAR_call_destructors() + +function _PEAR_call_destructors() +{ + global $_PEAR_destructor_object_list; + if (is_array($_PEAR_destructor_object_list) && + sizeof($_PEAR_destructor_object_list)) + { + reset($_PEAR_destructor_object_list); + if (PEAR_ZE2) { + $destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo'); + } else { + $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo'); + } + + if ($destructLifoExists) { + $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); + } + + while (list($k, $objref) = each($_PEAR_destructor_object_list)) { + $classname = get_class($objref); + while ($classname) { + $destructor = "_$classname"; + if (method_exists($objref, $destructor)) { + $objref->$destructor(); + break; + } else { + $classname = get_parent_class($classname); + } + } + } + // Empty the object list to ensure that destructors are + // not called more than once. + $_PEAR_destructor_object_list = array(); + } + + // Now call the shutdown functions + if (isset($GLOBALS['_PEAR_shutdown_funcs']) AND is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) { + foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { + call_user_func_array($value[0], $value[1]); + } + } +} + +// }}} +/** + * Standard PEAR error class for PHP 4 + * + * This class is supserseded by {@link PEAR_Exception} in PHP 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Gregory Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/manual/en/core.pear.pear-error.php + * @see PEAR::raiseError(), PEAR::throwError() + * @since Class available since PHP 4.0.2 + */ +class PEAR_Error +{ + // {{{ properties + + var $error_message_prefix = ''; + var $mode = PEAR_ERROR_RETURN; + var $level = E_USER_NOTICE; + var $code = -1; + var $message = ''; + var $userinfo = ''; + var $backtrace = null; + + // }}} + // {{{ constructor + + /** + * PEAR_Error constructor + * + * @param string $message message + * + * @param int $code (optional) error code + * + * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, + * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION + * + * @param mixed $options (optional) error level, _OR_ in the case of + * PEAR_ERROR_CALLBACK, the callback function or object/method + * tuple. + * + * @param string $userinfo (optional) additional user/debug info + * + * @access public + * + */ + function PEAR_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + if ($mode === null) { + $mode = PEAR_ERROR_RETURN; + } + $this->message = $message; + $this->code = $code; + $this->mode = $mode; + $this->userinfo = $userinfo; + + if (PEAR_ZE2) { + $skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace'); + } else { + $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace'); + } + + if (!$skiptrace) { + $this->backtrace = debug_backtrace(); + if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) { + unset($this->backtrace[0]['object']); + } + } + + if ($mode & PEAR_ERROR_CALLBACK) { + $this->level = E_USER_NOTICE; + $this->callback = $options; + } else { + if ($options === null) { + $options = E_USER_NOTICE; + } + + $this->level = $options; + $this->callback = null; + } + + if ($this->mode & PEAR_ERROR_PRINT) { + if (is_null($options) || is_int($options)) { + $format = "%s"; + } else { + $format = $options; + } + + printf($format, $this->getMessage()); + } + + if ($this->mode & PEAR_ERROR_TRIGGER) { + trigger_error($this->getMessage(), $this->level); + } + + if ($this->mode & PEAR_ERROR_DIE) { + $msg = $this->getMessage(); + if (is_null($options) || is_int($options)) { + $format = "%s"; + if (substr($msg, -1) != "\n") { + $msg .= "\n"; + } + } else { + $format = $options; + } + die(sprintf($format, $msg)); + } + + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_callable($this->callback)) { + call_user_func($this->callback, $this); + } + } + + if ($this->mode & PEAR_ERROR_EXCEPTION) { + trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); + eval('$e = new Exception($this->message, $this->code);throw($e);'); + } + } + + // }}} + // {{{ getMode() + + /** + * Get the error mode from an error object. + * + * @return int error mode + * @access public + */ + function getMode() { + return $this->mode; + } + + // }}} + // {{{ getCallback() + + /** + * Get the callback function/method from an error object. + * + * @return mixed callback function or object/method array + * @access public + */ + function getCallback() { + return $this->callback; + } + + // }}} + // {{{ getMessage() + + + /** + * Get the error message from an error object. + * + * @return string full error message + * @access public + */ + function getMessage() + { + return ($this->error_message_prefix . $this->message); + } + + + // }}} + // {{{ getCode() + + /** + * Get error code from an error object + * + * @return int error code + * @access public + */ + function getCode() + { + return $this->code; + } + + // }}} + // {{{ getType() + + /** + * Get the name of this error/exception. + * + * @return string error/exception name (type) + * @access public + */ + function getType() + { + return get_class($this); + } + + // }}} + // {{{ getUserInfo() + + /** + * Get additional user-supplied information. + * + * @return string user-supplied information + * @access public + */ + function getUserInfo() + { + return $this->userinfo; + } + + // }}} + // {{{ getDebugInfo() + + /** + * Get additional debug information supplied by the application. + * + * @return string debug information + * @access public + */ + function getDebugInfo() + { + return $this->getUserInfo(); + } + + // }}} + // {{{ getBacktrace() + + /** + * Get the call backtrace from where the error was generated. + * Supported with PHP 4.3.0 or newer. + * + * @param int $frame (optional) what frame to fetch + * @return array Backtrace, or NULL if not available. + * @access public + */ + function getBacktrace($frame = null) + { + if (defined('PEAR_IGNORE_BACKTRACE')) { + return null; + } + if ($frame === null) { + return $this->backtrace; + } + return $this->backtrace[$frame]; + } + + // }}} + // {{{ addUserInfo() + + function addUserInfo($info) + { + if (empty($this->userinfo)) { + $this->userinfo = $info; + } else { + $this->userinfo .= " ** $info"; + } + } + + // }}} + // {{{ toString() + function __toString() + { + return $this->getMessage(); + } + // }}} + // {{{ toString() + + /** + * Make a string representation of this object. + * + * @return string a string with an object summary + * @access public + */ + function toString() { + $modes = array(); + $levels = array(E_USER_NOTICE => 'notice', + E_USER_WARNING => 'warning', + E_USER_ERROR => 'error'); + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_array($this->callback)) { + $callback = (is_object($this->callback[0]) ? + strtolower(get_class($this->callback[0])) : + $this->callback[0]) . '::' . + $this->callback[1]; + } else { + $callback = $this->callback; + } + return sprintf('[%s: message="%s" code=%d mode=callback '. + 'callback=%s prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + $callback, $this->error_message_prefix, + $this->userinfo); + } + if ($this->mode & PEAR_ERROR_PRINT) { + $modes[] = 'print'; + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + $modes[] = 'trigger'; + } + if ($this->mode & PEAR_ERROR_DIE) { + $modes[] = 'die'; + } + if ($this->mode & PEAR_ERROR_RETURN) { + $modes[] = 'return'; + } + return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. + 'prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + implode("|", $modes), $levels[$this->level], + $this->error_message_prefix, + $this->userinfo); + } + + // }}} +} + +/* + * Local Variables: + * mode: php + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: ChannelFile.php 286951 2009-08-09 14:41:22Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Needed for error handling + */ +require_once 'phar://go-pear.phar/' . 'PEAR/ErrorStack.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/XMLParser.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/Common.php'; + +/** + * Error code if the channel.xml tag does not contain a valid version + */ +define('PEAR_CHANNELFILE_ERROR_NO_VERSION', 1); +/** + * Error code if the channel.xml tag version is not supported (version 1.0 is the only supported version, + * currently + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_VERSION', 2); + +/** + * Error code if parsing is attempted with no xml extension + */ +define('PEAR_CHANNELFILE_ERROR_NO_XML_EXT', 3); + +/** + * Error code if creating the xml parser resource fails + */ +define('PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER', 4); + +/** + * Error code used for all sax xml parsing errors + */ +define('PEAR_CHANNELFILE_ERROR_PARSER_ERROR', 5); + +/**#@+ + * Validation errors + */ +/** + * Error code when channel name is missing + */ +define('PEAR_CHANNELFILE_ERROR_NO_NAME', 6); +/** + * Error code when channel name is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_NAME', 7); +/** + * Error code when channel summary is missing + */ +define('PEAR_CHANNELFILE_ERROR_NO_SUMMARY', 8); +/** + * Error code when channel summary is multi-line + */ +define('PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY', 9); +/** + * Error code when channel server is missing for protocol + */ +define('PEAR_CHANNELFILE_ERROR_NO_HOST', 10); +/** + * Error code when channel server is invalid for protocol + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_HOST', 11); +/** + * Error code when a mirror name is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_MIRROR', 21); +/** + * Error code when a mirror type is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE', 22); +/** + * Error code when an attempt is made to generate xml, but the parsed content is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID', 23); +/** + * Error code when an empty package name validate regex is passed in + */ +define('PEAR_CHANNELFILE_ERROR_EMPTY_REGEX', 24); +/** + * Error code when a tag has no version + */ +define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION', 25); +/** + * Error code when a tag has no name + */ +define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME', 26); +/** + * Error code when a tag has no name + */ +define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME', 27); +/** + * Error code when a tag has no version attribute + */ +define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION', 28); +/** + * Error code when a mirror does not exist but is called for in one of the set* + * methods. + */ +define('PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND', 32); +/** + * Error code when a server port is not numeric + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_PORT', 33); +/** + * Error code when contains no version attribute + */ +define('PEAR_CHANNELFILE_ERROR_NO_STATICVERSION', 34); +/** + * Error code when contains no type attribute in a protocol definition + */ +define('PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE', 35); +/** + * Error code when a mirror is defined and the channel.xml represents the __uri pseudo-channel + */ +define('PEAR_CHANNELFILE_URI_CANT_MIRROR', 36); +/** + * Error code when ssl attribute is present and is not "yes" + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_SSL', 37); +/**#@-*/ + +/** + * Mirror types allowed. Currently only internet servers are recognized. + */ +$GLOBALS['_PEAR_CHANNELS_MIRROR_TYPES'] = array('server'); + + +/** + * The Channel handling class + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_ChannelFile +{ + /** + * @access private + * @var PEAR_ErrorStack + * @access private + */ + var $_stack; + + /** + * Supported channel.xml versions, for parsing + * @var array + * @access private + */ + var $_supportedVersions = array('1.0'); + + /** + * Parsed channel information + * @var array + * @access private + */ + var $_channelInfo; + + /** + * index into the subchannels array, used for parsing xml + * @var int + * @access private + */ + var $_subchannelIndex; + + /** + * index into the mirrors array, used for parsing xml + * @var int + * @access private + */ + var $_mirrorIndex; + + /** + * Flag used to determine the validity of parsed content + * @var boolean + * @access private + */ + var $_isValid = false; + + function PEAR_ChannelFile() + { + $this->_stack = &new PEAR_ErrorStack('PEAR_ChannelFile'); + $this->_stack->setErrorMessageTemplate($this->_getErrorMessage()); + $this->_isValid = false; + } + + /** + * @return array + * @access protected + */ + function _getErrorMessage() + { + return + array( + PEAR_CHANNELFILE_ERROR_INVALID_VERSION => + 'While parsing channel.xml, an invalid version number "%version% was passed in, expecting one of %versions%', + PEAR_CHANNELFILE_ERROR_NO_VERSION => + 'No version number found in tag', + PEAR_CHANNELFILE_ERROR_NO_XML_EXT => + '%error%', + PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER => + 'Unable to create XML parser', + PEAR_CHANNELFILE_ERROR_PARSER_ERROR => + '%error%', + PEAR_CHANNELFILE_ERROR_NO_NAME => + 'Missing channel name', + PEAR_CHANNELFILE_ERROR_INVALID_NAME => + 'Invalid channel %tag% "%name%"', + PEAR_CHANNELFILE_ERROR_NO_SUMMARY => + 'Missing channel summary', + PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY => + 'Channel summary should be on one line, but is multi-line', + PEAR_CHANNELFILE_ERROR_NO_HOST => + 'Missing channel server for %type% server', + PEAR_CHANNELFILE_ERROR_INVALID_HOST => + 'Server name "%server%" is invalid for %type% server', + PEAR_CHANNELFILE_ERROR_INVALID_MIRROR => + 'Invalid mirror name "%name%", mirror type %type%', + PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE => + 'Invalid mirror type "%type%"', + PEAR_CHANNELFILE_ERROR_INVALID => + 'Cannot generate xml, contents are invalid', + PEAR_CHANNELFILE_ERROR_EMPTY_REGEX => + 'packagenameregex cannot be empty', + PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION => + '%parent% %protocol% function has no version', + PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME => + '%parent% %protocol% function has no name', + PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE => + '%parent% rest baseurl has no type', + PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME => + 'Validation package has no name in tag', + PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION => + 'Validation package "%package%" has no version', + PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND => + 'Mirror "%mirror%" does not exist', + PEAR_CHANNELFILE_ERROR_INVALID_PORT => + 'Port "%port%" must be numeric', + PEAR_CHANNELFILE_ERROR_NO_STATICVERSION => + ' tag must contain version attribute', + PEAR_CHANNELFILE_URI_CANT_MIRROR => + 'The __uri pseudo-channel cannot have mirrors', + PEAR_CHANNELFILE_ERROR_INVALID_SSL => + '%server% has invalid ssl attribute "%ssl%" can only be yes or not present', + ); + } + + /** + * @param string contents of package.xml file + * @return bool success of parsing + */ + function fromXmlString($data) + { + if (preg_match('/_supportedVersions)) { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_INVALID_VERSION, 'error', + array('version' => $channelversion[1])); + return false; + } + $parser = new PEAR_XMLParser; + $result = $parser->parse($data); + if ($result !== true) { + if ($result->getCode() == 1) { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_XML_EXT, 'error', + array('error' => $result->getMessage())); + } else { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER, 'error'); + } + return false; + } + $this->_channelInfo = $parser->getData(); + return true; + } else { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_VERSION, 'error', array('xml' => $data)); + return false; + } + } + + /** + * @return array + */ + function toArray() + { + if (!$this->_isValid && !$this->validate()) { + return false; + } + return $this->_channelInfo; + } + + /** + * @param array + * @static + * @return PEAR_ChannelFile|false false if invalid + */ + function &fromArray($data, $compatibility = false, $stackClass = 'PEAR_ErrorStack') + { + $a = new PEAR_ChannelFile($compatibility, $stackClass); + $a->_fromArray($data); + if (!$a->validate()) { + $a = false; + return $a; + } + return $a; + } + + /** + * Unlike {@link fromArray()} this does not do any validation + * @param array + * @static + * @return PEAR_ChannelFile + */ + function &fromArrayWithErrors($data, $compatibility = false, + $stackClass = 'PEAR_ErrorStack') + { + $a = new PEAR_ChannelFile($compatibility, $stackClass); + $a->_fromArray($data); + return $a; + } + + /** + * @param array + * @access private + */ + function _fromArray($data) + { + $this->_channelInfo = $data; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getErrors($purge = false) + { + return $this->_stack->getErrors($purge); + } + + /** + * Unindent given string (?) + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } + } + return $data; + } + + /** + * Parse a channel.xml file. Expects the name of + * a channel xml file as input. + * + * @param string $descfile name of channel xml file + * @return bool success of parsing + */ + function fromXmlFile($descfile) + { + if (!file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) || + (!$fp = fopen($descfile, 'r'))) { + require_once 'phar://go-pear.phar/' . 'PEAR.php'; + return PEAR::raiseError("Unable to open $descfile"); + } + + // read the whole thing so we only get one cdata callback + // for each block of cdata + fclose($fp); + $data = file_get_contents($descfile); + return $this->fromXmlString($data); + } + + /** + * Parse channel information from different sources + * + * This method is able to extract information about a channel + * from an .xml file or a string + * + * @access public + * @param string Filename of the source or the source itself + * @return bool + */ + function fromAny($info) + { + if (is_string($info) && file_exists($info) && strlen($info) < 255) { + $tmp = substr($info, -4); + if ($tmp == '.xml') { + $info = $this->fromXmlFile($info); + } else { + $fp = fopen($info, "r"); + $test = fread($fp, 5); + fclose($fp); + if ($test == "fromXmlFile($info); + } + } + if (PEAR::isError($info)) { + require_once 'phar://go-pear.phar/' . 'PEAR.php'; + return PEAR::raiseError($info); + } + } + if (is_string($info)) { + $info = $this->fromXmlString($info); + } + return $info; + } + + /** + * Return an XML document based on previous parsing and modifications + * + * @return string XML data + * + * @access public + */ + function toXml() + { + if (!$this->_isValid && !$this->validate()) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID); + return false; + } + if (!isset($this->_channelInfo['attribs']['version'])) { + $this->_channelInfo['attribs']['version'] = '1.0'; + } + $channelInfo = $this->_channelInfo; + $ret = "\n"; + $ret .= " + $channelInfo[name] + " . htmlspecialchars($channelInfo['summary'])." +"; + if (isset($channelInfo['suggestedalias'])) { + $ret .= ' ' . $channelInfo['suggestedalias'] . "\n"; + } + if (isset($channelInfo['validatepackage'])) { + $ret .= ' ' . + htmlspecialchars($channelInfo['validatepackage']['_content']) . + "\n"; + } + $ret .= " \n"; + $ret .= ' _makeRestXml($channelInfo['servers']['primary']['rest'], ' '); + } + $ret .= " \n"; + if (isset($channelInfo['servers']['mirror'])) { + $ret .= $this->_makeMirrorsXml($channelInfo); + } + $ret .= " \n"; + $ret .= ""; + return str_replace("\r", "\n", str_replace("\r\n", "\n", $ret)); + } + + /** + * Generate the tag + * @access private + */ + function _makeRestXml($info, $indent) + { + $ret = $indent . "\n"; + if (isset($info['baseurl']) && !isset($info['baseurl'][0])) { + $info['baseurl'] = array($info['baseurl']); + } + + if (isset($info['baseurl'])) { + foreach ($info['baseurl'] as $url) { + $ret .= "$indent \n"; + } + } + $ret .= $indent . "\n"; + return $ret; + } + + /** + * Generate the tag + * @access private + */ + function _makeMirrorsXml($channelInfo) + { + $ret = ""; + if (!isset($channelInfo['servers']['mirror'][0])) { + $channelInfo['servers']['mirror'] = array($channelInfo['servers']['mirror']); + } + foreach ($channelInfo['servers']['mirror'] as $mirror) { + $ret .= ' _makeRestXml($mirror['rest'], ' '); + } + $ret .= " \n"; + } else { + $ret .= "/>\n"; + } + } + return $ret; + } + + /** + * Generate the tag + * @access private + */ + function _makeFunctionsXml($functions, $indent, $rest = false) + { + $ret = ''; + if (!isset($functions[0])) { + $functions = array($functions); + } + foreach ($functions as $function) { + $ret .= "$indent\n"; + } + return $ret; + } + + /** + * Validation error. Also marks the object contents as invalid + * @param error code + * @param array error information + * @access private + */ + function _validateError($code, $params = array()) + { + $this->_stack->push($code, 'error', $params); + $this->_isValid = false; + } + + /** + * Validation warning. Does not mark the object contents invalid. + * @param error code + * @param array error information + * @access private + */ + function _validateWarning($code, $params = array()) + { + $this->_stack->push($code, 'warning', $params); + } + + /** + * Validate parsed file. + * + * @access public + * @return boolean + */ + function validate() + { + $this->_isValid = true; + $info = $this->_channelInfo; + if (empty($info['name'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_NAME); + } elseif (!$this->validChannelServer($info['name'])) { + if ($info['name'] != '__uri') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, array('tag' => 'name', + 'name' => $info['name'])); + } + } + if (empty($info['summary'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY); + } elseif (strpos(trim($info['summary']), "\n") !== false) { + $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $info['summary'])); + } + if (isset($info['suggestedalias'])) { + if (!$this->validChannelServer($info['suggestedalias'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'suggestedalias', 'name' =>$info['suggestedalias'])); + } + } + if (isset($info['localalias'])) { + if (!$this->validChannelServer($info['localalias'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'localalias', 'name' =>$info['localalias'])); + } + } + if (isset($info['validatepackage'])) { + if (!isset($info['validatepackage']['_content'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME); + } + if (!isset($info['validatepackage']['attribs']['version'])) { + $content = isset($info['validatepackage']['_content']) ? + $info['validatepackage']['_content'] : + null; + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION, + array('package' => $content)); + } + } + + if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['port']) && + !is_numeric($info['servers']['primary']['attribs']['port'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_PORT, + array('port' => $info['servers']['primary']['attribs']['port'])); + } + + if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['ssl']) && + $info['servers']['primary']['attribs']['ssl'] != 'yes') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL, + array('ssl' => $info['servers']['primary']['attribs']['ssl'], + 'server' => $info['name'])); + } + + if (isset($info['servers']['primary']['rest']) && + isset($info['servers']['primary']['rest']['baseurl'])) { + $this->_validateFunctions('rest', $info['servers']['primary']['rest']['baseurl']); + } + if (isset($info['servers']['mirror'])) { + if ($this->_channelInfo['name'] == '__uri') { + $this->_validateError(PEAR_CHANNELFILE_URI_CANT_MIRROR); + } + if (!isset($info['servers']['mirror'][0])) { + $info['servers']['mirror'] = array($info['servers']['mirror']); + } + foreach ($info['servers']['mirror'] as $mirror) { + if (!isset($mirror['attribs']['host'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_HOST, + array('type' => 'mirror')); + } elseif (!$this->validChannelServer($mirror['attribs']['host'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_HOST, + array('server' => $mirror['attribs']['host'], 'type' => 'mirror')); + } + if (isset($mirror['attribs']['ssl']) && $mirror['attribs']['ssl'] != 'yes') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL, + array('ssl' => $info['ssl'], 'server' => $mirror['attribs']['host'])); + } + if (isset($mirror['rest'])) { + $this->_validateFunctions('rest', $mirror['rest']['baseurl'], + $mirror['attribs']['host']); + } + } + } + return $this->_isValid; + } + + /** + * @param string rest - protocol name this function applies to + * @param array the functions + * @param string the name of the parent element (mirror name, for instance) + */ + function _validateFunctions($protocol, $functions, $parent = '') + { + if (!isset($functions[0])) { + $functions = array($functions); + } + + foreach ($functions as $function) { + if (!isset($function['_content']) || empty($function['_content'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME, + array('parent' => $parent, 'protocol' => $protocol)); + } + + if ($protocol == 'rest') { + if (!isset($function['attribs']['type']) || + empty($function['attribs']['type'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE, + array('parent' => $parent, 'protocol' => $protocol)); + } + } else { + if (!isset($function['attribs']['version']) || + empty($function['attribs']['version'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION, + array('parent' => $parent, 'protocol' => $protocol)); + } + } + } + } + + /** + * Test whether a string contains a valid channel server. + * @param string $ver the package version to test + * @return bool + */ + function validChannelServer($server) + { + if ($server == '__uri') { + return true; + } + return (bool) preg_match(PEAR_CHANNELS_SERVER_PREG, $server); + } + + /** + * @return string|false + */ + function getName() + { + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + + return false; + } + + /** + * @return string|false + */ + function getServer() + { + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + + return false; + } + + /** + * @return int|80 port number to connect to + */ + function getPort($mirror = false) + { + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir['attribs']['port'])) { + return $mir['attribs']['port']; + } + + if ($this->getSSL($mirror)) { + return 443; + } + + return 80; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary']['attribs']['port'])) { + return $this->_channelInfo['servers']['primary']['attribs']['port']; + } + + if ($this->getSSL()) { + return 443; + } + + return 80; + } + + /** + * @return bool Determines whether secure sockets layer (SSL) is used to connect to this channel + */ + function getSSL($mirror = false) + { + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir['attribs']['ssl'])) { + return true; + } + + return false; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) { + return true; + } + + return false; + } + + /** + * @return string|false + */ + function getSummary() + { + if (isset($this->_channelInfo['summary'])) { + return $this->_channelInfo['summary']; + } + + return false; + } + + /** + * @param string protocol type + * @param string Mirror name + * @return array|false + */ + function getFunctions($protocol, $mirror = false) + { + if ($this->getName() == '__uri') { + return false; + } + + $function = $protocol == 'rest' ? 'baseurl' : 'function'; + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir[$protocol][$function])) { + return $mir[$protocol][$function]; + } + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary'][$protocol][$function])) { + return $this->_channelInfo['servers']['primary'][$protocol][$function]; + } + + return false; + } + + /** + * @param string Protocol type + * @param string Function name (null to return the + * first protocol of the type requested) + * @param string Mirror name, if any + * @return array + */ + function getFunction($type, $name = null, $mirror = false) + { + $protocols = $this->getFunctions($type, $mirror); + if (!$protocols) { + return false; + } + + foreach ($protocols as $protocol) { + if ($name === null) { + return $protocol; + } + + if ($protocol['_content'] != $name) { + continue; + } + + return $protocol; + } + + return false; + } + + /** + * @param string protocol type + * @param string protocol name + * @param string version + * @param string mirror name + * @return boolean + */ + function supports($type, $name = null, $mirror = false, $version = '1.0') + { + $protocols = $this->getFunctions($type, $mirror); + if (!$protocols) { + return false; + } + + foreach ($protocols as $protocol) { + if ($protocol['attribs']['version'] != $version) { + continue; + } + + if ($name === null) { + return true; + } + + if ($protocol['_content'] != $name) { + continue; + } + + return true; + } + + return false; + } + + /** + * Determines whether a channel supports Representational State Transfer (REST) protocols + * for retrieving channel information + * @param string + * @return bool + */ + function supportsREST($mirror = false) + { + if ($mirror == $this->_channelInfo['name']) { + $mirror = false; + } + + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + return isset($mir['rest']); + } + + return false; + } + + return isset($this->_channelInfo['servers']['primary']['rest']); + } + + /** + * Get the URL to access a base resource. + * + * Hyperlinks in the returned xml will be used to retrieve the proper information + * needed. This allows extreme extensibility and flexibility in implementation + * @param string Resource Type to retrieve + */ + function getBaseURL($resourceType, $mirror = false) + { + if ($mirror == $this->_channelInfo['name']) { + $mirror = false; + } + + if ($mirror) { + $mir = $this->getMirror($mirror); + if (!$mir) { + return false; + } + + $rest = $mir['rest']; + } else { + $rest = $this->_channelInfo['servers']['primary']['rest']; + } + + if (!isset($rest['baseurl'][0])) { + $rest['baseurl'] = array($rest['baseurl']); + } + + foreach ($rest['baseurl'] as $baseurl) { + if (strtolower($baseurl['attribs']['type']) == strtolower($resourceType)) { + return $baseurl['_content']; + } + } + + return false; + } + + /** + * Since REST does not implement RPC, provide this as a logical wrapper around + * resetFunctions for REST + * @param string|false mirror name, if any + */ + function resetREST($mirror = false) + { + return $this->resetFunctions('rest', $mirror); + } + + /** + * Empty all protocol definitions + * @param string protocol type + * @param string|false mirror name, if any + */ + function resetFunctions($type, $mirror = false) + { + if ($mirror) { + if (isset($this->_channelInfo['servers']['mirror'])) { + $mirrors = $this->_channelInfo['servers']['mirror']; + if (!isset($mirrors[0])) { + $mirrors = array($mirrors); + } + + foreach ($mirrors as $i => $mir) { + if ($mir['attribs']['host'] == $mirror) { + if (isset($this->_channelInfo['servers']['mirror'][$i][$type])) { + unset($this->_channelInfo['servers']['mirror'][$i][$type]); + } + + return true; + } + } + + return false; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary'][$type])) { + unset($this->_channelInfo['servers']['primary'][$type]); + } + + return true; + } + + /** + * Set a channel's protocols to the protocols supported by pearweb + */ + function setDefaultPEARProtocols($version = '1.0', $mirror = false) + { + switch ($version) { + case '1.0' : + $this->resetREST($mirror); + + if (!isset($this->_channelInfo['servers'])) { + $this->_channelInfo['servers'] = array('primary' => + array('rest' => array())); + } elseif (!isset($this->_channelInfo['servers']['primary'])) { + $this->_channelInfo['servers']['primary'] = array('rest' => array()); + } + + return true; + break; + default : + return false; + break; + } + } + + /** + * @return array + */ + function getMirrors() + { + if (isset($this->_channelInfo['servers']['mirror'])) { + $mirrors = $this->_channelInfo['servers']['mirror']; + if (!isset($mirrors[0])) { + $mirrors = array($mirrors); + } + + return $mirrors; + } + + return array(); + } + + /** + * Get the unserialized XML representing a mirror + * @return array|false + */ + function getMirror($server) + { + foreach ($this->getMirrors() as $mirror) { + if ($mirror['attribs']['host'] == $server) { + return $mirror; + } + } + + return false; + } + + /** + * @param string + * @return string|false + * @error PEAR_CHANNELFILE_ERROR_NO_NAME + * @error PEAR_CHANNELFILE_ERROR_INVALID_NAME + */ + function setName($name) + { + return $this->setServer($name); + } + + /** + * Set the socket number (port) that is used to connect to this channel + * @param integer + * @param string|false name of the mirror server, or false for the primary + */ + function setPort($port, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $this->_channelInfo['servers']['mirror'][$i]['attribs']['port'] = $port; + return true; + } + } + + return false; + } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $this->_channelInfo['servers']['mirror']['attribs']['port'] = $port; + $this->_isValid = false; + return true; + } + } + + $this->_channelInfo['servers']['primary']['attribs']['port'] = $port; + $this->_isValid = false; + return true; + } + + /** + * Set the socket number (port) that is used to connect to this channel + * @param bool Determines whether to turn on SSL support or turn it off + * @param string|false name of the mirror server, or false for the primary + */ + function setSSL($ssl = true, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + if (!$ssl) { + if (isset($this->_channelInfo['servers']['mirror'][$i] + ['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl']); + } + } else { + $this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl'] = 'yes'; + } + + return true; + } + } + + return false; + } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + if (!$ssl) { + if (isset($this->_channelInfo['servers']['mirror']['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['mirror']['attribs']['ssl']); + } + } else { + $this->_channelInfo['servers']['mirror']['attribs']['ssl'] = 'yes'; + } + + $this->_isValid = false; + return true; + } + } + + if ($ssl) { + $this->_channelInfo['servers']['primary']['attribs']['ssl'] = 'yes'; + } else { + if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['primary']['attribs']['ssl']); + } + } + + $this->_isValid = false; + return true; + } + + /** + * @param string + * @return string|false + * @error PEAR_CHANNELFILE_ERROR_NO_SERVER + * @error PEAR_CHANNELFILE_ERROR_INVALID_SERVER + */ + function setServer($server, $mirror = false) + { + if (empty($server)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SERVER); + return false; + } elseif (!$this->validChannelServer($server)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'name', 'name' => $server)); + return false; + } + + if ($mirror) { + $found = false; + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $found = true; + break; + } + } + + if (!$found) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $this->_channelInfo['mirror'][$i]['attribs']['host'] = $server; + return true; + } + + $this->_channelInfo['name'] = $server; + return true; + } + + /** + * @param string + * @return boolean success + * @error PEAR_CHANNELFILE_ERROR_NO_SUMMARY + * @warning PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY + */ + function setSummary($summary) + { + if (empty($summary)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY); + return false; + } elseif (strpos(trim($summary), "\n") !== false) { + $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $summary)); + } + + $this->_channelInfo['summary'] = $summary; + return true; + } + + /** + * @param string + * @param boolean determines whether the alias is in channel.xml or local + * @return boolean success + */ + function setAlias($alias, $local = false) + { + if (!$this->validChannelServer($alias)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'suggestedalias', 'name' => $alias)); + return false; + } + + if ($local) { + $this->_channelInfo['localalias'] = $alias; + } else { + $this->_channelInfo['suggestedalias'] = $alias; + } + + return true; + } + + /** + * @return string + */ + function getAlias() + { + if (isset($this->_channelInfo['localalias'])) { + return $this->_channelInfo['localalias']; + } + if (isset($this->_channelInfo['suggestedalias'])) { + return $this->_channelInfo['suggestedalias']; + } + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + return ''; + } + + /** + * Set the package validation object if it differs from PEAR's default + * The class must be includeable via changing _ in the classname to path separator, + * but no checking of this is made. + * @param string|false pass in false to reset to the default packagename regex + * @return boolean success + */ + function setValidationPackage($validateclass, $version) + { + if (empty($validateclass)) { + unset($this->_channelInfo['validatepackage']); + } + $this->_channelInfo['validatepackage'] = array('_content' => $validateclass); + $this->_channelInfo['validatepackage']['attribs'] = array('version' => $version); + } + + /** + * Add a protocol to the provides section + * @param string protocol type + * @param string protocol version + * @param string protocol name, if any + * @param string mirror name, if this is a mirror's protocol + * @return bool + */ + function addFunction($type, $version, $name = '', $mirror = false) + { + if ($mirror) { + return $this->addMirrorFunction($mirror, $type, $version, $name); + } + + $set = array('attribs' => array('version' => $version), '_content' => $name); + if (!isset($this->_channelInfo['servers']['primary'][$type]['function'])) { + if (!isset($this->_channelInfo['servers'])) { + $this->_channelInfo['servers'] = array('primary' => + array($type => array())); + } elseif (!isset($this->_channelInfo['servers']['primary'])) { + $this->_channelInfo['servers']['primary'] = array($type => array()); + } + + $this->_channelInfo['servers']['primary'][$type]['function'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($this->_channelInfo['servers']['primary'][$type]['function'][0])) { + $this->_channelInfo['servers']['primary'][$type]['function'] = array( + $this->_channelInfo['servers']['primary'][$type]['function']); + } + + $this->_channelInfo['servers']['primary'][$type]['function'][] = $set; + return true; + } + /** + * Add a protocol to a mirror's provides section + * @param string mirror name (server) + * @param string protocol type + * @param string protocol version + * @param string protocol name, if any + */ + function addMirrorFunction($mirror, $type, $version, $name = '') + { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $setmirror = false; + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $setmirror = &$this->_channelInfo['servers']['mirror'][$i]; + break; + } + } + } else { + if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $setmirror = &$this->_channelInfo['servers']['mirror']; + } + } + + if (!$setmirror) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $set = array('attribs' => array('version' => $version), '_content' => $name); + if (!isset($setmirror[$type]['function'])) { + $setmirror[$type]['function'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($setmirror[$type]['function'][0])) { + $setmirror[$type]['function'] = array($setmirror[$type]['function']); + } + + $setmirror[$type]['function'][] = $set; + $this->_isValid = false; + return true; + } + + /** + * @param string Resource Type this url links to + * @param string URL + * @param string|false mirror name, if this is not a primary server REST base URL + */ + function setBaseURL($resourceType, $url, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $setmirror = false; + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $setmirror = &$this->_channelInfo['servers']['mirror'][$i]; + break; + } + } + } else { + if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $setmirror = &$this->_channelInfo['servers']['mirror']; + } + } + } else { + $setmirror = &$this->_channelInfo['servers']['primary']; + } + + $set = array('attribs' => array('type' => $resourceType), '_content' => $url); + if (!isset($setmirror['rest'])) { + $setmirror['rest'] = array(); + } + + if (!isset($setmirror['rest']['baseurl'])) { + $setmirror['rest']['baseurl'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($setmirror['rest']['baseurl'][0])) { + $setmirror['rest']['baseurl'] = array($setmirror['rest']['baseurl']); + } + + foreach ($setmirror['rest']['baseurl'] as $i => $url) { + if ($url['attribs']['type'] == $resourceType) { + $this->_isValid = false; + $setmirror['rest']['baseurl'][$i] = $set; + return true; + } + } + + $setmirror['rest']['baseurl'][] = $set; + $this->_isValid = false; + return true; + } + + /** + * @param string mirror server + * @param int mirror http port + * @return boolean + */ + function addMirror($server, $port = null) + { + if ($this->_channelInfo['name'] == '__uri') { + return false; // the __uri channel cannot have mirrors by definition + } + + $set = array('attribs' => array('host' => $server)); + if (is_numeric($port)) { + $set['attribs']['port'] = $port; + } + + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_channelInfo['servers']['mirror'] = $set; + return true; + } + + if (!isset($this->_channelInfo['servers']['mirror'][0])) { + $this->_channelInfo['servers']['mirror'] = + array($this->_channelInfo['servers']['mirror']); + } + + $this->_channelInfo['servers']['mirror'][] = $set; + return true; + } + + /** + * Retrieve the name of the validation package for this channel + * @return string|false + */ + function getValidationPackage() + { + if (!$this->_isValid && !$this->validate()) { + return false; + } + + if (!isset($this->_channelInfo['validatepackage'])) { + return array('attribs' => array('version' => 'default'), + '_content' => 'PEAR_Validate'); + } + + return $this->_channelInfo['validatepackage']; + } + + /** + * Retrieve the object that can be used for custom validation + * @param string|false the name of the package to validate. If the package is + * the channel validation package, PEAR_Validate is returned + * @return PEAR_Validate|false false is returned if the validation package + * cannot be located + */ + function &getValidationObject($package = false) + { + if (!class_exists('PEAR_Validate')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Validate.php'; + } + + if (!$this->_isValid) { + if (!$this->validate()) { + $a = false; + return $a; + } + } + + if (isset($this->_channelInfo['validatepackage'])) { + if ($package == $this->_channelInfo['validatepackage']) { + // channel validation packages are always validated by PEAR_Validate + $val = &new PEAR_Validate; + return $val; + } + + if (!class_exists(str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']))) { + if ($this->isIncludeable(str_replace('_', '/', + $this->_channelInfo['validatepackage']['_content']) . '.php')) { + include_once 'phar://go-pear.phar/' . str_replace('_', '/', + $this->_channelInfo['validatepackage']['_content']) . '.php'; + $vclass = str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']); + $val = &new $vclass; + } else { + $a = false; + return $a; + } + } else { + $vclass = str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']); + $val = &new $vclass; + } + } else { + $val = &new PEAR_Validate; + } + + return $val; + } + + function isIncludeable($path) + { + $possibilities = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($possibilities as $dir) { + if (file_exists($dir . DIRECTORY_SEPARATOR . $path) + && is_readable($dir . DIRECTORY_SEPARATOR . $path)) { + return true; + } + } + + return false; + } + + /** + * This function is used by the channel updater and retrieves a value set by + * the registry, or the current time if it has not been set + * @return string + */ + function lastModified() + { + if (isset($this->_channelInfo['_lastmodified'])) { + return $this->_channelInfo['_lastmodified']; + } + + return time(); + } +} + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Parser.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base xml parser class + */ +require_once 'phar://go-pear.phar/' . 'PEAR/XMLParser.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/ChannelFile.php'; +/** + * Parser for channel.xml + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_ChannelFile_Parser extends PEAR_XMLParser +{ + var $_config; + var $_logger; + var $_registry; + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + function parse($data, $file) + { + if (PEAR::isError($err = parent::parse($data, $file))) { + return $err; + } + + $ret = new PEAR_ChannelFile; + $ret->setConfig($this->_config); + if (isset($this->_logger)) { + $ret->setLogger($this->_logger); + } + + $ret->fromArray($this->_unserializedData); + // make sure the filelist is in the easy to read format needed + $ret->flattenFilelist(); + $ret->setPackagefile($file, $archive); + return $ret; + } +} + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Command.php 286494 2009-07-29 06:57:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * Needed for error handling + */ +require_once 'phar://go-pear.phar/' . 'PEAR.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/Frontend.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/XMLParser.php'; + +/** + * List of commands and what classes they are implemented in. + * @var array command => implementing class + */ +$GLOBALS['_PEAR_Command_commandlist'] = array(); + +/** + * List of commands and their descriptions + * @var array command => description + */ +$GLOBALS['_PEAR_Command_commanddesc'] = array(); + +/** + * List of shortcuts to common commands. + * @var array shortcut => command + */ +$GLOBALS['_PEAR_Command_shortcuts'] = array(); + +/** + * Array of command objects + * @var array class => object + */ +$GLOBALS['_PEAR_Command_objects'] = array(); + +/** + * PEAR command class, a simple factory class for administrative + * commands. + * + * How to implement command classes: + * + * - The class must be called PEAR_Command_Nnn, installed in the + * "PEAR/Common" subdir, with a method called getCommands() that + * returns an array of the commands implemented by the class (see + * PEAR/Command/Install.php for an example). + * + * - The class must implement a run() function that is called with three + * params: + * + * (string) command name + * (array) assoc array with options, freely defined by each + * command, for example: + * array('force' => true) + * (array) list of the other parameters + * + * The run() function returns a PEAR_CommandResponse object. Use + * these methods to get information: + * + * int getStatus() Returns PEAR_COMMAND_(SUCCESS|FAILURE|PARTIAL) + * *_PARTIAL means that you need to issue at least + * one more command to complete the operation + * (used for example for validation steps). + * + * string getMessage() Returns a message for the user. Remember, + * no HTML or other interface-specific markup. + * + * If something unexpected happens, run() returns a PEAR error. + * + * - DON'T OUTPUT ANYTHING! Return text for output instead. + * + * - DON'T USE HTML! The text you return will be used from both Gtk, + * web and command-line interfaces, so for now, keep everything to + * plain text. + * + * - DON'T USE EXIT OR DIE! Always use pear errors. From static + * classes do PEAR::raiseError(), from other classes do + * $this->raiseError(). + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command +{ + // {{{ factory() + + /** + * Get the right object for executing a command. + * + * @param string $command The name of the command + * @param object $config Instance of PEAR_Config object + * + * @return object the command object or a PEAR error + * + * @access public + * @static + */ + function &factory($command, &$config) + { + if (empty($GLOBALS['_PEAR_Command_commandlist'])) { + PEAR_Command::registerCommands(); + } + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } + if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { + $a = PEAR::raiseError("unknown command `$command'"); + return $a; + } + $class = $GLOBALS['_PEAR_Command_commandlist'][$command]; + if (!class_exists($class)) { + require_once $GLOBALS['_PEAR_Command_objects'][$class]; + } + if (!class_exists($class)) { + $a = PEAR::raiseError("unknown command `$command'"); + return $a; + } + $ui =& PEAR_Command::getFrontendObject(); + $obj = &new $class($ui, $config); + return $obj; + } + + // }}} + // {{{ & getObject() + function &getObject($command) + { + $class = $GLOBALS['_PEAR_Command_commandlist'][$command]; + if (!class_exists($class)) { + require_once $GLOBALS['_PEAR_Command_objects'][$class]; + } + if (!class_exists($class)) { + return PEAR::raiseError("unknown command `$command'"); + } + $ui =& PEAR_Command::getFrontendObject(); + $config = &PEAR_Config::singleton(); + $obj = &new $class($ui, $config); + return $obj; + } + + // }}} + // {{{ & getFrontendObject() + + /** + * Get instance of frontend object. + * + * @return object|PEAR_Error + * @static + */ + function &getFrontendObject() + { + $a = &PEAR_Frontend::singleton(); + return $a; + } + + // }}} + // {{{ & setFrontendClass() + + /** + * Load current frontend class. + * + * @param string $uiclass Name of class implementing the frontend + * + * @return object the frontend object, or a PEAR error + * @static + */ + function &setFrontendClass($uiclass) + { + $a = &PEAR_Frontend::setFrontendClass($uiclass); + return $a; + } + + // }}} + // {{{ setFrontendType() + + /** + * Set current frontend. + * + * @param string $uitype Name of the frontend type (for example "CLI") + * + * @return object the frontend object, or a PEAR error + * @static + */ + function setFrontendType($uitype) + { + $uiclass = 'PEAR_Frontend_' . $uitype; + return PEAR_Command::setFrontendClass($uiclass); + } + + // }}} + // {{{ registerCommands() + + /** + * Scan through the Command directory looking for classes + * and see what commands they implement. + * + * @param bool (optional) if FALSE (default), the new list of + * commands should replace the current one. If TRUE, + * new entries will be merged with old. + * + * @param string (optional) where (what directory) to look for + * classes, defaults to the Command subdirectory of + * the directory from where this file (__FILE__) is + * included. + * + * @return bool TRUE on success, a PEAR error on failure + * + * @access public + * @static + */ + function registerCommands($merge = false, $dir = null) + { + $parser = new PEAR_XMLParser; + if ($dir === null) { + $dir = dirname(__FILE__) . '/Command'; + } + if (!is_dir($dir)) { + return PEAR::raiseError("registerCommands: opendir($dir) '$dir' does not exist or is not a directory"); + } + $dp = @opendir($dir); + if (empty($dp)) { + return PEAR::raiseError("registerCommands: opendir($dir) failed"); + } + if (!$merge) { + $GLOBALS['_PEAR_Command_commandlist'] = array(); + } + + while ($file = readdir($dp)) { + if ($file{0} == '.' || substr($file, -4) != '.xml') { + continue; + } + + $f = substr($file, 0, -4); + $class = "PEAR_Command_" . $f; + // List of commands + if (empty($GLOBALS['_PEAR_Command_objects'][$class])) { + $GLOBALS['_PEAR_Command_objects'][$class] = "$dir/" . $f . '.php'; + } + + $parser->parse(file_get_contents("$dir/$file")); + $implements = $parser->getData(); + foreach ($implements as $command => $desc) { + if ($command == 'attribs') { + continue; + } + + if (isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { + return PEAR::raiseError('Command "' . $command . '" already registered in ' . + 'class "' . $GLOBALS['_PEAR_Command_commandlist'][$command] . '"'); + } + + $GLOBALS['_PEAR_Command_commandlist'][$command] = $class; + $GLOBALS['_PEAR_Command_commanddesc'][$command] = $desc['summary']; + if (isset($desc['shortcut'])) { + $shortcut = $desc['shortcut']; + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$shortcut])) { + return PEAR::raiseError('Command shortcut "' . $shortcut . '" already ' . + 'registered to command "' . $command . '" in class "' . + $GLOBALS['_PEAR_Command_commandlist'][$command] . '"'); + } + $GLOBALS['_PEAR_Command_shortcuts'][$shortcut] = $command; + } + + if (isset($desc['options']) && $desc['options']) { + foreach ($desc['options'] as $oname => $option) { + if (isset($option['shortopt']) && strlen($option['shortopt']) > 1) { + return PEAR::raiseError('Option "' . $oname . '" short option "' . + $option['shortopt'] . '" must be ' . + 'only 1 character in Command "' . $command . '" in class "' . + $class . '"'); + } + } + } + } + } + + ksort($GLOBALS['_PEAR_Command_shortcuts']); + ksort($GLOBALS['_PEAR_Command_commandlist']); + @closedir($dp); + return true; + } + + // }}} + // {{{ getCommands() + + /** + * Get the list of currently supported commands, and what + * classes implement them. + * + * @return array command => implementing class + * + * @access public + * @static + */ + function getCommands() + { + if (empty($GLOBALS['_PEAR_Command_commandlist'])) { + PEAR_Command::registerCommands(); + } + return $GLOBALS['_PEAR_Command_commandlist']; + } + + // }}} + // {{{ getShortcuts() + + /** + * Get the list of command shortcuts. + * + * @return array shortcut => command + * + * @access public + * @static + */ + function getShortcuts() + { + if (empty($GLOBALS['_PEAR_Command_shortcuts'])) { + PEAR_Command::registerCommands(); + } + return $GLOBALS['_PEAR_Command_shortcuts']; + } + + // }}} + // {{{ getGetoptArgs() + + /** + * Compiles arguments for getopt. + * + * @param string $command command to get optstring for + * @param string $short_args (reference) short getopt format + * @param array $long_args (reference) long getopt format + * + * @return void + * + * @access public + * @static + */ + function getGetoptArgs($command, &$short_args, &$long_args) + { + if (empty($GLOBALS['_PEAR_Command_commandlist'])) { + PEAR_Command::registerCommands(); + } + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } + if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { + return null; + } + $obj = &PEAR_Command::getObject($command); + return $obj->getGetoptArgs($command, $short_args, $long_args); + } + + // }}} + // {{{ getDescription() + + /** + * Get description for a command. + * + * @param string $command Name of the command + * + * @return string command description + * + * @access public + * @static + */ + function getDescription($command) + { + if (!isset($GLOBALS['_PEAR_Command_commanddesc'][$command])) { + return null; + } + return $GLOBALS['_PEAR_Command_commanddesc'][$command]; + } + + // }}} + // {{{ getHelp() + + /** + * Get help for command. + * + * @param string $command Name of the command to return help for + * + * @access public + * @static + */ + function getHelp($command) + { + $cmds = PEAR_Command::getCommands(); + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } + if (isset($cmds[$command])) { + $obj = &PEAR_Command::getObject($command); + return $obj->getHelp($command); + } + return false; + } + // }}} +} + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'phar://go-pear.phar/' . 'PEAR.php'; + +/** + * PEAR commands base class + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Common extends PEAR +{ + /** + * PEAR_Config object used to pass user system and configuration + * on when executing commands + * + * @var PEAR_Config + */ + var $config; + /** + * @var PEAR_Registry + * @access protected + */ + var $_registry; + + /** + * User Interface object, for all interaction with the user. + * @var object + */ + var $ui; + + var $_deps_rel_trans = array( + 'lt' => '<', + 'le' => '<=', + 'eq' => '=', + 'ne' => '!=', + 'gt' => '>', + 'ge' => '>=', + 'has' => '==' + ); + + var $_deps_type_trans = array( + 'pkg' => 'package', + 'ext' => 'extension', + 'php' => 'PHP', + 'prog' => 'external program', + 'ldlib' => 'external library for linking', + 'rtlib' => 'external runtime library', + 'os' => 'operating system', + 'websrv' => 'web server', + 'sapi' => 'SAPI backend' + ); + + /** + * PEAR_Command_Common constructor. + * + * @access public + */ + function PEAR_Command_Common(&$ui, &$config) + { + parent::PEAR(); + $this->config = &$config; + $this->ui = &$ui; + } + + /** + * Return a list of all the commands defined by this class. + * @return array list of commands + * @access public + */ + function getCommands() + { + $ret = array(); + foreach (array_keys($this->commands) as $command) { + $ret[$command] = $this->commands[$command]['summary']; + } + + return $ret; + } + + /** + * Return a list of all the command shortcuts defined by this class. + * @return array shortcut => command + * @access public + */ + function getShortcuts() + { + $ret = array(); + foreach (array_keys($this->commands) as $command) { + if (isset($this->commands[$command]['shortcut'])) { + $ret[$this->commands[$command]['shortcut']] = $command; + } + } + + return $ret; + } + + function getOptions($command) + { + $shortcuts = $this->getShortcuts(); + if (isset($shortcuts[$command])) { + $command = $shortcuts[$command]; + } + + if (isset($this->commands[$command]) && + isset($this->commands[$command]['options'])) { + return $this->commands[$command]['options']; + } + + return null; + } + + function getGetoptArgs($command, &$short_args, &$long_args) + { + $short_args = ''; + $long_args = array(); + if (empty($this->commands[$command]) || empty($this->commands[$command]['options'])) { + return; + } + + reset($this->commands[$command]['options']); + while (list($option, $info) = each($this->commands[$command]['options'])) { + $larg = $sarg = ''; + if (isset($info['arg'])) { + if ($info['arg']{0} == '(') { + $larg = '=='; + $sarg = '::'; + $arg = substr($info['arg'], 1, -1); + } else { + $larg = '='; + $sarg = ':'; + $arg = $info['arg']; + } + } + + if (isset($info['shortopt'])) { + $short_args .= $info['shortopt'] . $sarg; + } + + $long_args[] = $option . $larg; + } + } + + /** + * Returns the help message for the given command + * + * @param string $command The command + * @return mixed A fail string if the command does not have help or + * a two elements array containing [0]=>help string, + * [1]=> help string for the accepted cmd args + */ + function getHelp($command) + { + $config = &PEAR_Config::singleton(); + if (!isset($this->commands[$command])) { + return "No such command \"$command\""; + } + + $help = null; + if (isset($this->commands[$command]['doc'])) { + $help = $this->commands[$command]['doc']; + } + + if (empty($help)) { + // XXX (cox) Fallback to summary if there is no doc (show both?) + if (!isset($this->commands[$command]['summary'])) { + return "No help for command \"$command\""; + } + $help = $this->commands[$command]['summary']; + } + + if (preg_match_all('/{config\s+([^\}]+)}/e', $help, $matches)) { + foreach($matches[0] as $k => $v) { + $help = preg_replace("/$v/", $config->get($matches[1][$k]), $help); + } + } + + return array($help, $this->getHelpArgs($command)); + } + + /** + * Returns the help for the accepted arguments of a command + * + * @param string $command + * @return string The help string + */ + function getHelpArgs($command) + { + if (isset($this->commands[$command]['options']) && + count($this->commands[$command]['options'])) + { + $help = "Options:\n"; + foreach ($this->commands[$command]['options'] as $k => $v) { + if (isset($v['arg'])) { + if ($v['arg'][0] == '(') { + $arg = substr($v['arg'], 1, -1); + $sapp = " [$arg]"; + $lapp = "[=$arg]"; + } else { + $sapp = " $v[arg]"; + $lapp = "=$v[arg]"; + } + } else { + $sapp = $lapp = ""; + } + + if (isset($v['shortopt'])) { + $s = $v['shortopt']; + $help .= " -$s$sapp, --$k$lapp\n"; + } else { + $help .= " --$k$lapp\n"; + } + + $p = " "; + $doc = rtrim(str_replace("\n", "\n$p", $v['doc'])); + $help .= " $doc\n"; + } + + return $help; + } + + return null; + } + + function run($command, $options, $params) + { + if (empty($this->commands[$command]['function'])) { + // look for shortcuts + foreach (array_keys($this->commands) as $cmd) { + if (isset($this->commands[$cmd]['shortcut']) && $this->commands[$cmd]['shortcut'] == $command) { + if (empty($this->commands[$cmd]['function'])) { + return $this->raiseError("unknown command `$command'"); + } else { + $func = $this->commands[$cmd]['function']; + } + $command = $cmd; + + //$command = $this->commands[$cmd]['function']; + break; + } + } + } else { + $func = $this->commands[$command]['function']; + } + + return $this->$func($command, $options, $params); + } +} + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Install.php 287477 2009-08-19 14:19:43Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Command/Common.php'; + +/** + * PEAR commands for installation or deinstallation/upgrading of + * packages. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Install extends PEAR_Command_Common +{ + // {{{ properties + + var $commands = array( + 'install' => array( + 'summary' => 'Install Package', + 'function' => 'doInstall', + 'shortcut' => 'i', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'will overwrite newer installed packages', + ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, install anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as installed', + ), + 'soft' => array( + 'shortopt' => 's', + 'doc' => 'soft install, fail silently, or upgrade if already installed', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM', + ), + 'packagingroot' => array( + 'shortopt' => 'P', + 'arg' => 'DIR', + 'doc' => 'root directory used when packaging files, like RPM packaging', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'onlyreqdeps' => array( + 'shortopt' => 'o', + 'doc' => 'install all required dependencies', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to download any urls or contact channels', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), + ), + 'doc' => '[channel/] ... +Installs one or more PEAR packages. You can specify a package to +install in four ways: + +"Package-1.0.tgz" : installs from a local file + +"http://example.com/Package-1.0.tgz" : installs from +anywhere on the net. + +"package.xml" : installs the package described in +package.xml. Useful for testing, or for wrapping a PEAR package in +another package manager such as RPM. + +"Package[-version/state][.tar]" : queries your default channel\'s server +({config master_server}) and downloads the newest package with +the preferred quality/state ({config preferred_state}). + +To retrieve Package version 1.1, use "Package-1.1," to retrieve +Package state beta, use "Package-beta." To retrieve an uncompressed +file, append .tar (make sure there is no file by the same name first) + +To download a package from another channel, prefix with the channel name like +"channel/Package" + +More than one package may be specified at once. It is ok to mix these +four ways of specifying packages. +'), + 'upgrade' => array( + 'summary' => 'Upgrade Package', + 'function' => 'doInstall', + 'shortcut' => 'up', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'overwrite newer installed packages', + ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'onlyreqdeps' => array( + 'shortopt' => 'o', + 'doc' => 'install all required dependencies', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to download any urls or contact channels', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), + ), + 'doc' => ' ... +Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. +'), + 'upgrade-all' => array( + 'summary' => 'Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters]', + 'function' => 'doUpgradeAll', + 'shortcut' => 'ua', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'loose' => array( + 'doc' => 'do not check for recommended dependency version', + ), + ), + 'doc' => ' +WARNING: This function is deprecated in favor of using the upgrade command with no params + +Upgrades all packages that have a newer release available. Upgrades are +done only if there is a release available of the state specified in +"preferred_state" (currently {config preferred_state}), or a state considered +more stable. +'), + 'uninstall' => array( + 'summary' => 'Un-install Package', + 'function' => 'doUninstall', + 'shortcut' => 'un', + 'options' => array( + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, uninstall anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not remove files, only register the packages as not installed', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to uninstall remotely', + ), + ), + 'doc' => '[channel/] ... +Uninstalls one or more PEAR packages. More than one package may be +specified at once. Prefix with channel name to uninstall from a +channel not in your default channel ({config default_channel}) +'), + 'bundle' => array( + 'summary' => 'Unpacks a Pecl Package', + 'function' => 'doBundle', + 'shortcut' => 'bun', + 'options' => array( + 'destination' => array( + 'shortopt' => 'd', + 'arg' => 'DIR', + 'doc' => 'Optional destination directory for unpacking (defaults to current path or "ext" if exists)', + ), + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'Force the unpacking even if there were errors in the package', + ), + ), + 'doc' => ' +Unpacks a Pecl Package into the selected location. It will download the +package if needed. +'), + 'run-scripts' => array( + 'summary' => 'Run Post-Install Scripts bundled with a package', + 'function' => 'doRunScripts', + 'shortcut' => 'rs', + 'options' => array( + ), + 'doc' => ' +Run post-installation scripts in package , if any exist. +'), + ); + + // }}} + // {{{ constructor + + /** + * PEAR_Command_Install constructor. + * + * @access public + */ + function PEAR_Command_Install(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + // }}} + + /** + * For unit testing purposes + */ + function &getDownloader(&$ui, $options, &$config) + { + if (!class_exists('PEAR_Downloader')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Downloader.php'; + } + $a = &new PEAR_Downloader($ui, $options, $config); + return $a; + } + + /** + * For unit testing purposes + */ + function &getInstaller(&$ui) + { + if (!class_exists('PEAR_Installer')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Installer.php'; + } + $a = &new PEAR_Installer($ui); + return $a; + } + + function enableExtension($binaries, $type) + { + if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) { + return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location'); + } + $ini = $this->_parseIni($phpini); + if (PEAR::isError($ini)) { + return $ini; + } + $line = 0; + if ($type == 'extsrc' || $type == 'extbin') { + $search = 'extensions'; + $enable = 'extension'; + } else { + $search = 'zend_extensions'; + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('Thread Safety.+enabled', $info) ? '_ts' : ''; + $enable = 'zend_extension' . $debug . $ts; + } + foreach ($ini[$search] as $line => $extension) { + if (in_array($extension, $binaries, true) || in_array( + $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) { + // already enabled - assume if one is, all are + return true; + } + } + if ($line) { + $newini = array_slice($ini['all'], 0, $line); + } else { + $newini = array(); + } + foreach ($binaries as $binary) { + if ($ini['extension_dir']) { + $binary = basename($binary); + } + $newini[] = $enable . '="' . $binary . '"' . (OS_UNIX ? "\n" : "\r\n"); + } + $newini = array_merge($newini, array_slice($ini['all'], $line)); + $fp = @fopen($phpini, 'wb'); + if (!$fp) { + return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing'); + } + foreach ($newini as $line) { + fwrite($fp, $line); + } + fclose($fp); + return true; + } + + function disableExtension($binaries, $type) + { + if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) { + return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location'); + } + $ini = $this->_parseIni($phpini); + if (PEAR::isError($ini)) { + return $ini; + } + $line = 0; + if ($type == 'extsrc' || $type == 'extbin') { + $search = 'extensions'; + $enable = 'extension'; + } else { + $search = 'zend_extensions'; + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('Thread Safety.+enabled', $info) ? '_ts' : ''; + $enable = 'zend_extension' . $debug . $ts; + } + $found = false; + foreach ($ini[$search] as $line => $extension) { + if (in_array($extension, $binaries, true) || in_array( + $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) { + $found = true; + break; + } + } + if (!$found) { + // not enabled + return true; + } + $fp = @fopen($phpini, 'wb'); + if (!$fp) { + return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing'); + } + if ($line) { + $newini = array_slice($ini['all'], 0, $line); + // delete the enable line + $newini = array_merge($newini, array_slice($ini['all'], $line + 1)); + } else { + $newini = array_slice($ini['all'], 1); + } + foreach ($newini as $line) { + fwrite($fp, $line); + } + fclose($fp); + return true; + } + + function _parseIni($filename) + { + if (!file_exists($filename)) { + return PEAR::raiseError('php.ini "' . $filename . '" does not exist'); + } + + if (filesize($filename) > 300000) { + return PEAR::raiseError('php.ini "' . $filename . '" is too large, aborting'); + } + + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; + $zend_extension_line = 'zend_extension' . $debug . $ts; + $all = @file($filename); + if (!$all) { + return PEAR::raiseError('php.ini "' . $filename .'" could not be read'); + } + $zend_extensions = $extensions = array(); + // assume this is right, but pull from the php.ini if it is found + $extension_dir = ini_get('extension_dir'); + foreach ($all as $linenum => $line) { + $line = trim($line); + if (!$line) { + continue; + } + if ($line[0] == ';') { + continue; + } + if (strtolower(substr($line, 0, 13)) == 'extension_dir') { + $line = trim(substr($line, 13)); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $extension_dir = str_replace('"', '', array_shift($x)); + continue; + } + } + if (strtolower(substr($line, 0, 9)) == 'extension') { + $line = trim(substr($line, 9)); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $extensions[$linenum] = str_replace('"', '', array_shift($x)); + continue; + } + } + if (strtolower(substr($line, 0, strlen($zend_extension_line))) == + $zend_extension_line) { + $line = trim(substr($line, strlen($zend_extension_line))); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $zend_extensions[$linenum] = str_replace('"', '', array_shift($x)); + continue; + } + } + } + return array( + 'extensions' => $extensions, + 'zend_extensions' => $zend_extensions, + 'extension_dir' => $extension_dir, + 'all' => $all, + ); + } + + // {{{ doInstall() + + function doInstall($command, $options, $params) + { + if (!class_exists('PEAR_PackageFile')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile.php'; + } + + if (isset($options['installroot']) && isset($options['packagingroot'])) { + return $this->raiseError('ERROR: cannot use both --installroot and --packagingroot'); + } + + $reg = &$this->config->getRegistry(); + $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel'); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + if (empty($this->installer)) { + $this->installer = &$this->getInstaller($this->ui); + } + + if ($command == 'upgrade' || $command == 'upgrade-all') { + // If people run the upgrade command but pass nothing, emulate a upgrade-all + if ($command == 'upgrade' && empty($params)) { + return $this->doUpgradeAll($command, $options, $params); + } + $options['upgrade'] = true; + } else { + $packages = $params; + } + + $instreg = &$reg; // instreg used to check if package is installed + if (isset($options['packagingroot']) && !isset($options['upgrade'])) { + $packrootphp_dir = $this->installer->_prependPath( + $this->config->get('php_dir', null, 'pear.php.net'), + $options['packagingroot']); + $instreg = new PEAR_Registry($packrootphp_dir); // other instreg! + + if ($this->config->get('verbose') > 2) { + $this->ui->outputData('using package root: ' . $options['packagingroot']); + } + } + + $abstractpackages = $otherpackages = array(); + // parse params + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + + foreach ($params as $param) { + if (strpos($param, 'http://') === 0) { + $otherpackages[] = $param; + continue; + } + + if (strpos($param, 'channel://') === false && @file_exists($param)) { + if (isset($options['force'])) { + $otherpackages[] = $param; + continue; + } + + $pkg = new PEAR_PackageFile($this->config); + $pf = $pkg->fromAnyFile($param, PEAR_VALIDATE_DOWNLOADING); + if (PEAR::isError($pf)) { + $otherpackages[] = $param; + continue; + } + + $exists = $reg->packageExists($pf->getPackage(), $pf->getChannel()); + $pversion = $reg->packageInfo($pf->getPackage(), 'version', $pf->getChannel()); + $version_compare = version_compare($pf->getVersion(), $pversion, '<='); + if ($exists && $version_compare) { + if ($this->config->get('verbose')) { + $this->ui->outputData('Ignoring installed package ' . + $reg->parsedPackageNameToString( + array('package' => $pf->getPackage(), + 'channel' => $pf->getChannel()), true)); + } + continue; + } + $otherpackages[] = $param; + continue; + } + + $e = $reg->parsePackageName($param, $channel); + if (PEAR::isError($e)) { + $otherpackages[] = $param; + } else { + $abstractpackages[] = $e; + } + } + PEAR::staticPopErrorHandling(); + + // if there are any local package .tgz or remote static url, we can't + // filter. The filter only works for abstract packages + if (count($abstractpackages) && !isset($options['force'])) { + // when not being forced, only do necessary upgrades/installs + if (isset($options['upgrade'])) { + $abstractpackages = $this->_filterUptodatePackages($abstractpackages, $command); + } else { + $count = count($abstractpackages); + foreach ($abstractpackages as $i => $package) { + if (isset($package['group'])) { + // do not filter out install groups + continue; + } + + if ($instreg->packageExists($package['package'], $package['channel'])) { + if ($count > 1) { + if ($this->config->get('verbose')) { + $this->ui->outputData('Ignoring installed package ' . + $reg->parsedPackageNameToString($package, true)); + } + unset($abstractpackages[$i]); + } elseif ($count === 1) { + // Lets try to upgrade it since it's already installed + $options['upgrade'] = true; + } + } + } + } + $abstractpackages = + array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages); + } elseif (count($abstractpackages)) { + $abstractpackages = + array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages); + } + + $packages = array_merge($abstractpackages, $otherpackages); + if (!count($packages)) { + $c = ''; + if (isset($options['channel'])){ + $c .= ' in channel "' . $options['channel'] . '"'; + } + $this->ui->outputData('Nothing to ' . $command . $c); + return true; + } + + $this->downloader = &$this->getDownloader($this->ui, $options, $this->config); + $errors = $downloaded = $binaries = array(); + $downloaded = &$this->downloader->download($packages); + if (PEAR::isError($downloaded)) { + return $this->raiseError($downloaded); + } + + $errors = $this->downloader->getErrorMsgs(); + if (count($errors)) { + $err = array(); + $err['data'] = array(); + foreach ($errors as $error) { + if ($error !== null) { + $err['data'][] = array($error); + } + } + + if (!empty($err['data'])) { + $err['headline'] = 'Install Errors'; + $this->ui->outputData($err); + } + + if (!count($downloaded)) { + return $this->raiseError("$command failed"); + } + } + + $data = array( + 'headline' => 'Packages that would be Installed' + ); + + if (isset($options['pretend'])) { + foreach ($downloaded as $package) { + $data['data'][] = array($reg->parsedPackageNameToString($package->getParsedPackage())); + } + $this->ui->outputData($data, 'pretend'); + return true; + } + + $this->installer->setOptions($options); + $this->installer->sortPackagesForInstall($downloaded); + if (PEAR::isError($err = $this->installer->setDownloadedPackages($downloaded))) { + $this->raiseError($err->getMessage()); + return true; + } + + $binaries = $extrainfo = array(); + foreach ($downloaded as $param) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->install($param, $options); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($info)) { + $oldinfo = $info; + $pkg = &$param->getPackageFile(); + if ($info->getCode() != PEAR_INSTALLER_NOBINARY) { + if (!($info = $pkg->installBinary($this->installer))) { + $this->ui->outputData('ERROR: ' .$oldinfo->getMessage()); + continue; + } + + // we just installed a different package than requested, + // let's change the param and info so that the rest of this works + $param = $info[0]; + $info = $info[1]; + } + } + + if (!is_array($info)) { + return $this->raiseError("$command failed"); + } + + if ($param->getPackageType() == 'extsrc' || + $param->getPackageType() == 'extbin' || + $param->getPackageType() == 'zendextsrc' || + $param->getPackageType() == 'zendextbin') { + $pkg = &$param->getPackageFile(); + if ($instbin = $pkg->getInstalledBinary()) { + $instpkg = &$instreg->getPackage($instbin, $pkg->getChannel()); + } else { + $instpkg = &$instreg->getPackage($pkg->getPackage(), $pkg->getChannel()); + } + + foreach ($instpkg->getFilelist() as $name => $atts) { + $pinfo = pathinfo($atts['installed_as']); + if (!isset($pinfo['extension']) || + in_array($pinfo['extension'], array('c', 'h'))) { + continue; // make sure we don't match php_blah.h + } + + if ((strpos($pinfo['basename'], 'php_') === 0 && + $pinfo['extension'] == 'dll') || + // most unices + $pinfo['extension'] == 'so' || + // hp-ux + $pinfo['extension'] == 'sl') { + $binaries[] = array($atts['installed_as'], $pinfo); + break; + } + } + + if (count($binaries)) { + foreach ($binaries as $pinfo) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->enableExtension(array($pinfo[0]), $param->getPackageType()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($ret)) { + $extrainfo[] = $ret->getMessage(); + if ($param->getPackageType() == 'extsrc' || + $param->getPackageType() == 'extbin') { + $exttype = 'extension'; + } else { + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('Thread Safety.+enabled', $info) ? '_ts' : ''; + $exttype = 'zend_extension' . $debug . $ts; + } + $extrainfo[] = 'You should add "' . $exttype . '=' . + $pinfo[1]['basename'] . '" to php.ini'; + } else { + $extrainfo[] = 'Extension ' . $instpkg->getProvidesExtension() . + ' enabled in php.ini'; + } + } + } + } + + if ($this->config->get('verbose') > 0) { + $chan = $param->getChannel(); + $label = $reg->parsedPackageNameToString( + array( + 'channel' => $chan, + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + )); + $out = array('data' => "$command ok: $label"); + if (isset($info['release_warnings'])) { + $out['release_warnings'] = $info['release_warnings']; + } + $this->ui->outputData($out, $command); + + if (!isset($options['register-only']) && !isset($options['offline'])) { + if ($this->config->isDefinedLayer('ftp')) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->ftpInstall($param); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($info)) { + $this->ui->outputData($info->getMessage()); + $this->ui->outputData("remote install failed: $label"); + } else { + $this->ui->outputData("remote install ok: $label"); + } + } + } + } + + $deps = $param->getDeps(); + if ($deps) { + if (isset($deps['group'])) { + $groups = $deps['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + + foreach ($groups as $group) { + if ($group['attribs']['name'] == 'default') { + // default group is always installed, unless the user + // explicitly chooses to install another group + continue; + } + $extrainfo[] = $param->getPackage() . ': Optional feature ' . + $group['attribs']['name'] . ' available (' . + $group['attribs']['hint'] . ')'; + } + + $extrainfo[] = $param->getPackage() . + ': To install optional features use "pear install ' . + $reg->parsedPackageNameToString( + array('package' => $param->getPackage(), + 'channel' => $param->getChannel()), true) . + '#featurename"'; + } + } + + $pkg = &$instreg->getPackage($param->getPackage(), $param->getChannel()); + // $pkg may be NULL if install is a 'fake' install via --packagingroot + if (is_object($pkg)) { + $pkg->setConfig($this->config); + if ($list = $pkg->listPostinstallScripts()) { + $pn = $reg->parsedPackageNameToString(array('channel' => + $param->getChannel(), 'package' => $param->getPackage()), true); + $extrainfo[] = $pn . ' has post-install scripts:'; + foreach ($list as $file) { + $extrainfo[] = $file; + } + $extrainfo[] = $param->getPackage() . + ': Use "pear run-scripts ' . $pn . '" to finish setup.'; + $extrainfo[] = 'DO NOT RUN SCRIPTS FROM UNTRUSTED SOURCES'; + } + } + } + + if (count($extrainfo)) { + foreach ($extrainfo as $info) { + $this->ui->outputData($info); + } + } + + return true; + } + + // }}} + // {{{ doUpgradeAll() + + function doUpgradeAll($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $upgrade = array(); + + if (isset($options['channel'])) { + $channels = array($options['channel']); + } else { + $channels = $reg->listChannels(); + } + + foreach ($channels as $channel) { + if ($channel == '__uri') { + continue; + } + + // parse name with channel + foreach ($reg->listPackages($channel) as $name) { + $upgrade[] = $reg->parsedPackageNameToString(array( + 'channel' => $channel, + 'package' => $name + )); + } + } + + $err = $this->doInstall($command, $options, $upgrade); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + } + } + + // }}} + // {{{ doUninstall() + + function doUninstall($command, $options, $params) + { + if (count($params) < 1) { + return $this->raiseError("Please supply the package(s) you want to uninstall"); + } + + if (empty($this->installer)) { + $this->installer = &$this->getInstaller($this->ui); + } + + if (isset($options['remoteconfig'])) { + $e = $this->config->readFTPConfigFile($options['remoteconfig']); + if (!PEAR::isError($e)) { + $this->installer->setConfig($this->config); + } + } + + $reg = &$this->config->getRegistry(); + $newparams = array(); + $binaries = array(); + $badparams = array(); + foreach ($params as $pkg) { + $channel = $this->config->get('default_channel'); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($pkg, $channel); + PEAR::staticPopErrorHandling(); + if (!$parsed || PEAR::isError($parsed)) { + $badparams[] = $pkg; + continue; + } + $package = $parsed['package']; + $channel = $parsed['channel']; + $info = &$reg->getPackage($package, $channel); + if ($info === null && + ($channel == 'pear.php.net' || $channel == 'pecl.php.net')) { + // make sure this isn't a package that has flipped from pear to pecl but + // used a package.xml 1.0 + $testc = ($channel == 'pear.php.net') ? 'pecl.php.net' : 'pear.php.net'; + $info = &$reg->getPackage($package, $testc); + if ($info !== null) { + $channel = $testc; + } + } + if ($info === null) { + $badparams[] = $pkg; + } else { + $newparams[] = &$info; + // check for binary packages (this is an alias for those packages if so) + if ($installedbinary = $info->getInstalledBinary()) { + $this->ui->log('adding binary package ' . + $reg->parsedPackageNameToString(array('channel' => $channel, + 'package' => $installedbinary), true)); + $newparams[] = &$reg->getPackage($installedbinary, $channel); + } + // add the contents of a dependency group to the list of installed packages + if (isset($parsed['group'])) { + $group = $info->getDependencyGroup($parsed['group']); + if ($group) { + $installed = $reg->getInstalledGroup($group); + if ($installed) { + foreach ($installed as $i => $p) { + $newparams[] = &$installed[$i]; + } + } + } + } + } + } + $err = $this->installer->sortPackagesForUninstall($newparams); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + return true; + } + $params = $newparams; + // twist this to use it to check on whether dependent packages are also being uninstalled + // for circular dependencies like subpackages + $this->installer->setUninstallPackages($newparams); + $params = array_merge($params, $badparams); + $binaries = array(); + foreach ($params as $pkg) { + $this->installer->pushErrorHandling(PEAR_ERROR_RETURN); + if ($err = $this->installer->uninstall($pkg, $options)) { + $this->installer->popErrorHandling(); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + continue; + } + if ($pkg->getPackageType() == 'extsrc' || + $pkg->getPackageType() == 'extbin' || + $pkg->getPackageType() == 'zendextsrc' || + $pkg->getPackageType() == 'zendextbin') { + if ($instbin = $pkg->getInstalledBinary()) { + continue; // this will be uninstalled later + } + + foreach ($pkg->getFilelist() as $name => $atts) { + $pinfo = pathinfo($atts['installed_as']); + if (!isset($pinfo['extension']) || + in_array($pinfo['extension'], array('c', 'h'))) { + continue; // make sure we don't match php_blah.h + } + if ((strpos($pinfo['basename'], 'php_') === 0 && + $pinfo['extension'] == 'dll') || + // most unices + $pinfo['extension'] == 'so' || + // hp-ux + $pinfo['extension'] == 'sl') { + $binaries[] = array($atts['installed_as'], $pinfo); + break; + } + } + if (count($binaries)) { + foreach ($binaries as $pinfo) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->disableExtension(array($pinfo[0]), $pkg->getPackageType()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($ret)) { + $extrainfo[] = $ret->getMessage(); + if ($pkg->getPackageType() == 'extsrc' || + $pkg->getPackageType() == 'extbin') { + $exttype = 'extension'; + } else { + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('Thread Safety.+enabled', $info) ? '_ts' : ''; + $exttype = 'zend_extension' . $debug . $ts; + } + $this->ui->outputData('Unable to remove "' . $exttype . '=' . + $pinfo[1]['basename'] . '" from php.ini', $command); + } else { + $this->ui->outputData('Extension ' . $pkg->getProvidesExtension() . + ' disabled in php.ini', $command); + } + } + } + } + $savepkg = $pkg; + if ($this->config->get('verbose') > 0) { + if (is_object($pkg)) { + $pkg = $reg->parsedPackageNameToString($pkg); + } + $this->ui->outputData("uninstall ok: $pkg", $command); + } + if (!isset($options['offline']) && is_object($savepkg) && + defined('PEAR_REMOTEINSTALL_OK')) { + if ($this->config->isDefinedLayer('ftp')) { + $this->installer->pushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->ftpUninstall($savepkg); + $this->installer->popErrorHandling(); + if (PEAR::isError($info)) { + $this->ui->outputData($info->getMessage()); + $this->ui->outputData("remote uninstall failed: $pkg"); + } else { + $this->ui->outputData("remote uninstall ok: $pkg"); + } + } + } + } else { + $this->installer->popErrorHandling(); + if (!is_object($pkg)) { + return $this->raiseError("uninstall failed: $pkg"); + } + $pkg = $reg->parsedPackageNameToString($pkg); + } + } + + return true; + } + + // }}} + + + // }}} + // {{{ doBundle() + /* + (cox) It just downloads and untars the package, does not do + any check that the PEAR_Installer::_installFile() does. + */ + + function doBundle($command, $options, $params) + { + $opts = array( + 'force' => true, + 'nodeps' => true, + 'soft' => true, + 'downloadonly' => true + ); + $downloader = &$this->getDownloader($this->ui, $opts, $this->config); + $reg = &$this->config->getRegistry(); + if (count($params) < 1) { + return $this->raiseError("Please supply the package you want to bundle"); + } + + if (isset($options['destination'])) { + if (!is_dir($options['destination'])) { + System::mkdir('-p ' . $options['destination']); + } + $dest = realpath($options['destination']); + } else { + $pwd = getcwd(); + $dir = $pwd . DIRECTORY_SEPARATOR . 'ext'; + $dest = is_dir($dir) ? $dir : $pwd; + } + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $downloader->setDownloadDir($dest); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($err)) { + return PEAR::raiseError('download directory "' . $dest . + '" is not writeable.'); + } + $result = &$downloader->download(array($params[0])); + if (PEAR::isError($result)) { + return $result; + } + if (!isset($result[0])) { + return $this->raiseError('unable to unpack ' . $params[0]); + } + $pkgfile = &$result[0]->getPackageFile(); + $pkgname = $pkgfile->getName(); + $pkgversion = $pkgfile->getVersion(); + + // Unpacking ------------------------------------------------- + $dest .= DIRECTORY_SEPARATOR . $pkgname; + $orig = $pkgname . '-' . $pkgversion; + + $tar = &new Archive_Tar($pkgfile->getArchiveFile()); + if (!$tar->extractModify($dest, $orig)) { + return $this->raiseError('unable to unpack ' . $pkgfile->getArchiveFile()); + } + $this->ui->outputData("Package ready at '$dest'"); + // }}} + } + + // }}} + + function doRunScripts($command, $options, $params) + { + if (!isset($params[0])) { + return $this->raiseError('run-scripts expects 1 parameter: a package name'); + } + + $reg = &$this->config->getRegistry(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($parsed)) { + return $this->raiseError($parsed); + } + + $package = &$reg->getPackage($parsed['package'], $parsed['channel']); + if (!is_object($package)) { + return $this->raiseError('Could not retrieve package "' . $params[0] . '" from registry'); + } + + $package->setConfig($this->config); + $package->runPostinstallScripts(); + $this->ui->outputData('Install scripts complete', $command); + return true; + } + + /** + * Given a list of packages, filter out those ones that are already up to date + * + * @param $packages: packages, in parsed array format ! + * @return list of packages that can be upgraded + */ + function _filterUptodatePackages($packages, $command) + { + $reg = &$this->config->getRegistry(); + $latestReleases = array(); + + $ret = array(); + foreach ($packages as $package) { + if (isset($package['group'])) { + $ret[] = $package; + continue; + } + + $channel = $package['channel']; + $name = $package['package']; + if (!$reg->packageExists($name, $channel)) { + $ret[] = $package; + continue; + } + + if (!isset($latestReleases[$channel])) { + // fill in cache for this channel + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + $base2 = false; + $preferred_mirror = $this->config->get('preferred_mirror', null, $channel); + if ($chan->supportsREST($preferred_mirror) && + ( + //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) || + ($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + ) { + $dorest = true; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!isset($package['state'])) { + $state = $this->config->get('preferred_state', null, $channel); + } else { + $state = $package['state']; + } + + if ($dorest) { + if ($base2) { + $rest = &$this->config->getREST('1.4', array()); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', array()); + } + + $installed = array_flip($reg->listPackages($channel)); + $latest = $rest->listLatestUpgrades($base, $state, $installed, $channel, $reg); + } + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($latest)) { + $this->ui->outputData('Error getting channel info from ' . $channel . + ': ' . $latest->getMessage()); + continue; + } + + $latestReleases[$channel] = array_change_key_case($latest); + } + + // check package for latest release + $name_lower = strtolower($name); + if (isset($latestReleases[$channel][$name_lower])) { + // if not set, up to date + $inst_version = $reg->packageInfo($name, 'version', $channel); + $channel_version = $latestReleases[$channel][$name_lower]['version']; + if (version_compare($channel_version, $inst_version, 'le')) { + // installed version is up-to-date + continue; + } + + // maintain BC + if ($command == 'upgrade-all') { + $this->ui->outputData(array('data' => 'Will upgrade ' . + $reg->parsedPackageNameToString($package)), $command); + } + $ret[] = $package; + } + } + + return $ret; + } +} + + Install Package + doInstall + i + + + f + will overwrite newer installed packages + + + l + do not check for recommended dependency version + + + n + ignore dependencies, install anyway + + + r + do not install files, only register the package as installed + + + s + soft install, fail silently, or upgrade if already installed + + + B + don't build C extensions + + + Z + request uncompressed files when downloading + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT), use packagingroot for RPM + DIR + + + P + root directory used when packaging files, like RPM packaging + DIR + + + + force install even if there were errors + + + a + install all required and optional dependencies + + + o + install all required dependencies + + + O + do not attempt to download any urls or contact channels + + + p + Only list the packages that would be downloaded + + + [channel/]<package> ... +Installs one or more PEAR packages. You can specify a package to +install in four ways: + +"Package-1.0.tgz" : installs from a local file + +"http://example.com/Package-1.0.tgz" : installs from +anywhere on the net. + +"package.xml" : installs the package described in +package.xml. Useful for testing, or for wrapping a PEAR package in +another package manager such as RPM. + +"Package[-version/state][.tar]" : queries your default channel's server +({config master_server}) and downloads the newest package with +the preferred quality/state ({config preferred_state}). + +To retrieve Package version 1.1, use "Package-1.1," to retrieve +Package state beta, use "Package-beta." To retrieve an uncompressed +file, append .tar (make sure there is no file by the same name first) + +To download a package from another channel, prefix with the channel name like +"channel/Package" + +More than one package may be specified at once. It is ok to mix these +four ways of specifying packages. + + + + Upgrade Package + doInstall + up + + + c + upgrade packages from a specific channel + CHAN + + + f + overwrite newer installed packages + + + l + do not check for recommended dependency version + + + n + ignore dependencies, upgrade anyway + + + r + do not install files, only register the package as upgraded + + + B + don't build C extensions + + + Z + request uncompressed files when downloading + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT) + DIR + + + + force install even if there were errors + + + a + install all required and optional dependencies + + + o + install all required dependencies + + + O + do not attempt to download any urls or contact channels + + + p + Only list the packages that would be downloaded + + + <package> ... +Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. + + + + Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters] + doUpgradeAll + ua + + + c + upgrade packages from a specific channel + CHAN + + + n + ignore dependencies, upgrade anyway + + + r + do not install files, only register the package as upgraded + + + B + don't build C extensions + + + Z + request uncompressed files when downloading + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT), use packagingroot for RPM + DIR + + + + force install even if there were errors + + + + do not check for recommended dependency version + + + +WARNING: This function is deprecated in favor of using the upgrade command with no params + +Upgrades all packages that have a newer release available. Upgrades are +done only if there is a release available of the state specified in +"preferred_state" (currently {config preferred_state}), or a state considered +more stable. + + + + Un-install Package + doUninstall + un + + + n + ignore dependencies, uninstall anyway + + + r + do not remove files, only register the packages as not installed + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT) + DIR + + + + force install even if there were errors + + + O + do not attempt to uninstall remotely + + + [channel/]<package> ... +Uninstalls one or more PEAR packages. More than one package may be +specified at once. Prefix with channel name to uninstall from a +channel not in your default channel ({config default_channel}) + + + + Unpacks a Pecl Package + doBundle + bun + + + d + Optional destination directory for unpacking (defaults to current path or "ext" if exists) + DIR + + + f + Force the unpacking even if there were errors in the package + + + <package> +Unpacks a Pecl Package into the selected location. It will download the +package if needed. + + + + Run Post-Install Scripts bundled with a package + doRunScripts + rs + + <package> +Run post-installation scripts in package <package>, if any exist. + + + + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 282969 2009-06-28 23:09:27Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1.0 + * @deprecated File deprecated since Release 1.4.0a1 + */ + +/** + * Include error handling + */ +require_once 'phar://go-pear.phar/' . 'PEAR.php'; + +/** + * PEAR_Common error when an invalid PHP file is passed to PEAR_Common::analyzeSourceCode() + */ +define('PEAR_COMMON_ERROR_INVALIDPHP', 1); +define('_PEAR_COMMON_PACKAGE_NAME_PREG', '[A-Za-z][a-zA-Z0-9_]+'); +define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/'); + +// this should allow: 1, 1.0, 1.0RC1, 1.0dev, 1.0dev123234234234, 1.0a1, 1.0b1, 1.0pl1 +define('_PEAR_COMMON_PACKAGE_VERSION_PREG', '\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?'); +define('PEAR_COMMON_PACKAGE_VERSION_PREG', '/^' . _PEAR_COMMON_PACKAGE_VERSION_PREG . '\\z/i'); + +// XXX far from perfect :-) +define('_PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '(' . _PEAR_COMMON_PACKAGE_NAME_PREG . + ')(-([.0-9a-zA-Z]+))?'); +define('PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_PACKAGE_DOWNLOAD_PREG . + '\\z/'); + +define('_PEAR_CHANNELS_NAME_PREG', '[A-Za-z][a-zA-Z0-9\.]+'); +define('PEAR_CHANNELS_NAME_PREG', '/^' . _PEAR_CHANNELS_NAME_PREG . '\\z/'); + +// this should allow any dns or IP address, plus a path - NO UNDERSCORES ALLOWED +define('_PEAR_CHANNELS_SERVER_PREG', '[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(\/[a-zA-Z0-9\-]+)*'); +define('PEAR_CHANNELS_SERVER_PREG', '/^' . _PEAR_CHANNELS_SERVER_PREG . '\\z/i'); + +define('_PEAR_CHANNELS_PACKAGE_PREG', '(' ._PEAR_CHANNELS_SERVER_PREG . ')\/(' + . _PEAR_COMMON_PACKAGE_NAME_PREG . ')'); +define('PEAR_CHANNELS_PACKAGE_PREG', '/^' . _PEAR_CHANNELS_PACKAGE_PREG . '\\z/i'); + +define('_PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '(' . _PEAR_CHANNELS_NAME_PREG . ')::(' + . _PEAR_COMMON_PACKAGE_NAME_PREG . ')(-([.0-9a-zA-Z]+))?'); +define('PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_CHANNEL_DOWNLOAD_PREG . '\\z/'); + +/** + * List of temporary files and directories registered by + * PEAR_Common::addTempFile(). + * @var array + */ +$GLOBALS['_PEAR_Common_tempfiles'] = array(); + +/** + * Valid maintainer roles + * @var array + */ +$GLOBALS['_PEAR_Common_maintainer_roles'] = array('lead','developer','contributor','helper'); + +/** + * Valid release states + * @var array + */ +$GLOBALS['_PEAR_Common_release_states'] = array('alpha','beta','stable','snapshot','devel'); + +/** + * Valid dependency types + * @var array + */ +$GLOBALS['_PEAR_Common_dependency_types'] = array('pkg','ext','php','prog','ldlib','rtlib','os','websrv','sapi'); + +/** + * Valid dependency relations + * @var array + */ +$GLOBALS['_PEAR_Common_dependency_relations'] = array('has','eq','lt','le','gt','ge','not', 'ne'); + +/** + * Valid file roles + * @var array + */ +$GLOBALS['_PEAR_Common_file_roles'] = array('php','ext','test','doc','data','src','script'); + +/** + * Valid replacement types + * @var array + */ +$GLOBALS['_PEAR_Common_replacement_types'] = array('php-const', 'pear-config', 'package-info'); + +/** + * Valid "provide" types + * @var array + */ +$GLOBALS['_PEAR_Common_provide_types'] = array('ext', 'prog', 'class', 'function', 'feature', 'api'); + +/** + * Valid "provide" types + * @var array + */ +$GLOBALS['_PEAR_Common_script_phases'] = array('pre-install', 'post-install', 'pre-uninstall', 'post-uninstall', 'pre-build', 'post-build', 'pre-configure', 'post-configure', 'pre-setup', 'post-setup'); + +/** + * Class providing common functionality for PEAR administration classes. + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + * @deprecated This class will disappear, and its components will be spread + * into smaller classes, like the AT&T breakup, as of Release 1.4.0a1 + */ +class PEAR_Common extends PEAR +{ + /** + * User Interface object (PEAR_Frontend_* class). If null, + * the log() method uses print. + * @var object + */ + var $ui = null; + + /** + * Configuration object (PEAR_Config). + * @var PEAR_Config + */ + var $config = null; + + /** stack of elements, gives some sort of XML context */ + var $element_stack = array(); + + /** name of currently parsed XML element */ + var $current_element; + + /** array of attributes of the currently parsed XML element */ + var $current_attributes = array(); + + /** assoc with information about a package */ + var $pkginfo = array(); + + var $current_path = null; + + /** + * Flag variable used to mark a valid package file + * @var boolean + * @access private + */ + var $_validPackageFile; + + /** + * PEAR_Common constructor + * + * @access public + */ + function PEAR_Common() + { + parent::PEAR(); + $this->config = &PEAR_Config::singleton(); + $this->debug = $this->config->get('verbose'); + } + + /** + * PEAR_Common destructor + * + * @access private + */ + function _PEAR_Common() + { + // doesn't work due to bug #14744 + //$tempfiles = $this->_tempfiles; + $tempfiles =& $GLOBALS['_PEAR_Common_tempfiles']; + while ($file = array_shift($tempfiles)) { + if (@is_dir($file)) { + if (!class_exists('System')) { + require_once 'phar://go-pear.phar/' . 'System.php'; + } + + System::rm(array('-rf', $file)); + } elseif (file_exists($file)) { + unlink($file); + } + } + } + + /** + * Register a temporary file or directory. When the destructor is + * executed, all registered temporary files and directories are + * removed. + * + * @param string $file name of file or directory + * + * @return void + * + * @access public + */ + function addTempFile($file) + { + if (!class_exists('PEAR_Frontend')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Frontend.php'; + } + PEAR_Frontend::addTempFile($file); + } + + /** + * Wrapper to System::mkDir(), creates a directory as well as + * any necessary parent directories. + * + * @param string $dir directory name + * + * @return bool TRUE on success, or a PEAR error + * + * @access public + */ + function mkDirHier($dir) + { + // Only used in Installer - move it there ? + $this->log(2, "+ create dir $dir"); + if (!class_exists('System')) { + require_once 'phar://go-pear.phar/' . 'System.php'; + } + return System::mkDir(array('-p', $dir)); + } + + /** + * Logging method. + * + * @param int $level log level (0 is quiet, higher is noisier) + * @param string $msg message to write to the log + * + * @return void + * + * @access public + * @static + */ + function log($level, $msg, $append_crlf = true) + { + if ($this->debug >= $level) { + if (!class_exists('PEAR_Frontend')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Frontend.php'; + } + + $ui = &PEAR_Frontend::singleton(); + if (is_a($ui, 'PEAR_Frontend')) { + $ui->log($msg, $append_crlf); + } else { + print "$msg\n"; + } + } + } + + /** + * Create and register a temporary directory. + * + * @param string $tmpdir (optional) Directory to use as tmpdir. + * Will use system defaults (for example + * /tmp or c:\windows\temp) if not specified + * + * @return string name of created directory + * + * @access public + */ + function mkTempDir($tmpdir = '') + { + $topt = $tmpdir ? array('-t', $tmpdir) : array(); + $topt = array_merge($topt, array('-d', 'pear')); + if (!class_exists('System')) { + require_once 'phar://go-pear.phar/' . 'System.php'; + } + + if (!$tmpdir = System::mktemp($topt)) { + return false; + } + + $this->addTempFile($tmpdir); + return $tmpdir; + } + + /** + * Set object that represents the frontend to be used. + * + * @param object Reference of the frontend object + * @return void + * @access public + */ + function setFrontendObject(&$ui) + { + $this->ui = &$ui; + } + + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + if ($include) { + $i--; + } + return array_slice($states, $i + 1); + } + + /** + * Get the valid roles for a PEAR package maintainer + * + * @return array + * @static + */ + function getUserRoles() + { + return $GLOBALS['_PEAR_Common_maintainer_roles']; + } + + /** + * Get the valid package release states of packages + * + * @return array + * @static + */ + function getReleaseStates() + { + return $GLOBALS['_PEAR_Common_release_states']; + } + + /** + * Get the implemented dependency types (php, ext, pkg etc.) + * + * @return array + * @static + */ + function getDependencyTypes() + { + return $GLOBALS['_PEAR_Common_dependency_types']; + } + + /** + * Get the implemented dependency relations (has, lt, ge etc.) + * + * @return array + * @static + */ + function getDependencyRelations() + { + return $GLOBALS['_PEAR_Common_dependency_relations']; + } + + /** + * Get the implemented file roles + * + * @return array + * @static + */ + function getFileRoles() + { + return $GLOBALS['_PEAR_Common_file_roles']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getReplacementTypes() + { + return $GLOBALS['_PEAR_Common_replacement_types']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getProvideTypes() + { + return $GLOBALS['_PEAR_Common_provide_types']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getScriptPhases() + { + return $GLOBALS['_PEAR_Common_script_phases']; + } + + /** + * Test whether a string contains a valid package name. + * + * @param string $name the package name to test + * + * @return bool + * + * @access public + */ + function validPackageName($name) + { + return (bool)preg_match(PEAR_COMMON_PACKAGE_NAME_PREG, $name); + } + + /** + * Test whether a string contains a valid package version. + * + * @param string $ver the package version to test + * + * @return bool + * + * @access public + */ + function validPackageVersion($ver) + { + return (bool)preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver); + } + + /** + * @param string $path relative or absolute include path + * @return boolean + * @static + */ + function isIncludeable($path) + { + if (file_exists($path) && is_readable($path)) { + return true; + } + + $ipath = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($ipath as $include) { + $test = realpath($include . DIRECTORY_SEPARATOR . $path); + if (file_exists($test) && is_readable($test)) { + return true; + } + } + + return false; + } + + function _postProcessChecks($pf) + { + if (!PEAR::isError($pf)) { + return $this->_postProcessValidPackagexml($pf); + } + + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + $e = $this->raiseError($error['message'], $error['code'], null, null, $error); + } + } + + return $pf; + } + + /** + * Returns information about a package file. Expects the name of + * a gzipped tar file as input. + * + * @param string $file name of .tgz file + * + * @return array array with package information + * + * @access public + * @deprecated use PEAR_PackageFile->fromTgzFile() instead + * + */ + function infoFromTgzFile($file) + { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromTgzFile($file, PEAR_VALIDATE_NORMAL); + return $this->_postProcessChecks($pf); + } + + /** + * Returns information about a package file. Expects the name of + * a package xml file as input. + * + * @param string $descfile name of package xml file + * + * @return array array with package information + * + * @access public + * @deprecated use PEAR_PackageFile->fromPackageFile() instead + * + */ + function infoFromDescriptionFile($descfile) + { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); + return $this->_postProcessChecks($pf); + } + + /** + * Returns information about a package file. Expects the contents + * of a package xml file as input. + * + * @param string $data contents of package.xml file + * + * @return array array with package information + * + * @access public + * @deprecated use PEAR_PackageFile->fromXmlstring() instead + * + */ + function infoFromString($data) + { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromXmlString($data, PEAR_VALIDATE_NORMAL, false); + return $this->_postProcessChecks($pf); + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return array + */ + function _postProcessValidPackagexml(&$pf) + { + if (!is_a($pf, 'PEAR_PackageFile_v2')) { + $this->pkginfo = $pf->toArray(); + return $this->pkginfo; + } + + // sort of make this into a package.xml 1.0-style array + // changelog is not converted to old format. + $arr = $pf->toArray(true); + $arr = array_merge($arr, $arr['old']); + unset($arr['old'], $arr['xsdversion'], $arr['contents'], $arr['compatible'], + $arr['channel'], $arr['uri'], $arr['dependencies'], $arr['phprelease'], + $arr['extsrcrelease'], $arr['zendextsrcrelease'], $arr['extbinrelease'], + $arr['zendextbinrelease'], $arr['bundle'], $arr['lead'], $arr['developer'], + $arr['helper'], $arr['contributor']); + $arr['filelist'] = $pf->getFilelist(); + $this->pkginfo = $arr; + return $arr; + } + + /** + * Returns package information from different sources + * + * This method is able to extract information about a package + * from a .tgz archive or from a XML package definition file. + * + * @access public + * @param string Filename of the source ('package.xml', '.tgz') + * @return string + * @deprecated use PEAR_PackageFile->fromAnyFile() instead + */ + function infoFromAny($info) + { + if (is_string($info) && file_exists($info)) { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($pf)) { + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + $e = $this->raiseError($error['message'], $error['code'], null, null, $error); + } + } + + return $pf; + } + + return $this->_postProcessValidPackagexml($pf); + } + + return $info; + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @param array $pkginfo package info + * + * @return string XML data + * + * @access public + * @deprecated use a PEAR_PackageFile_v* object's generator instead + */ + function xmlFromInfo($pkginfo) + { + $config = &PEAR_Config::singleton(); + $packagefile = &new PEAR_PackageFile($config); + $pf = &$packagefile->fromArray($pkginfo); + $gen = &$pf->getDefaultGenerator(); + return $gen->toXml(PEAR_VALIDATE_PACKAGING); + } + + /** + * Validate XML package definition file. + * + * @param string $info Filename of the package archive or of the + * package definition file + * @param array $errors Array that will contain the errors + * @param array $warnings Array that will contain the warnings + * @param string $dir_prefix (optional) directory where source files + * may be found, or empty if they are not available + * @access public + * @return boolean + * @deprecated use the validation of PEAR_PackageFile objects + */ + function validatePackageInfo($info, &$errors, &$warnings, $dir_prefix = '') + { + $config = &PEAR_Config::singleton(); + $packagefile = &new PEAR_PackageFile($config); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (strpos($info, 'fromXmlString($info, PEAR_VALIDATE_NORMAL, ''); + } else { + $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL); + } + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + if ($error['level'] == 'error') { + $errors[] = $error['message']; + } else { + $warnings[] = $error['message']; + } + } + } + + return false; + } + + return true; + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access public + * + */ + function buildProvidesArray($srcinfo) + { + $file = basename($srcinfo['source_file']); + $pn = ''; + if (isset($this->_packageName)) { + $pn = $this->_packageName; + } + + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($this->pkginfo['provides'][$key])) { + continue; + } + + $this->pkginfo['provides'][$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $this->pkginfo['provides'][$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($this->pkginfo['provides'][$key])) { + continue; + } + + $this->pkginfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($this->pkginfo['provides'][$key])) { + continue; + } + + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + + $this->pkginfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @return mixed + * @access public + */ + function analyzeSourceCode($file) + { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v2/Validator.php'; + } + + $a = new PEAR_PackageFile_v2_Validator; + return $a->analyzeSourceCode($file); + } + + function detectDependencies($any, $status_callback = null) + { + if (!function_exists("token_get_all")) { + return false; + } + + if (PEAR::isError($info = $this->infoFromAny($any))) { + return $this->raiseError($info); + } + + if (!is_array($info)) { + return false; + } + + $deps = array(); + $used_c = $decl_c = $decl_f = $decl_m = array(); + foreach ($info['filelist'] as $file => $fa) { + $tmp = $this->analyzeSourceCode($file); + $used_c = @array_merge($used_c, $tmp['used_classes']); + $decl_c = @array_merge($decl_c, $tmp['declared_classes']); + $decl_f = @array_merge($decl_f, $tmp['declared_functions']); + $decl_m = @array_merge($decl_m, $tmp['declared_methods']); + $inheri = @array_merge($inheri, $tmp['inheritance']); + } + + $used_c = array_unique($used_c); + $decl_c = array_unique($decl_c); + $undecl_c = array_diff($used_c, $decl_c); + + return array('used_classes' => $used_c, + 'declared_classes' => $decl_c, + 'declared_methods' => $decl_m, + 'declared_functions' => $decl_f, + 'undeclared_classes' => $undecl_c, + 'inheritance' => $inheri, + ); + } + + /** + * Download a file through HTTP. Considers suggested file name in + * Content-disposition: header and can run a callback function for + * different events. The callback will be called with two + * parameters: the callback type, and parameters. The implemented + * callback types are: + * + * 'setup' called at the very beginning, parameter is a UI object + * that should be used for all output + * 'message' the parameter is a string with an informational message + * 'saveas' may be used to save with a different file name, the + * parameter is the filename that is about to be used. + * If a 'saveas' callback returns a non-empty string, + * that file name will be used as the filename instead. + * Note that $save_dir will not be affected by this, only + * the basename of the file. + * 'start' download is starting, parameter is number of bytes + * that are expected, or -1 if unknown + * 'bytesread' parameter is the number of bytes read so far + * 'done' download is complete, parameter is the total number + * of bytes read + * 'connfailed' if the TCP connection fails, this callback is called + * with array(host,port,errno,errmsg) + * 'writefailed' if writing to disk fails, this callback is called + * with array(destfile,errmsg) + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param object $ui PEAR_Frontend_* instance + * @param object $config PEAR_Config instance + * @param string $save_dir (optional) directory to save file in + * @param mixed $callback (optional) function/method to call for status + * updates + * + * @return string Returns the full path of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). + * + * @access public + * @deprecated in favor of PEAR_Downloader::downloadHttp() + */ + function downloadHttp($url, &$ui, $save_dir = '.', $callback = null) + { + if (!class_exists('PEAR_Downloader')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Downloader.php'; + } + return PEAR_Downloader::downloadHttp($url, $ui, $save_dir, $callback); + } +} + +require_once 'phar://go-pear.phar/' . 'PEAR/Config.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile.php'; + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Config.php 286480 2009-07-29 02:50:02Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * Required for error handling + */ +require_once 'phar://go-pear.phar/' . 'PEAR.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/Registry.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/Installer/Role.php'; +require_once 'phar://go-pear.phar/' . 'System.php'; + +/** + * Last created PEAR_Config instance. + * @var object + */ +$GLOBALS['_PEAR_Config_instance'] = null; +if (!defined('PEAR_INSTALL_DIR') || !PEAR_INSTALL_DIR) { + $PEAR_INSTALL_DIR = PHP_LIBDIR . DIRECTORY_SEPARATOR . 'pear'; +} else { + $PEAR_INSTALL_DIR = PEAR_INSTALL_DIR; +} + +// Below we define constants with default values for all configuration +// parameters except username/password. All of them can have their +// defaults set through environment variables. The reason we use the +// PHP_ prefix is for some security, PHP protects environment +// variables starting with PHP_*. + +// default channel and preferred mirror is based on whether we are invoked through +// the "pear" or the "pecl" command +if (!defined('PEAR_RUNTYPE')) { + define('PEAR_RUNTYPE', 'pear'); +} + +if (PEAR_RUNTYPE == 'pear') { + define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pear.php.net'); +} else { + define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pecl.php.net'); +} + +if (getenv('PHP_PEAR_SYSCONF_DIR')) { + define('PEAR_CONFIG_SYSCONFDIR', getenv('PHP_PEAR_SYSCONF_DIR')); +} elseif (getenv('SystemRoot')) { + define('PEAR_CONFIG_SYSCONFDIR', getenv('SystemRoot')); +} else { + define('PEAR_CONFIG_SYSCONFDIR', PHP_SYSCONFDIR); +} + +// Default for master_server +if (getenv('PHP_PEAR_MASTER_SERVER')) { + define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', getenv('PHP_PEAR_MASTER_SERVER')); +} else { + define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', 'pear.php.net'); +} + +// Default for http_proxy +if (getenv('PHP_PEAR_HTTP_PROXY')) { + define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('PHP_PEAR_HTTP_PROXY')); +} elseif (getenv('http_proxy')) { + define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('http_proxy')); +} else { + define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', ''); +} + +// Default for php_dir +if (getenv('PHP_PEAR_INSTALL_DIR')) { + define('PEAR_CONFIG_DEFAULT_PHP_DIR', getenv('PHP_PEAR_INSTALL_DIR')); +} else { + if (@file_exists($PEAR_INSTALL_DIR) && is_dir($PEAR_INSTALL_DIR)) { + define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR); + } else { + define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR); + } +} + +// Default for ext_dir +if (getenv('PHP_PEAR_EXTENSION_DIR')) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', getenv('PHP_PEAR_EXTENSION_DIR')); +} else { + if (ini_get('extension_dir')) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', ini_get('extension_dir')); + } elseif (defined('PEAR_EXTENSION_DIR') && + file_exists(PEAR_EXTENSION_DIR) && is_dir(PEAR_EXTENSION_DIR)) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', PEAR_EXTENSION_DIR); + } elseif (defined('PHP_EXTENSION_DIR')) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', PHP_EXTENSION_DIR); + } else { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', '.'); + } +} + +// Default for doc_dir +if (getenv('PHP_PEAR_DOC_DIR')) { + define('PEAR_CONFIG_DEFAULT_DOC_DIR', getenv('PHP_PEAR_DOC_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_DOC_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'docs'); +} + +// Default for bin_dir +if (getenv('PHP_PEAR_BIN_DIR')) { + define('PEAR_CONFIG_DEFAULT_BIN_DIR', getenv('PHP_PEAR_BIN_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_BIN_DIR', PHP_BINDIR); +} + +// Default for data_dir +if (getenv('PHP_PEAR_DATA_DIR')) { + define('PEAR_CONFIG_DEFAULT_DATA_DIR', getenv('PHP_PEAR_DATA_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_DATA_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'data'); +} + +// Default for cfg_dir +if (getenv('PHP_PEAR_CFG_DIR')) { + define('PEAR_CONFIG_DEFAULT_CFG_DIR', getenv('PHP_PEAR_CFG_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_CFG_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'cfg'); +} + +// Default for www_dir +if (getenv('PHP_PEAR_WWW_DIR')) { + define('PEAR_CONFIG_DEFAULT_WWW_DIR', getenv('PHP_PEAR_WWW_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_WWW_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'www'); +} + +// Default for test_dir +if (getenv('PHP_PEAR_TEST_DIR')) { + define('PEAR_CONFIG_DEFAULT_TEST_DIR', getenv('PHP_PEAR_TEST_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_TEST_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'tests'); +} + +// Default for temp_dir +if (getenv('PHP_PEAR_TEMP_DIR')) { + define('PEAR_CONFIG_DEFAULT_TEMP_DIR', getenv('PHP_PEAR_TEMP_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_TEMP_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'temp'); +} + +// Default for cache_dir +if (getenv('PHP_PEAR_CACHE_DIR')) { + define('PEAR_CONFIG_DEFAULT_CACHE_DIR', getenv('PHP_PEAR_CACHE_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_CACHE_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'cache'); +} + +// Default for download_dir +if (getenv('PHP_PEAR_DOWNLOAD_DIR')) { + define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR', getenv('PHP_PEAR_DOWNLOAD_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'download'); +} + +// Default for php_bin +if (getenv('PHP_PEAR_PHP_BIN')) { + define('PEAR_CONFIG_DEFAULT_PHP_BIN', getenv('PHP_PEAR_PHP_BIN')); +} else { + define('PEAR_CONFIG_DEFAULT_PHP_BIN', PEAR_CONFIG_DEFAULT_BIN_DIR. + DIRECTORY_SEPARATOR.'php'.(OS_WINDOWS ? '.exe' : '')); +} + +// Default for verbose +if (getenv('PHP_PEAR_VERBOSE')) { + define('PEAR_CONFIG_DEFAULT_VERBOSE', getenv('PHP_PEAR_VERBOSE')); +} else { + define('PEAR_CONFIG_DEFAULT_VERBOSE', 1); +} + +// Default for preferred_state +if (getenv('PHP_PEAR_PREFERRED_STATE')) { + define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', getenv('PHP_PEAR_PREFERRED_STATE')); +} else { + define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', 'stable'); +} + +// Default for umask +if (getenv('PHP_PEAR_UMASK')) { + define('PEAR_CONFIG_DEFAULT_UMASK', getenv('PHP_PEAR_UMASK')); +} else { + define('PEAR_CONFIG_DEFAULT_UMASK', decoct(umask())); +} + +// Default for cache_ttl +if (getenv('PHP_PEAR_CACHE_TTL')) { + define('PEAR_CONFIG_DEFAULT_CACHE_TTL', getenv('PHP_PEAR_CACHE_TTL')); +} else { + define('PEAR_CONFIG_DEFAULT_CACHE_TTL', 3600); +} + +// Default for sig_type +if (getenv('PHP_PEAR_SIG_TYPE')) { + define('PEAR_CONFIG_DEFAULT_SIG_TYPE', getenv('PHP_PEAR_SIG_TYPE')); +} else { + define('PEAR_CONFIG_DEFAULT_SIG_TYPE', 'gpg'); +} + +// Default for sig_bin +if (getenv('PHP_PEAR_SIG_BIN')) { + define('PEAR_CONFIG_DEFAULT_SIG_BIN', getenv('PHP_PEAR_SIG_BIN')); +} else { + define('PEAR_CONFIG_DEFAULT_SIG_BIN', + System::which( + 'gpg', OS_WINDOWS ? 'c:\gnupg\gpg.exe' : '/usr/local/bin/gpg')); +} + +// Default for sig_keydir +if (getenv('PHP_PEAR_SIG_KEYDIR')) { + define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR', getenv('PHP_PEAR_SIG_KEYDIR')); +} else { + define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR', + PEAR_CONFIG_SYSCONFDIR . DIRECTORY_SEPARATOR . 'pearkeys'); +} + +/** + * This is a class for storing configuration data, keeping track of + * which are system-defined, user-defined or defaulted. + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Config extends PEAR +{ + /** + * Array of config files used. + * + * @var array layer => config file + */ + var $files = array( + 'system' => '', + 'user' => '', + ); + + var $layers = array(); + + /** + * Configuration data, two-dimensional array where the first + * dimension is the config layer ('user', 'system' and 'default'), + * and the second dimension is keyname => value. + * + * The order in the first dimension is important! Earlier + * layers will shadow later ones when a config value is + * requested (if a 'user' value exists, it will be returned first, + * then 'system' and finally 'default'). + * + * @var array layer => array(keyname => value, ...) + */ + var $configuration = array( + 'user' => array(), + 'system' => array(), + 'default' => array(), + ); + + /** + * Configuration values that can be set for a channel + * + * All other configuration values can only have a global value + * @var array + * @access private + */ + var $_channelConfigInfo = array( + 'php_dir', 'ext_dir', 'doc_dir', 'bin_dir', 'data_dir', 'cfg_dir', + 'test_dir', 'www_dir', 'php_bin', 'php_prefix', 'php_suffix', 'username', + 'password', 'verbose', 'preferred_state', 'umask', 'preferred_mirror', 'php_ini' + ); + + /** + * Channels that can be accessed + * @see setChannels() + * @var array + * @access private + */ + var $_channels = array('pear.php.net', 'pecl.php.net', '__uri'); + + /** + * This variable is used to control the directory values returned + * @see setInstallRoot(); + * @var string|false + * @access private + */ + var $_installRoot = false; + + /** + * If requested, this will always refer to the registry + * contained in php_dir + * @var PEAR_Registry + */ + var $_registry = array(); + + /** + * @var array + * @access private + */ + var $_regInitialized = array(); + + /** + * @var bool + * @access private + */ + var $_noRegistry = false; + + /** + * amount of errors found while parsing config + * @var integer + * @access private + */ + var $_errorsFound = 0; + var $_lastError = null; + + /** + * Information about the configuration data. Stores the type, + * default value and a documentation string for each configuration + * value. + * + * @var array layer => array(infotype => value, ...) + */ + var $configuration_info = array( + // Channels/Internet Access + 'default_channel' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_CHANNEL, + 'doc' => 'the default channel to use for all non explicit commands', + 'prompt' => 'Default Channel', + 'group' => 'Internet Access', + ), + 'preferred_mirror' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_CHANNEL, + 'doc' => 'the default server or mirror to use for channel actions', + 'prompt' => 'Default Channel Mirror', + 'group' => 'Internet Access', + ), + 'remote_config' => array( + 'type' => 'password', + 'default' => '', + 'doc' => 'ftp url of remote configuration file to use for synchronized install', + 'prompt' => 'Remote Configuration File', + 'group' => 'Internet Access', + ), + 'auto_discover' => array( + 'type' => 'integer', + 'default' => 0, + 'doc' => 'whether to automatically discover new channels', + 'prompt' => 'Auto-discover new Channels', + 'group' => 'Internet Access', + ), + // Internet Access + 'master_server' => array( + 'type' => 'string', + 'default' => 'pear.php.net', + 'doc' => 'name of the main PEAR server [NOT USED IN THIS VERSION]', + 'prompt' => 'PEAR server [DEPRECATED]', + 'group' => 'Internet Access', + ), + 'http_proxy' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_HTTP_PROXY, + 'doc' => 'HTTP proxy (host:port) to use when downloading packages', + 'prompt' => 'HTTP Proxy Server Address', + 'group' => 'Internet Access', + ), + // File Locations + 'php_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_PHP_DIR, + 'doc' => 'directory where .php files are installed', + 'prompt' => 'PEAR directory', + 'group' => 'File Locations', + ), + 'ext_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_EXT_DIR, + 'doc' => 'directory where loadable extensions are installed', + 'prompt' => 'PHP extension directory', + 'group' => 'File Locations', + ), + 'doc_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_DOC_DIR, + 'doc' => 'directory where documentation is installed', + 'prompt' => 'PEAR documentation directory', + 'group' => 'File Locations', + ), + 'bin_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_BIN_DIR, + 'doc' => 'directory where executables are installed', + 'prompt' => 'PEAR executables directory', + 'group' => 'File Locations', + ), + 'data_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_DATA_DIR, + 'doc' => 'directory where data files are installed', + 'prompt' => 'PEAR data directory', + 'group' => 'File Locations (Advanced)', + ), + 'cfg_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_CFG_DIR, + 'doc' => 'directory where modifiable configuration files are installed', + 'prompt' => 'PEAR configuration file directory', + 'group' => 'File Locations (Advanced)', + ), + 'www_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_WWW_DIR, + 'doc' => 'directory where www frontend files (html/js) are installed', + 'prompt' => 'PEAR www files directory', + 'group' => 'File Locations (Advanced)', + ), + 'test_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_TEST_DIR, + 'doc' => 'directory where regression tests are installed', + 'prompt' => 'PEAR test directory', + 'group' => 'File Locations (Advanced)', + ), + 'cache_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_CACHE_DIR, + 'doc' => 'directory which is used for web service cache', + 'prompt' => 'PEAR Installer cache directory', + 'group' => 'File Locations (Advanced)', + ), + 'temp_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_TEMP_DIR, + 'doc' => 'directory which is used for all temp files', + 'prompt' => 'PEAR Installer temp directory', + 'group' => 'File Locations (Advanced)', + ), + 'download_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR, + 'doc' => 'directory which is used for all downloaded files', + 'prompt' => 'PEAR Installer download directory', + 'group' => 'File Locations (Advanced)', + ), + 'php_bin' => array( + 'type' => 'file', + 'default' => PEAR_CONFIG_DEFAULT_PHP_BIN, + 'doc' => 'PHP CLI/CGI binary for executing scripts', + 'prompt' => 'PHP CLI/CGI binary', + 'group' => 'File Locations (Advanced)', + ), + 'php_prefix' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '--program-prefix for php_bin\'s ./configure, used for pecl installs', + 'prompt' => '--program-prefix passed to PHP\'s ./configure', + 'group' => 'File Locations (Advanced)', + ), + 'php_suffix' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '--program-suffix for php_bin\'s ./configure, used for pecl installs', + 'prompt' => '--program-suffix passed to PHP\'s ./configure', + 'group' => 'File Locations (Advanced)', + ), + 'php_ini' => array( + 'type' => 'file', + 'default' => '', + 'doc' => 'location of php.ini in which to enable PECL extensions on install', + 'prompt' => 'php.ini location', + 'group' => 'File Locations (Advanced)', + ), + // Maintainers + 'username' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '(maintainers) your PEAR account name', + 'prompt' => 'PEAR username (for maintainers)', + 'group' => 'Maintainers', + ), + 'password' => array( + 'type' => 'password', + 'default' => '', + 'doc' => '(maintainers) your PEAR account password', + 'prompt' => 'PEAR password (for maintainers)', + 'group' => 'Maintainers', + ), + // Advanced + 'verbose' => array( + 'type' => 'integer', + 'default' => PEAR_CONFIG_DEFAULT_VERBOSE, + 'doc' => 'verbosity level +0: really quiet +1: somewhat quiet +2: verbose +3: debug', + 'prompt' => 'Debug Log Level', + 'group' => 'Advanced', + ), + 'preferred_state' => array( + 'type' => 'set', + 'default' => PEAR_CONFIG_DEFAULT_PREFERRED_STATE, + 'doc' => 'the installer will prefer releases with this state when installing packages without a version or state specified', + 'valid_set' => array( + 'stable', 'beta', 'alpha', 'devel', 'snapshot'), + 'prompt' => 'Preferred Package State', + 'group' => 'Advanced', + ), + 'umask' => array( + 'type' => 'mask', + 'default' => PEAR_CONFIG_DEFAULT_UMASK, + 'doc' => 'umask used when creating files (Unix-like systems only)', + 'prompt' => 'Unix file mask', + 'group' => 'Advanced', + ), + 'cache_ttl' => array( + 'type' => 'integer', + 'default' => PEAR_CONFIG_DEFAULT_CACHE_TTL, + 'doc' => 'amount of secs where the local cache is used and not updated', + 'prompt' => 'Cache TimeToLive', + 'group' => 'Advanced', + ), + 'sig_type' => array( + 'type' => 'set', + 'default' => PEAR_CONFIG_DEFAULT_SIG_TYPE, + 'doc' => 'which package signature mechanism to use', + 'valid_set' => array('gpg'), + 'prompt' => 'Package Signature Type', + 'group' => 'Maintainers', + ), + 'sig_bin' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_SIG_BIN, + 'doc' => 'which package signature mechanism to use', + 'prompt' => 'Signature Handling Program', + 'group' => 'Maintainers', + ), + 'sig_keyid' => array( + 'type' => 'string', + 'default' => '', + 'doc' => 'which key to use for signing with', + 'prompt' => 'Signature Key Id', + 'group' => 'Maintainers', + ), + 'sig_keydir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_SIG_KEYDIR, + 'doc' => 'directory where signature keys are located', + 'prompt' => 'Signature Key Directory', + 'group' => 'Maintainers', + ), + // __channels is reserved - used for channel-specific configuration + ); + + /** + * Constructor. + * + * @param string file to read user-defined options from + * @param string file to read system-wide defaults from + * @param bool determines whether a registry object "follows" + * the value of php_dir (is automatically created + * and moved when php_dir is changed) + * @param bool if true, fails if configuration files cannot be loaded + * + * @access public + * + * @see PEAR_Config::singleton + */ + function PEAR_Config($user_file = '', $system_file = '', $ftp_file = false, + $strict = true) + { + $this->PEAR(); + PEAR_Installer_Role::initializeConfig($this); + $sl = DIRECTORY_SEPARATOR; + if (empty($user_file)) { + if (OS_WINDOWS) { + $user_file = PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini'; + } else { + $user_file = getenv('HOME') . $sl . '.pearrc'; + } + } + + if (empty($system_file)) { + $system_file = PEAR_CONFIG_SYSCONFDIR . $sl; + if (OS_WINDOWS) { + $system_file .= 'pearsys.ini'; + } else { + $system_file .= 'pear.conf'; + } + } + + $this->layers = array_keys($this->configuration); + $this->files['user'] = $user_file; + $this->files['system'] = $system_file; + if ($user_file && file_exists($user_file)) { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->readConfigFile($user_file, 'user', $strict); + $this->popErrorHandling(); + if ($this->_errorsFound > 0) { + return; + } + } + + if ($system_file && @file_exists($system_file)) { + $this->mergeConfigFile($system_file, false, 'system', $strict); + if ($this->_errorsFound > 0) { + return; + } + + } + + if (!$ftp_file) { + $ftp_file = $this->get('remote_config'); + } + + if ($ftp_file && defined('PEAR_REMOTEINSTALL_OK')) { + $this->readFTPConfigFile($ftp_file); + } + + foreach ($this->configuration_info as $key => $info) { + $this->configuration['default'][$key] = $info['default']; + } + + $this->_registry['default'] = &new PEAR_Registry($this->configuration['default']['php_dir']); + $this->_registry['default']->setConfig($this, false); + $this->_regInitialized['default'] = false; + //$GLOBALS['_PEAR_Config_instance'] = &$this; + } + + /** + * Return the default locations of user and system configuration files + * @static + */ + function getDefaultConfigFiles() + { + $sl = DIRECTORY_SEPARATOR; + if (OS_WINDOWS) { + return array( + 'user' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini', + 'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pearsys.ini' + ); + } + + return array( + 'user' => getenv('HOME') . $sl . '.pearrc', + 'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.conf' + ); + } + + /** + * Static singleton method. If you want to keep only one instance + * of this class in use, this method will give you a reference to + * the last created PEAR_Config object if one exists, or create a + * new object. + * + * @param string (optional) file to read user-defined options from + * @param string (optional) file to read system-wide defaults from + * + * @return object an existing or new PEAR_Config instance + * + * @access public + * + * @see PEAR_Config::PEAR_Config + */ + function &singleton($user_file = '', $system_file = '', $strict = true) + { + if (is_object($GLOBALS['_PEAR_Config_instance'])) { + return $GLOBALS['_PEAR_Config_instance']; + } + + $t_conf = &new PEAR_Config($user_file, $system_file, false, $strict); + if ($t_conf->_errorsFound > 0) { + return $t_conf->lastError; + } + + $GLOBALS['_PEAR_Config_instance'] = &$t_conf; + return $GLOBALS['_PEAR_Config_instance']; + } + + /** + * Determine whether any configuration files have been detected, and whether a + * registry object can be retrieved from this configuration. + * @return bool + * @since PEAR 1.4.0a1 + */ + function validConfiguration() + { + if ($this->isDefinedLayer('user') || $this->isDefinedLayer('system')) { + return true; + } + + return false; + } + + /** + * Reads configuration data from a file. All existing values in + * the config layer are discarded and replaced with data from the + * file. + * @param string file to read from, if NULL or not specified, the + * last-used file for the same layer (second param) is used + * @param string config layer to insert data into ('user' or 'system') + * @return bool TRUE on success or a PEAR error on failure + */ + function readConfigFile($file = null, $layer = 'user', $strict = true) + { + if (empty($this->files[$layer])) { + return $this->raiseError("unknown config layer `$layer'"); + } + + if ($file === null) { + $file = $this->files[$layer]; + } + + $data = $this->_readConfigDataFrom($file); + if (PEAR::isError($data)) { + if (!$strict) { + return true; + } + + $this->_errorsFound++; + $this->lastError = $data; + + return $data; + } + + $this->files[$layer] = $file; + $this->_decodeInput($data); + $this->configuration[$layer] = $data; + $this->_setupChannels(); + if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + } + return true; + } + + /** + * @param string url to the remote config file, like ftp://www.example.com/pear/config.ini + * @return true|PEAR_Error + */ + function readFTPConfigFile($path) + { + do { // poor man's try + if (!class_exists('PEAR_FTP')) { + if (!class_exists('PEAR_Common')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Common.php'; + } + if (PEAR_Common::isIncludeable('PEAR/FTP.php')) { + require_once 'phar://go-pear.phar/' . 'PEAR/FTP.php'; + } + } + + if (!class_exists('PEAR_FTP')) { + return PEAR::raiseError('PEAR_RemoteInstaller must be installed to use remote config'); + } + + $this->_ftp = &new PEAR_FTP; + $this->_ftp->pushErrorHandling(PEAR_ERROR_RETURN); + $e = $this->_ftp->init($path); + if (PEAR::isError($e)) { + $this->_ftp->popErrorHandling(); + return $e; + } + + $tmp = System::mktemp('-d'); + PEAR_Common::addTempFile($tmp); + $e = $this->_ftp->get(basename($path), $tmp . DIRECTORY_SEPARATOR . + 'pear.ini', false, FTP_BINARY); + if (PEAR::isError($e)) { + $this->_ftp->popErrorHandling(); + return $e; + } + + PEAR_Common::addTempFile($tmp . DIRECTORY_SEPARATOR . 'pear.ini'); + $this->_ftp->disconnect(); + $this->_ftp->popErrorHandling(); + $this->files['ftp'] = $tmp . DIRECTORY_SEPARATOR . 'pear.ini'; + $e = $this->readConfigFile(null, 'ftp'); + if (PEAR::isError($e)) { + return $e; + } + + $fail = array(); + foreach ($this->configuration_info as $key => $val) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + // any directory configs must be set for this to work + if (!isset($this->configuration['ftp'][$key])) { + $fail[] = $key; + } + } + } + + if (!count($fail)) { + return true; + } + + $fail = '"' . implode('", "', $fail) . '"'; + unset($this->files['ftp']); + unset($this->configuration['ftp']); + return PEAR::raiseError('ERROR: Ftp configuration file must set all ' . + 'directory configuration variables. These variables were not set: ' . + $fail); + } while (false); // poor man's catch + unset($this->files['ftp']); + return PEAR::raiseError('no remote host specified'); + } + + /** + * Reads the existing configurations and creates the _channels array from it + */ + function _setupChannels() + { + $set = array_flip(array_values($this->_channels)); + foreach ($this->configuration as $layer => $data) { + $i = 1000; + if (isset($data['__channels']) && is_array($data['__channels'])) { + foreach ($data['__channels'] as $channel => $info) { + $set[$channel] = $i++; + } + } + } + $this->_channels = array_values(array_flip($set)); + $this->setChannels($this->_channels); + } + + function deleteChannel($channel) + { + $ch = strtolower($channel); + foreach ($this->configuration as $layer => $data) { + if (isset($data['__channels']) && isset($data['__channels'][$ch])) { + unset($this->configuration[$layer]['__channels'][$ch]); + } + } + + $this->_channels = array_flip($this->_channels); + unset($this->_channels[$ch]); + $this->_channels = array_flip($this->_channels); + } + + /** + * Merges data into a config layer from a file. Does the same + * thing as readConfigFile, except it does not replace all + * existing values in the config layer. + * @param string file to read from + * @param bool whether to overwrite existing data (default TRUE) + * @param string config layer to insert data into ('user' or 'system') + * @param string if true, errors are returned if file opening fails + * @return bool TRUE on success or a PEAR error on failure + */ + function mergeConfigFile($file, $override = true, $layer = 'user', $strict = true) + { + if (empty($this->files[$layer])) { + return $this->raiseError("unknown config layer `$layer'"); + } + + if ($file === null) { + $file = $this->files[$layer]; + } + + $data = $this->_readConfigDataFrom($file); + if (PEAR::isError($data)) { + if (!$strict) { + return true; + } + + $this->_errorsFound++; + $this->lastError = $data; + + return $data; + } + + $this->_decodeInput($data); + if ($override) { + $this->configuration[$layer] = + PEAR_Config::arrayMergeRecursive($this->configuration[$layer], $data); + } else { + $this->configuration[$layer] = + PEAR_Config::arrayMergeRecursive($data, $this->configuration[$layer]); + } + + $this->_setupChannels(); + if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + } + return true; + } + + /** + * @param array + * @param array + * @return array + * @static + */ + function arrayMergeRecursive($arr2, $arr1) + { + $ret = array(); + foreach ($arr2 as $key => $data) { + if (!isset($arr1[$key])) { + $ret[$key] = $data; + unset($arr1[$key]); + continue; + } + if (is_array($data)) { + if (!is_array($arr1[$key])) { + $ret[$key] = $arr1[$key]; + unset($arr1[$key]); + continue; + } + $ret[$key] = PEAR_Config::arrayMergeRecursive($arr1[$key], $arr2[$key]); + unset($arr1[$key]); + } + } + + return array_merge($ret, $arr1); + } + + /** + * Writes data into a config layer from a file. + * + * @param string|null file to read from, or null for default + * @param string config layer to insert data into ('user' or + * 'system') + * @param string|null data to write to config file or null for internal data [DEPRECATED] + * @return bool TRUE on success or a PEAR error on failure + */ + function writeConfigFile($file = null, $layer = 'user', $data = null) + { + $this->_lazyChannelSetup($layer); + if ($layer == 'both' || $layer == 'all') { + foreach ($this->files as $type => $file) { + $err = $this->writeConfigFile($file, $type, $data); + if (PEAR::isError($err)) { + return $err; + } + } + return true; + } + + if (empty($this->files[$layer])) { + return $this->raiseError("unknown config file type `$layer'"); + } + + if ($file === null) { + $file = $this->files[$layer]; + } + + $data = ($data === null) ? $this->configuration[$layer] : $data; + $this->_encodeOutput($data); + $opt = array('-p', dirname($file)); + if (!@System::mkDir($opt)) { + return $this->raiseError("could not create directory: " . dirname($file)); + } + + if (file_exists($file) && is_file($file) && !is_writeable($file)) { + return $this->raiseError("no write access to $file!"); + } + + $fp = @fopen($file, "w"); + if (!$fp) { + return $this->raiseError("PEAR_Config::writeConfigFile fopen('$file','w') failed ($php_errormsg)"); + } + + $contents = "#PEAR_Config 0.9\n" . serialize($data); + if (!@fwrite($fp, $contents)) { + return $this->raiseError("PEAR_Config::writeConfigFile: fwrite failed ($php_errormsg)"); + } + return true; + } + + /** + * Reads configuration data from a file and returns the parsed data + * in an array. + * + * @param string file to read from + * @return array configuration data or a PEAR error on failure + * @access private + */ + function _readConfigDataFrom($file) + { + $fp = false; + if (file_exists($file)) { + $fp = @fopen($file, "r"); + } + + if (!$fp) { + return $this->raiseError("PEAR_Config::readConfigFile fopen('$file','r') failed"); + } + + $size = filesize($file); + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + fclose($fp); + $contents = file_get_contents($file); + if (empty($contents)) { + return $this->raiseError('Configuration file "' . $file . '" is empty'); + } + + set_magic_quotes_runtime($rt); + + $version = false; + if (preg_match('/^#PEAR_Config\s+(\S+)\s+/si', $contents, $matches)) { + $version = $matches[1]; + $contents = substr($contents, strlen($matches[0])); + } else { + // Museum config file + if (substr($contents,0,2) == 'a:') { + $version = '0.1'; + } + } + + if ($version && version_compare("$version", '1', '<')) { + // no '@', it is possible that unserialize + // raises a notice but it seems to block IO to + // STDOUT if a '@' is used and a notice is raise + $data = unserialize($contents); + + if (!is_array($data) && !$data) { + if ($contents == serialize(false)) { + $data = array(); + } else { + $err = $this->raiseError("PEAR_Config: bad data in $file"); + return $err; + } + } + if (!is_array($data)) { + if (strlen(trim($contents)) > 0) { + $error = "PEAR_Config: bad data in $file"; + $err = $this->raiseError($error); + return $err; + } + + $data = array(); + } + // add parsing of newer formats here... + } else { + $err = $this->raiseError("$file: unknown version `$version'"); + return $err; + } + + return $data; + } + + /** + * Gets the file used for storing the config for a layer + * + * @param string $layer 'user' or 'system' + */ + function getConfFile($layer) + { + return $this->files[$layer]; + } + + /** + * @param string Configuration class name, used for detecting duplicate calls + * @param array information on a role as parsed from its xml file + * @return true|PEAR_Error + * @access private + */ + function _addConfigVars($class, $vars) + { + static $called = array(); + if (isset($called[$class])) { + return; + } + + $called[$class] = 1; + if (count($vars) > 3) { + return $this->raiseError('Roles can only define 3 new config variables or less'); + } + + foreach ($vars as $name => $var) { + if (!is_array($var)) { + return $this->raiseError('Configuration information must be an array'); + } + + if (!isset($var['type'])) { + return $this->raiseError('Configuration information must contain a type'); + } elseif (!in_array($var['type'], + array('string', 'mask', 'password', 'directory', 'file', 'set'))) { + return $this->raiseError( + 'Configuration type must be one of directory, file, string, ' . + 'mask, set, or password'); + } + if (!isset($var['default'])) { + return $this->raiseError( + 'Configuration information must contain a default value ("default" index)'); + } + + if (is_array($var['default'])) { + $real_default = ''; + foreach ($var['default'] as $config_var => $val) { + if (strpos($config_var, 'text') === 0) { + $real_default .= $val; + } elseif (strpos($config_var, 'constant') === 0) { + if (!defined($val)) { + return $this->raiseError( + 'Unknown constant "' . $val . '" requested in ' . + 'default value for configuration variable "' . + $name . '"'); + } + + $real_default .= constant($val); + } elseif (isset($this->configuration_info[$config_var])) { + $real_default .= + $this->configuration_info[$config_var]['default']; + } else { + return $this->raiseError( + 'Unknown request for "' . $config_var . '" value in ' . + 'default value for configuration variable "' . + $name . '"'); + } + } + $var['default'] = $real_default; + } + + if ($var['type'] == 'integer') { + $var['default'] = (integer) $var['default']; + } + + if (!isset($var['doc'])) { + return $this->raiseError( + 'Configuration information must contain a summary ("doc" index)'); + } + + if (!isset($var['prompt'])) { + return $this->raiseError( + 'Configuration information must contain a simple prompt ("prompt" index)'); + } + + if (!isset($var['group'])) { + return $this->raiseError( + 'Configuration information must contain a simple group ("group" index)'); + } + + if (isset($this->configuration_info[$name])) { + return $this->raiseError('Configuration variable "' . $name . + '" already exists'); + } + + $this->configuration_info[$name] = $var; + // fix bug #7351: setting custom config variable in a channel fails + $this->_channelConfigInfo[] = $name; + } + + return true; + } + + /** + * Encodes/scrambles configuration data before writing to files. + * Currently, 'password' values will be base64-encoded as to avoid + * that people spot cleartext passwords by accident. + * + * @param array (reference) array to encode values in + * @return bool TRUE on success + * @access private + */ + function _encodeOutput(&$data) + { + foreach ($data as $key => $value) { + if ($key == '__channels') { + foreach ($data['__channels'] as $channel => $blah) { + $this->_encodeOutput($data['__channels'][$channel]); + } + } + + if (!isset($this->configuration_info[$key])) { + continue; + } + + $type = $this->configuration_info[$key]['type']; + switch ($type) { + // we base64-encode passwords so they are at least + // not shown in plain by accident + case 'password': { + $data[$key] = base64_encode($data[$key]); + break; + } + case 'mask': { + $data[$key] = octdec($data[$key]); + break; + } + } + } + + return true; + } + + /** + * Decodes/unscrambles configuration data after reading from files. + * + * @param array (reference) array to encode values in + * @return bool TRUE on success + * @access private + * + * @see PEAR_Config::_encodeOutput + */ + function _decodeInput(&$data) + { + if (!is_array($data)) { + return true; + } + + foreach ($data as $key => $value) { + if ($key == '__channels') { + foreach ($data['__channels'] as $channel => $blah) { + $this->_decodeInput($data['__channels'][$channel]); + } + } + + if (!isset($this->configuration_info[$key])) { + continue; + } + + $type = $this->configuration_info[$key]['type']; + switch ($type) { + case 'password': { + $data[$key] = base64_decode($data[$key]); + break; + } + case 'mask': { + $data[$key] = decoct($data[$key]); + break; + } + } + } + + return true; + } + + /** + * Retrieve the default channel. + * + * On startup, channels are not initialized, so if the default channel is not + * pear.php.net, then initialize the config. + * @param string registry layer + * @return string|false + */ + function getDefaultChannel($layer = null) + { + $ret = false; + if ($layer === null) { + foreach ($this->layers as $layer) { + if (isset($this->configuration[$layer]['default_channel'])) { + $ret = $this->configuration[$layer]['default_channel']; + break; + } + } + } elseif (isset($this->configuration[$layer]['default_channel'])) { + $ret = $this->configuration[$layer]['default_channel']; + } + + if ($ret == 'pear.php.net' && defined('PEAR_RUNTYPE') && PEAR_RUNTYPE == 'pecl') { + $ret = 'pecl.php.net'; + } + + if ($ret) { + if ($ret != 'pear.php.net') { + $this->_lazyChannelSetup(); + } + + return $ret; + } + + return PEAR_CONFIG_DEFAULT_CHANNEL; + } + + /** + * Returns a configuration value, prioritizing layers as per the + * layers property. + * + * @param string config key + * @return mixed the config value, or NULL if not found + * @access public + */ + function get($key, $layer = null, $channel = false) + { + if (!isset($this->configuration_info[$key])) { + return null; + } + + if ($key == '__channels') { + return null; + } + + if ($key == 'default_channel') { + return $this->getDefaultChannel($layer); + } + + if (!$channel) { + $channel = $this->getDefaultChannel(); + } elseif ($channel != 'pear.php.net') { + $this->_lazyChannelSetup(); + } + $channel = strtolower($channel); + + $test = (in_array($key, $this->_channelConfigInfo)) ? + $this->_getChannelValue($key, $layer, $channel) : + null; + if ($test !== null) { + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + return $test; + } + + if ($layer === null) { + foreach ($this->layers as $layer) { + if (isset($this->configuration[$layer][$key])) { + $test = $this->configuration[$layer][$key]; + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + + if ($key == 'preferred_mirror') { + $reg = &$this->getRegistry(); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($test) && $chan->getName() != $test) { + return $channel; // mirror does not exist + } + } + } + return $test; + } + } + } elseif (isset($this->configuration[$layer][$key])) { + $test = $this->configuration[$layer][$key]; + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + + if ($key == 'preferred_mirror') { + $reg = &$this->getRegistry(); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($test) && $chan->getName() != $test) { + return $channel; // mirror does not exist + } + } + } + + return $test; + } + + return null; + } + + /** + * Returns a channel-specific configuration value, prioritizing layers as per the + * layers property. + * + * @param string config key + * @return mixed the config value, or NULL if not found + * @access private + */ + function _getChannelValue($key, $layer, $channel) + { + if ($key == '__channels' || $channel == 'pear.php.net') { + return null; + } + + $ret = null; + if ($layer === null) { + foreach ($this->layers as $ilayer) { + if (isset($this->configuration[$ilayer]['__channels'][$channel][$key])) { + $ret = $this->configuration[$ilayer]['__channels'][$channel][$key]; + break; + } + } + } elseif (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + $ret = $this->configuration[$layer]['__channels'][$channel][$key]; + } + + if ($key != 'preferred_mirror') { + return $ret; + } + + + if ($ret !== null) { + $reg = &$this->getRegistry($layer); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($ret) && $chan->getName() != $ret) { + return $channel; // mirror does not exist + } + } + + return $ret; + } + + if ($channel != $this->getDefaultChannel($layer)) { + return $channel; // we must use the channel name as the preferred mirror + // if the user has not chosen an alternate + } + + return $this->getDefaultChannel($layer); + } + + /** + * Set a config value in a specific layer (defaults to 'user'). + * Enforces the types defined in the configuration_info array. An + * integer config variable will be cast to int, and a set config + * variable will be validated against its legal values. + * + * @param string config key + * @param string config value + * @param string (optional) config layer + * @param string channel to set this value for, or null for global value + * @return bool TRUE on success, FALSE on failure + */ + function set($key, $value, $layer = 'user', $channel = false) + { + if ($key == '__channels') { + return false; + } + + if (!isset($this->configuration[$layer])) { + return false; + } + + if ($key == 'default_channel') { + // can only set this value globally + $channel = 'pear.php.net'; + if ($value != 'pear.php.net') { + $this->_lazyChannelSetup($layer); + } + } + + if ($key == 'preferred_mirror') { + if ($channel == '__uri') { + return false; // can't set the __uri pseudo-channel's mirror + } + + $reg = &$this->getRegistry($layer); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel ? $channel : 'pear.php.net'); + if (PEAR::isError($chan)) { + return false; + } + + if (!$chan->getMirror($value) && $chan->getName() != $value) { + return false; // mirror does not exist + } + } + } + + if (!isset($this->configuration_info[$key])) { + return false; + } + + extract($this->configuration_info[$key]); + switch ($type) { + case 'integer': + $value = (int)$value; + break; + case 'set': { + // If a valid_set is specified, require the value to + // be in the set. If there is no valid_set, accept + // any value. + if ($valid_set) { + reset($valid_set); + if ((key($valid_set) === 0 && !in_array($value, $valid_set)) || + (key($valid_set) !== 0 && empty($valid_set[$value]))) + { + return false; + } + } + break; + } + } + + if (!$channel) { + $channel = $this->get('default_channel', null, 'pear.php.net'); + } + + if (!in_array($channel, $this->_channels)) { + $this->_lazyChannelSetup($layer); + $reg = &$this->getRegistry($layer); + if ($reg) { + $channel = $reg->channelName($channel); + } + + if (!in_array($channel, $this->_channels)) { + return false; + } + } + + if ($channel != 'pear.php.net') { + if (in_array($key, $this->_channelConfigInfo)) { + $this->configuration[$layer]['__channels'][$channel][$key] = $value; + return true; + } + + return false; + } + + if ($key == 'default_channel') { + if (!isset($reg)) { + $reg = &$this->getRegistry($layer); + if (!$reg) { + $reg = &$this->getRegistry(); + } + } + + if ($reg) { + $value = $reg->channelName($value); + } + + if (!$value) { + return false; + } + } + + $this->configuration[$layer][$key] = $value; + if ($key == 'php_dir' && !$this->_noRegistry) { + if (!isset($this->_registry[$layer]) || + $value != $this->_registry[$layer]->install_dir) { + $this->_registry[$layer] = &new PEAR_Registry($value); + $this->_regInitialized[$layer] = false; + $this->_registry[$layer]->setConfig($this, false); + } + } + + return true; + } + + function _lazyChannelSetup($uselayer = false) + { + if ($this->_noRegistry) { + return; + } + + $merge = false; + foreach ($this->_registry as $layer => $p) { + if ($uselayer && $uselayer != $layer) { + continue; + } + + if (!$this->_regInitialized[$layer]) { + if ($layer == 'default' && isset($this->_registry['user']) || + isset($this->_registry['system'])) { + // only use the default registry if there are no alternatives + continue; + } + + if (!is_object($this->_registry[$layer])) { + if ($phpdir = $this->get('php_dir', $layer, 'pear.php.net')) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + return; + } + } + + $this->setChannels($this->_registry[$layer]->listChannels(), $merge); + $this->_regInitialized[$layer] = true; + $merge = true; + } + } + } + + /** + * Set the list of channels. + * + * This should be set via a call to {@link PEAR_Registry::listChannels()} + * @param array + * @param bool + * @return bool success of operation + */ + function setChannels($channels, $merge = false) + { + if (!is_array($channels)) { + return false; + } + + if ($merge) { + $this->_channels = array_merge($this->_channels, $channels); + } else { + $this->_channels = $channels; + } + + foreach ($channels as $channel) { + $channel = strtolower($channel); + if ($channel == 'pear.php.net') { + continue; + } + + foreach ($this->layers as $layer) { + if (!isset($this->configuration[$layer]['__channels'])) { + $this->configuration[$layer]['__channels'] = array(); + } + if (!isset($this->configuration[$layer]['__channels'][$channel]) + || !is_array($this->configuration[$layer]['__channels'][$channel])) { + $this->configuration[$layer]['__channels'][$channel] = array(); + } + } + } + + return true; + } + + /** + * Get the type of a config value. + * + * @param string config key + * + * @return string type, one of "string", "integer", "file", + * "directory", "set" or "password". + * + * @access public + * + */ + function getType($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['type']; + } + return false; + } + + /** + * Get the documentation for a config value. + * + * @param string config key + * @return string documentation string + * + * @access public + * + */ + function getDocs($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['doc']; + } + + return false; + } + + /** + * Get the short documentation for a config value. + * + * @param string config key + * @return string short documentation string + * + * @access public + * + */ + function getPrompt($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['prompt']; + } + + return false; + } + + /** + * Get the parameter group for a config key. + * + * @param string config key + * @return string parameter group + * + * @access public + * + */ + function getGroup($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['group']; + } + + return false; + } + + /** + * Get the list of parameter groups. + * + * @return array list of parameter groups + * + * @access public + * + */ + function getGroups() + { + $tmp = array(); + foreach ($this->configuration_info as $key => $info) { + $tmp[$info['group']] = 1; + } + + return array_keys($tmp); + } + + /** + * Get the list of the parameters in a group. + * + * @param string $group parameter group + * @return array list of parameters in $group + * + * @access public + * + */ + function getGroupKeys($group) + { + $keys = array(); + foreach ($this->configuration_info as $key => $info) { + if ($info['group'] == $group) { + $keys[] = $key; + } + } + + return $keys; + } + + /** + * Get the list of allowed set values for a config value. Returns + * NULL for config values that are not sets. + * + * @param string config key + * @return array enumerated array of set values, or NULL if the + * config key is unknown or not a set + * + * @access public + * + */ + function getSetValues($key) + { + if (isset($this->configuration_info[$key]) && + isset($this->configuration_info[$key]['type']) && + $this->configuration_info[$key]['type'] == 'set') + { + $valid_set = $this->configuration_info[$key]['valid_set']; + reset($valid_set); + if (key($valid_set) === 0) { + return $valid_set; + } + + return array_keys($valid_set); + } + + return null; + } + + /** + * Get all the current config keys. + * + * @return array simple array of config keys + * + * @access public + */ + function getKeys() + { + $keys = array(); + foreach ($this->layers as $layer) { + $test = $this->configuration[$layer]; + if (isset($test['__channels'])) { + foreach ($test['__channels'] as $channel => $configs) { + $keys = array_merge($keys, $configs); + } + } + + unset($test['__channels']); + $keys = array_merge($keys, $test); + + } + return array_keys($keys); + } + + /** + * Remove the a config key from a specific config layer. + * + * @param string config key + * @param string (optional) config layer + * @param string (optional) channel (defaults to default channel) + * @return bool TRUE on success, FALSE on failure + * + * @access public + */ + function remove($key, $layer = 'user', $channel = null) + { + if ($channel === null) { + $channel = $this->getDefaultChannel(); + } + + if ($channel !== 'pear.php.net') { + if (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + unset($this->configuration[$layer]['__channels'][$channel][$key]); + return true; + } + } + + if (isset($this->configuration[$layer][$key])) { + unset($this->configuration[$layer][$key]); + return true; + } + + return false; + } + + /** + * Temporarily remove an entire config layer. USE WITH CARE! + * + * @param string config key + * @param string (optional) config layer + * @return bool TRUE on success, FALSE on failure + * + * @access public + */ + function removeLayer($layer) + { + if (isset($this->configuration[$layer])) { + $this->configuration[$layer] = array(); + return true; + } + + return false; + } + + /** + * Stores configuration data in a layer. + * + * @param string config layer to store + * @return bool TRUE on success, or PEAR error on failure + * + * @access public + */ + function store($layer = 'user', $data = null) + { + return $this->writeConfigFile(null, $layer, $data); + } + + /** + * Tells what config layer that gets to define a key. + * + * @param string config key + * @param boolean return the defining channel + * + * @return string|array the config layer, or an empty string if not found. + * + * if $returnchannel, the return is an array array('layer' => layername, + * 'channel' => channelname), or an empty string if not found + * + * @access public + */ + function definedBy($key, $returnchannel = false) + { + foreach ($this->layers as $layer) { + $channel = $this->getDefaultChannel(); + if ($channel !== 'pear.php.net') { + if (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + if ($returnchannel) { + return array('layer' => $layer, 'channel' => $channel); + } + return $layer; + } + } + + if (isset($this->configuration[$layer][$key])) { + if ($returnchannel) { + return array('layer' => $layer, 'channel' => 'pear.php.net'); + } + return $layer; + } + } + + return ''; + } + + /** + * Tells whether a given key exists as a config value. + * + * @param string config key + * @return bool whether exists in this object + * + * @access public + */ + function isDefined($key) + { + foreach ($this->layers as $layer) { + if (isset($this->configuration[$layer][$key])) { + return true; + } + } + + return false; + } + + /** + * Tells whether a given config layer exists. + * + * @param string config layer + * @return bool whether exists in this object + * + * @access public + */ + function isDefinedLayer($layer) + { + return isset($this->configuration[$layer]); + } + + /** + * Returns the layers defined (except the 'default' one) + * + * @return array of the defined layers + */ + function getLayers() + { + $cf = $this->configuration; + unset($cf['default']); + return array_keys($cf); + } + + function apiVersion() + { + return '1.1'; + } + + /** + * @return PEAR_Registry + */ + function &getRegistry($use = null) + { + $layer = $use === null ? 'user' : $use; + if (isset($this->_registry[$layer])) { + return $this->_registry[$layer]; + } elseif ($use === null && isset($this->_registry['system'])) { + return $this->_registry['system']; + } elseif ($use === null && isset($this->_registry['default'])) { + return $this->_registry['default']; + } elseif ($use) { + $a = false; + return $a; + } + + // only go here if null was passed in + echo "CRITICAL ERROR: Registry could not be initialized from any value"; + exit(1); + } + + /** + * This is to allow customization like the use of installroot + * @param PEAR_Registry + * @return bool + */ + function setRegistry(&$reg, $layer = 'user') + { + if ($this->_noRegistry) { + return false; + } + + if (!in_array($layer, array('user', 'system'))) { + return false; + } + + $this->_registry[$layer] = &$reg; + if (is_object($reg)) { + $this->_registry[$layer]->setConfig($this, false); + } + + return true; + } + + function noRegistry() + { + $this->_noRegistry = true; + } + + /** + * @return PEAR_REST + */ + function &getREST($version, $options = array()) + { + $version = str_replace('.', '', $version); + if (!class_exists($class = 'PEAR_REST_' . $version)) { + require_once 'phar://go-pear.phar/' . 'PEAR/REST/' . $version . '.php'; + } + + $remote = &new $class($this, $options); + return $remote; + } + + /** + * The ftp server is set in {@link readFTPConfigFile()}. It exists only if a + * remote configuration file has been specified + * @return PEAR_FTP|false + */ + function &getFTP() + { + if (isset($this->_ftp)) { + return $this->_ftp; + } + + $a = false; + return $a; + } + + function _prependPath($path, $prepend) + { + if (strlen($prepend) > 0) { + if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) { + if (preg_match('/^[a-z]:/i', $prepend)) { + $prepend = substr($prepend, 2); + } elseif ($prepend{0} != '\\') { + $prepend = "\\$prepend"; + } + $path = substr($path, 0, 2) . $prepend . substr($path, 2); + } else { + $path = $prepend . $path; + } + } + return $path; + } + + /** + * @param string|false installation directory to prepend to all _dir variables, or false to + * disable + */ + function setInstallRoot($root) + { + if (substr($root, -1) == DIRECTORY_SEPARATOR) { + $root = substr($root, 0, -1); + } + $old = $this->_installRoot; + $this->_installRoot = $root; + if (($old != $root) && !$this->_noRegistry) { + foreach (array_keys($this->_registry) as $layer) { + if ($layer == 'ftp' || !isset($this->_registry[$layer])) { + continue; + } + $this->_registry[$layer] = + &new PEAR_Registry($this->get('php_dir', $layer, 'pear.php.net')); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } + } + } +} + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Dependency2.php 286494 2009-07-29 06:57:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Required for the PEAR_VALIDATE_* constants + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Validate.php'; + +/** + * Dependency check for PEAR packages + * + * This class handles both version 1.0 and 2.0 dependencies + * WARNING: *any* changes to this class must be duplicated in the + * test_PEAR_Dependency2 class found in tests/PEAR_Dependency2/setup.php.inc, + * or unit tests will not actually validate the changes + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Dependency2 +{ + /** + * One of the PEAR_VALIDATE_* states + * @see PEAR_VALIDATE_NORMAL + * @var integer + */ + var $_state; + + /** + * Command-line options to install/upgrade/uninstall commands + * @param array + */ + var $_options; + + /** + * @var OS_Guess + */ + var $_os; + + /** + * @var PEAR_Registry + */ + var $_registry; + + /** + * @var PEAR_Config + */ + var $_config; + + /** + * @var PEAR_DependencyDB + */ + var $_dependencydb; + + /** + * Output of PEAR_Registry::parsedPackageName() + * @var array + */ + var $_currentPackage; + + /** + * @param PEAR_Config + * @param array installation options + * @param array format of PEAR_Registry::parsedPackageName() + * @param int installation state (one of PEAR_VALIDATE_*) + */ + function PEAR_Dependency2(&$config, $installoptions, $package, + $state = PEAR_VALIDATE_INSTALLING) + { + $this->_config = &$config; + if (!class_exists('PEAR_DependencyDB')) { + require_once 'phar://go-pear.phar/' . 'PEAR/DependencyDB.php'; + } + + if (isset($installoptions['packagingroot'])) { + // make sure depdb is in the right location + $config->setInstallRoot($installoptions['packagingroot']); + } + + $this->_registry = &$config->getRegistry(); + $this->_dependencydb = &PEAR_DependencyDB::singleton($config); + if (isset($installoptions['packagingroot'])) { + $config->setInstallRoot(false); + } + + $this->_options = $installoptions; + $this->_state = $state; + if (!class_exists('OS_Guess')) { + require_once 'phar://go-pear.phar/' . 'OS/Guess.php'; + } + + $this->_os = new OS_Guess; + $this->_currentPackage = $package; + } + + function _getExtraString($dep) + { + $extra = ' ('; + if (isset($dep['uri'])) { + return ''; + } + + if (isset($dep['recommended'])) { + $extra .= 'recommended version ' . $dep['recommended']; + } else { + if (isset($dep['min'])) { + $extra .= 'version >= ' . $dep['min']; + } + + if (isset($dep['max'])) { + if ($extra != ' (') { + $extra .= ', '; + } + $extra .= 'version <= ' . $dep['max']; + } + + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if ($extra != ' (') { + $extra .= ', '; + } + + $extra .= 'excluded versions: '; + foreach ($dep['exclude'] as $i => $exclude) { + if ($i) { + $extra .= ', '; + } + $extra .= $exclude; + } + } + } + + $extra .= ')'; + if ($extra == ' ()') { + $extra = ''; + } + + return $extra; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getPHP_OS() + { + return PHP_OS; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getsysname() + { + return $this->_os->getSysname(); + } + + /** + * Specify a dependency on an OS. Use arch for detailed os/processor information + * + * There are two generic OS dependencies that will be the most common, unix and windows. + * Other options are linux, freebsd, darwin (OS X), sunos, irix, hpux, aix + */ + function validateOsDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + if ($dep['name'] == '*') { + return true; + } + + $not = isset($dep['conflicts']) ? true : false; + switch (strtolower($dep['name'])) { + case 'windows' : + if ($not) { + if (strtolower(substr($this->getPHP_OS(), 0, 3)) == 'win') { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Cannot install %s on Windows"); + } + + return $this->warning("warning: Cannot install %s on Windows"); + } + } else { + if (strtolower(substr($this->getPHP_OS(), 0, 3)) != 'win') { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Can only install %s on Windows"); + } + + return $this->warning("warning: Can only install %s on Windows"); + } + } + break; + case 'unix' : + $unices = array('linux', 'freebsd', 'darwin', 'sunos', 'irix', 'hpux', 'aix'); + if ($not) { + if (in_array($this->getSysname(), $unices)) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Cannot install %s on any Unix system"); + } + + return $this->warning( "warning: Cannot install %s on any Unix system"); + } + } else { + if (!in_array($this->getSysname(), $unices)) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Can only install %s on a Unix system"); + } + + return $this->warning("warning: Can only install %s on a Unix system"); + } + } + break; + default : + if ($not) { + if (strtolower($dep['name']) == strtolower($this->getSysname())) { + if (!isset($this->_options['nodeps']) && + !isset($this->_options['force'])) { + return $this->raiseError('Cannot install %s on ' . $dep['name'] . + ' operating system'); + } + + return $this->warning('warning: Cannot install %s on ' . + $dep['name'] . ' operating system'); + } + } else { + if (strtolower($dep['name']) != strtolower($this->getSysname())) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('Cannot install %s on ' . + $this->getSysname() . + ' operating system, can only install on ' . $dep['name']); + } + + return $this->warning('warning: Cannot install %s on ' . + $this->getSysname() . + ' operating system, can only install on ' . $dep['name']); + } + } + } + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function matchSignature($pattern) + { + return $this->_os->matchSignature($pattern); + } + + /** + * Specify a complex dependency on an OS/processor/kernel version, + * Use OS for simple operating system dependency. + * + * This is the only dependency that accepts an eregable pattern. The pattern + * will be matched against the php_uname() output parsed by OS_Guess + */ + function validateArchDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING) { + return true; + } + + $not = isset($dep['conflicts']) ? true : false; + if (!$this->matchSignature($dep['pattern'])) { + if (!$not) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s Architecture dependency failed, does not ' . + 'match "' . $dep['pattern'] . '"'); + } + + return $this->warning('warning: %s Architecture dependency failed, does ' . + 'not match "' . $dep['pattern'] . '"'); + } + + return true; + } + + if ($not) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s Architecture dependency failed, required "' . + $dep['pattern'] . '"'); + } + + return $this->warning('warning: %s Architecture dependency failed, ' . + 'required "' . $dep['pattern'] . '"'); + } + + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function extension_loaded($name) + { + return extension_loaded($name); + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function phpversion($name = null) + { + if ($name !== null) { + return phpversion($name); + } + + return phpversion(); + } + + function validateExtensionDependency($dep, $required = true) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + $loaded = $this->extension_loaded($dep['name']); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (!isset($dep['min']) && !isset($dep['max']) && + !isset($dep['recommended']) && !isset($dep['exclude']) + ) { + if ($loaded) { + if (isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return true; + } + + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('warning: %s requires PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('%s can optionally use PHP extension "' . + $dep['name'] . '"' . $extra); + } + + if (!$loaded) { + if (isset($dep['conflicts'])) { + return true; + } + + if (!$required) { + return $this->warning('%s can optionally use PHP extension "' . + $dep['name'] . '"' . $extra); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . $dep['name'] . + '"' . $extra); + } + + return $this->warning('warning: %s requires PHP extension "' . $dep['name'] . + '"' . $extra); + } + + $version = (string) $this->phpversion($dep['name']); + if (empty($version)) { + $version = '0'; + } + + $fail = false; + if (isset($dep['min']) && !version_compare($version, $dep['min'], '>=')) { + $fail = true; + } + + if (isset($dep['max']) && !version_compare($version, $dep['max'], '<=')) { + $fail = true; + } + + if ($fail && !isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . $dep['name'] . + '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP extension "' . $dep['name'] . + '"' . $extra . ', installed version is ' . $version); + } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail && isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + if (isset($dep['exclude'])) { + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==')) { + if (isset($dep['conflicts'])) { + continue; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PHP extension "' . + $dep['name'] . '" version ' . + $exclude); + } + + return $this->warning('warning: %s is not compatible with PHP extension "' . + $dep['name'] . '" version ' . + $exclude); + } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + } + } + + if (isset($dep['recommended'])) { + if (version_compare($version, $dep['recommended'], '==')) { + return true; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s dependency: PHP extension ' . $dep['name'] . + ' version "' . $version . '"' . + ' is not the recommended version "' . $dep['recommended'] . + '", but may be compatible, use --force to install'); + } + + return $this->warning('warning: %s dependency: PHP extension ' . + $dep['name'] . ' version "' . $version . '"' . + ' is not the recommended version "' . $dep['recommended'].'"'); + } + + return true; + } + + function validatePhpDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + $version = $this->phpversion(); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (isset($dep['min'])) { + if (!version_compare($version, $dep['min'], '>=')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP' . + $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP' . + $extra . ', installed version is ' . $version); + } + } + + if (isset($dep['max'])) { + if (!version_compare($version, $dep['max'], '<=')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP' . + $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP' . + $extra . ', installed version is ' . $version); + } + } + + if (isset($dep['exclude'])) { + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PHP version ' . + $exclude); + } + + return $this->warning( + 'warning: %s is not compatible with PHP version ' . + $exclude); + } + } + } + + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getPEARVersion() + { + return '1.9.0'; + } + + function validatePearinstallerDependency($dep) + { + $pearversion = $this->getPEARVersion(); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (version_compare($pearversion, $dep['min'], '<')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + return $this->warning('warning: %s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + if (isset($dep['max'])) { + if (version_compare($pearversion, $dep['max'], '>')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + return $this->warning('warning: %s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + } + + if (isset($dep['exclude'])) { + if (!isset($dep['exclude'][0])) { + $dep['exclude'] = array($dep['exclude']); + } + + foreach ($dep['exclude'] as $exclude) { + if (version_compare($exclude, $pearversion, '==')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PEAR Installer ' . + 'version ' . $exclude); + } + + return $this->warning('warning: %s is not compatible with PEAR ' . + 'Installer version ' . $exclude); + } + } + } + + return true; + } + + function validateSubpackageDependency($dep, $required, $params) + { + return $this->validatePackageDependency($dep, $required, $params); + } + + /** + * @param array dependency information (2.0 format) + * @param boolean whether this is a required dependency + * @param array a list of downloaded packages to be installed, if any + * @param boolean if true, then deps on pear.php.net that fail will also check + * against pecl.php.net packages to accomodate extensions that have + * moved to pecl.php.net from pear.php.net + */ + function validatePackageDependency($dep, $required, $params, $depv1 = false) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + if (isset($dep['providesextension'])) { + if ($this->extension_loaded($dep['providesextension'])) { + $save = $dep; + $subdep = $dep; + $subdep['name'] = $subdep['providesextension']; + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->validateExtensionDependency($subdep, $required); + PEAR::popErrorHandling(); + if (!PEAR::isError($ret)) { + return true; + } + } + } + + if ($this->_state == PEAR_VALIDATE_INSTALLING) { + return $this->_validatePackageInstall($dep, $required, $depv1); + } + + if ($this->_state == PEAR_VALIDATE_DOWNLOADING) { + return $this->_validatePackageDownload($dep, $required, $params, $depv1); + } + } + + function _validatePackageDownload($dep, $required, $params, $depv1 = false) + { + $dep['package'] = $dep['name']; + if (isset($dep['uri'])) { + $dep['channel'] = '__uri'; + } + + $depname = $this->_registry->parsedPackageNameToString($dep, true); + $found = false; + foreach ($params as $param) { + if ($param->isEqual( + array('package' => $dep['name'], + 'channel' => $dep['channel']))) { + $found = true; + break; + } + + if ($depv1 && $dep['channel'] == 'pear.php.net') { + if ($param->isEqual( + array('package' => $dep['name'], + 'channel' => 'pecl.php.net'))) { + $found = true; + break; + } + } + } + + if (!$found && isset($dep['providesextension'])) { + foreach ($params as $param) { + if ($param->isExtension($dep['providesextension'])) { + $found = true; + break; + } + } + } + + if ($found) { + $version = $param->getVersion(); + $installed = false; + $downloaded = true; + } else { + if ($this->_registry->packageExists($dep['name'], $dep['channel'])) { + $installed = true; + $downloaded = false; + $version = $this->_registry->packageinfo($dep['name'], 'version', + $dep['channel']); + } else { + if ($dep['channel'] == 'pecl.php.net' && $this->_registry->packageExists($dep['name'], + 'pear.php.net')) { + $installed = true; + $downloaded = false; + $version = $this->_registry->packageinfo($dep['name'], 'version', + 'pear.php.net'); + } else { + $version = 'not installed or downloaded'; + $installed = false; + $downloaded = false; + } + } + } + + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude']) && !is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if (!isset($dep['min']) && !isset($dep['max']) && + !isset($dep['recommended']) && !isset($dep['exclude']) + ) { + if ($installed || $downloaded) { + $installed = $installed ? 'installed' : 'downloaded'; + if (isset($dep['conflicts'])) { + $rest = ''; + if ($version) { + $rest = ", $installed version is " . $version; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra . $rest); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . $extra . $rest); + } + + return true; + } + + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('%s can optionally use package "' . $depname . '"' . $extra); + } + + if (!$installed && !$downloaded) { + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('%s can optionally use package "' . $depname . '"' . $extra); + } + + $fail = false; + if (isset($dep['min']) && version_compare($version, $dep['min'], '<')) { + $fail = true; + } + + if (isset($dep['max']) && version_compare($version, $dep['max'], '>')) { + $fail = true; + } + + if ($fail && !isset($dep['conflicts'])) { + $installed = $installed ? 'installed' : 'downloaded'; + $dep['package'] = $dep['name']; + $dep = $this->_registry->parsedPackageNameToString($dep, true); + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail && + isset($dep['conflicts']) && !isset($dep['exclude'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra . + ", $installed version is " . $version); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + if (isset($dep['exclude'])) { + $installed = $installed ? 'installed' : 'downloaded'; + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==') && !isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && + !isset($this->_options['force']) + ) { + return $this->raiseError('%s is not compatible with ' . + $installed . ' package "' . + $depname . '" version ' . + $exclude); + } + + return $this->warning('warning: %s is not compatible with ' . + $installed . ' package "' . + $depname . '" version ' . + $exclude); + } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + } + } + + if (isset($dep['recommended'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (version_compare($version, $dep['recommended'], '==')) { + return true; + } + + if (!$found && $installed) { + $param = $this->_registry->getPackage($dep['name'], $dep['channel']); + } + + if ($param) { + $found = false; + foreach ($params as $parent) { + if ($parent->isEqual($this->_currentPackage)) { + $found = true; + break; + } + } + + if ($found) { + if ($param->isCompatible($parent)) { + return true; + } + } else { // this is for validPackage() calls + $parent = $this->_registry->getPackage($this->_currentPackage['package'], + $this->_currentPackage['channel']); + if ($parent !== null && $param->isCompatible($parent)) { + return true; + } + } + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force']) && + !isset($this->_options['loose']) + ) { + return $this->raiseError('%s dependency package "' . $depname . + '" ' . $installed . ' version ' . $version . + ' is not the recommended version ' . $dep['recommended'] . + ', but may be compatible, use --force to install'); + } + + return $this->warning('warning: %s dependency package "' . $depname . + '" ' . $installed . ' version ' . $version . + ' is not the recommended version ' . $dep['recommended']); + } + + return true; + } + + function _validatePackageInstall($dep, $required, $depv1 = false) + { + return $this->_validatePackageDownload($dep, $required, array(), $depv1); + } + + /** + * Verify that uninstalling packages passed in to command line is OK. + * + * @param PEAR_Installer $dl + * @return PEAR_Error|true + */ + function validatePackageUninstall(&$dl) + { + if (PEAR::isError($this->_dependencydb)) { + return $this->_dependencydb; + } + + $params = array(); + // construct an array of "downloaded" packages to fool the package dependency checker + // into using these to validate uninstalls of circular dependencies + $downloaded = &$dl->getUninstallPackages(); + foreach ($downloaded as $i => $pf) { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Downloader/Package.php'; + } + $dp = &new PEAR_Downloader_Package($dl); + $dp->setPackageFile($downloaded[$i]); + $params[$i] = &$dp; + } + + // check cache + $memyselfandI = strtolower($this->_currentPackage['channel']) . '/' . + strtolower($this->_currentPackage['package']); + if (isset($dl->___uninstall_package_cache)) { + $badpackages = $dl->___uninstall_package_cache; + if (isset($badpackages[$memyselfandI]['warnings'])) { + foreach ($badpackages[$memyselfandI]['warnings'] as $warning) { + $dl->log(0, $warning[0]); + } + } + + if (isset($badpackages[$memyselfandI]['errors'])) { + foreach ($badpackages[$memyselfandI]['errors'] as $error) { + if (is_array($error)) { + $dl->log(0, $error[0]); + } else { + $dl->log(0, $error->getMessage()); + } + } + + if (isset($this->_options['nodeps']) || isset($this->_options['force'])) { + return $this->warning( + 'warning: %s should not be uninstalled, other installed packages depend ' . + 'on this package'); + } + + return $this->raiseError( + '%s cannot be uninstalled, other installed packages depend on this package'); + } + + return true; + } + + // first, list the immediate parents of each package to be uninstalled + $perpackagelist = array(); + $allparents = array(); + foreach ($params as $i => $param) { + $a = array( + 'channel' => strtolower($param->getChannel()), + 'package' => strtolower($param->getPackage()) + ); + + $deps = $this->_dependencydb->getDependentPackages($a); + if ($deps) { + foreach ($deps as $d) { + $pardeps = $this->_dependencydb->getDependencies($d); + foreach ($pardeps as $dep) { + if (strtolower($dep['dep']['channel']) == $a['channel'] && + strtolower($dep['dep']['name']) == $a['package']) { + if (!isset($perpackagelist[$a['channel'] . '/' . $a['package']])) { + $perpackagelist[$a['channel'] . '/' . $a['package']] = array(); + } + $perpackagelist[$a['channel'] . '/' . $a['package']][] + = array($d['channel'] . '/' . $d['package'], $dep); + if (!isset($allparents[$d['channel'] . '/' . $d['package']])) { + $allparents[$d['channel'] . '/' . $d['package']] = array(); + } + if (!isset($allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']])) { + $allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']] = array(); + } + $allparents[$d['channel'] . '/' . $d['package']] + [$a['channel'] . '/' . $a['package']][] + = array($d, $dep); + } + } + } + } + } + + // next, remove any packages from the parents list that are not installed + $remove = array(); + foreach ($allparents as $parent => $d1) { + foreach ($d1 as $d) { + if ($this->_registry->packageExists($d[0][0]['package'], $d[0][0]['channel'])) { + continue; + } + $remove[$parent] = true; + } + } + + // next remove any packages from the parents list that are not passed in for + // uninstallation + foreach ($allparents as $parent => $d1) { + foreach ($d1 as $d) { + foreach ($params as $param) { + if (strtolower($param->getChannel()) == $d[0][0]['channel'] && + strtolower($param->getPackage()) == $d[0][0]['package']) { + // found it + continue 3; + } + } + $remove[$parent] = true; + } + } + + // remove all packages whose dependencies fail + // save which ones failed for error reporting + $badchildren = array(); + do { + $fail = false; + foreach ($remove as $package => $unused) { + if (!isset($allparents[$package])) { + continue; + } + + foreach ($allparents[$package] as $kid => $d1) { + foreach ($d1 as $depinfo) { + if ($depinfo[1]['type'] != 'optional') { + if (isset($badchildren[$kid])) { + continue; + } + $badchildren[$kid] = true; + $remove[$kid] = true; + $fail = true; + continue 2; + } + } + } + if ($fail) { + // start over, we removed some children + continue 2; + } + } + } while ($fail); + + // next, construct the list of packages that can't be uninstalled + $badpackages = array(); + $save = $this->_currentPackage; + foreach ($perpackagelist as $package => $packagedeps) { + foreach ($packagedeps as $parent) { + if (!isset($remove[$parent[0]])) { + continue; + } + + $packagename = $this->_registry->parsePackageName($parent[0]); + $packagename['channel'] = $this->_registry->channelAlias($packagename['channel']); + $pa = $this->_registry->getPackage($packagename['package'], $packagename['channel']); + $packagename['package'] = $pa->getPackage(); + $this->_currentPackage = $packagename; + // parent is not present in uninstall list, make sure we can actually + // uninstall it (parent dep is optional) + $parentname['channel'] = $this->_registry->channelAlias($parent[1]['dep']['channel']); + $pa = $this->_registry->getPackage($parent[1]['dep']['name'], $parent[1]['dep']['channel']); + $parentname['package'] = $pa->getPackage(); + $parent[1]['dep']['package'] = $parentname['package']; + $parent[1]['dep']['channel'] = $parentname['channel']; + if ($parent[1]['type'] == 'optional') { + $test = $this->_validatePackageUninstall($parent[1]['dep'], false, $dl); + if ($test !== true) { + $badpackages[$package]['warnings'][] = $test; + } + } else { + $test = $this->_validatePackageUninstall($parent[1]['dep'], true, $dl); + if ($test !== true) { + $badpackages[$package]['errors'][] = $test; + } + } + } + } + + $this->_currentPackage = $save; + $dl->___uninstall_package_cache = $badpackages; + if (isset($badpackages[$memyselfandI])) { + if (isset($badpackages[$memyselfandI]['warnings'])) { + foreach ($badpackages[$memyselfandI]['warnings'] as $warning) { + $dl->log(0, $warning[0]); + } + } + + if (isset($badpackages[$memyselfandI]['errors'])) { + foreach ($badpackages[$memyselfandI]['errors'] as $error) { + if (is_array($error)) { + $dl->log(0, $error[0]); + } else { + $dl->log(0, $error->getMessage()); + } + } + + if (isset($this->_options['nodeps']) || isset($this->_options['force'])) { + return $this->warning( + 'warning: %s should not be uninstalled, other installed packages depend ' . + 'on this package'); + } + + return $this->raiseError( + '%s cannot be uninstalled, other installed packages depend on this package'); + } + } + + return true; + } + + function _validatePackageUninstall($dep, $required, $dl) + { + $depname = $this->_registry->parsedPackageNameToString($dep, true); + $version = $this->_registry->packageinfo($dep['package'], 'version', $dep['channel']); + if (!$version) { + return true; + } + + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude']) && !is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if (isset($dep['conflicts'])) { + return true; // uninstall OK - these packages conflict (probably installed with --force) + } + + if (!isset($dep['min']) && !isset($dep['max'])) { + if (!$required) { + return $this->warning('"' . $depname . '" can be optionally used by ' . + 'installed package %s' . $extra); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('"' . $depname . '" is required by ' . + 'installed package %s' . $extra); + } + + return $this->warning('warning: "' . $depname . '" is required by ' . + 'installed package %s' . $extra); + } + + $fail = false; + if (isset($dep['min']) && version_compare($version, $dep['min'], '>=')) { + $fail = true; + } + + if (isset($dep['max']) && version_compare($version, $dep['max'], '<=')) { + $fail = true; + } + + // we re-use this variable, preserve the original value + $saverequired = $required; + if (!$required) { + return $this->warning($depname . $extra . ' can be optionally used by installed package' . + ' "%s"'); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError($depname . $extra . ' is required by installed package' . + ' "%s"'); + } + + return $this->raiseError('warning: ' . $depname . $extra . + ' is required by installed package "%s"'); + } + + /** + * validate a downloaded package against installed packages + * + * As of PEAR 1.4.3, this will only validate + * + * @param array|PEAR_Downloader_Package|PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * $pkg package identifier (either + * array('package' => blah, 'channel' => blah) or an array with + * index 'info' referencing an object) + * @param PEAR_Downloader $dl + * @param array $params full list of packages to install + * @return true|PEAR_Error + */ + function validatePackage($pkg, &$dl, $params = array()) + { + if (is_array($pkg) && isset($pkg['info'])) { + $deps = $this->_dependencydb->getDependentPackageDependencies($pkg['info']); + } else { + $deps = $this->_dependencydb->getDependentPackageDependencies($pkg); + } + + $fail = false; + if ($deps) { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Downloader/Package.php'; + } + + $dp = &new PEAR_Downloader_Package($dl); + if (is_object($pkg)) { + $dp->setPackageFile($pkg); + } else { + $dp->setDownloadURL($pkg); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($deps as $channel => $info) { + foreach ($info as $package => $ds) { + foreach ($params as $packd) { + if (strtolower($packd->getPackage()) == strtolower($package) && + $packd->getChannel() == $channel) { + $dl->log(3, 'skipping installed package check of "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $channel, 'package' => $package), + true) . + '", version "' . $packd->getVersion() . '" will be ' . + 'downloaded and installed'); + continue 2; // jump to next package + } + } + + foreach ($ds as $d) { + $checker = &new PEAR_Dependency2($this->_config, $this->_options, + array('channel' => $channel, 'package' => $package), $this->_state); + $dep = $d['dep']; + $required = $d['type'] == 'required'; + $ret = $checker->_validatePackageDownload($dep, $required, array(&$dp)); + if (is_array($ret)) { + $dl->log(0, $ret[0]); + } elseif (PEAR::isError($ret)) { + $dl->log(0, $ret->getMessage()); + $fail = true; + } + } + } + } + PEAR::popErrorHandling(); + } + + if ($fail) { + return $this->raiseError( + '%s cannot be installed, conflicts with installed packages'); + } + + return true; + } + + /** + * validate a package.xml 1.0 dependency + */ + function validateDependency1($dep, $params = array()) + { + if (!isset($dep['optional'])) { + $dep['optional'] = 'no'; + } + + list($newdep, $type) = $this->normalizeDep($dep); + if (!$newdep) { + return $this->raiseError("Invalid Dependency"); + } + + if (method_exists($this, "validate{$type}Dependency")) { + return $this->{"validate{$type}Dependency"}($newdep, $dep['optional'] == 'no', + $params, true); + } + } + + /** + * Convert a 1.0 dep into a 2.0 dep + */ + function normalizeDep($dep) + { + $types = array( + 'pkg' => 'Package', + 'ext' => 'Extension', + 'os' => 'Os', + 'php' => 'Php' + ); + + if (!isset($types[$dep['type']])) { + return array(false, false); + } + + $type = $types[$dep['type']]; + + $newdep = array(); + switch ($type) { + case 'Package' : + $newdep['channel'] = 'pear.php.net'; + case 'Extension' : + case 'Os' : + $newdep['name'] = $dep['name']; + break; + } + + $dep['rel'] = PEAR_Dependency2::signOperator($dep['rel']); + switch ($dep['rel']) { + case 'has' : + return array($newdep, $type); + break; + case 'not' : + $newdep['conflicts'] = true; + break; + case '>=' : + case '>' : + $newdep['min'] = $dep['version']; + if ($dep['rel'] == '>') { + $newdep['exclude'] = $dep['version']; + } + break; + case '<=' : + case '<' : + $newdep['max'] = $dep['version']; + if ($dep['rel'] == '<') { + $newdep['exclude'] = $dep['version']; + } + break; + case 'ne' : + case '!=' : + $newdep['min'] = '0'; + $newdep['max'] = '100000'; + $newdep['exclude'] = $dep['version']; + break; + case '==' : + $newdep['min'] = $dep['version']; + $newdep['max'] = $dep['version']; + break; + } + if ($type == 'Php') { + if (!isset($newdep['min'])) { + $newdep['min'] = '4.4.0'; + } + + if (!isset($newdep['max'])) { + $newdep['max'] = '6.0.0'; + } + } + return array($newdep, $type); + } + + /** + * Converts text comparing operators to them sign equivalents + * + * Example: 'ge' to '>=' + * + * @access public + * @param string Operator + * @return string Sign equivalent + */ + function signOperator($operator) + { + switch($operator) { + case 'lt': return '<'; + case 'le': return '<='; + case 'gt': return '>'; + case 'ge': return '>='; + case 'eq': return '=='; + case 'ne': return '!='; + default: + return $operator; + } + } + + function raiseError($msg) + { + if (isset($this->_options['ignore-errors'])) { + return $this->warning($msg); + } + + return PEAR::raiseError(sprintf($msg, $this->_registry->parsedPackageNameToString( + $this->_currentPackage, true))); + } + + function warning($msg) + { + return array(sprintf($msg, $this->_registry->parsedPackageNameToString( + $this->_currentPackage, true))); + } +} + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: DependencyDB.php 286686 2009-08-02 17:38:57Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Needed for error handling + */ +require_once 'phar://go-pear.phar/' . 'PEAR.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/Config.php'; + +$GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] = array(); +/** + * Track dependency relationships between installed packages + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Tomas V.V.Cox + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_DependencyDB +{ + // {{{ properties + + /** + * This is initialized by {@link setConfig()} + * @var PEAR_Config + * @access private + */ + var $_config; + /** + * This is initialized by {@link setConfig()} + * @var PEAR_Registry + * @access private + */ + var $_registry; + /** + * Filename of the dependency DB (usually .depdb) + * @var string + * @access private + */ + var $_depdb = false; + /** + * File name of the lockfile (usually .depdblock) + * @var string + * @access private + */ + var $_lockfile = false; + /** + * Open file resource for locking the lockfile + * @var resource|false + * @access private + */ + var $_lockFp = false; + /** + * API version of this class, used to validate a file on-disk + * @var string + * @access private + */ + var $_version = '1.0'; + /** + * Cached dependency database file + * @var array|null + * @access private + */ + var $_cache; + + // }}} + // {{{ & singleton() + + /** + * Get a raw dependency database. Calls setConfig() and assertDepsDB() + * @param PEAR_Config + * @param string|false full path to the dependency database, or false to use default + * @return PEAR_DependencyDB|PEAR_Error + * @static + */ + function &singleton(&$config, $depdb = false) + { + $phpdir = $config->get('php_dir', null, 'pear.php.net'); + if (!isset($GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir])) { + $a = new PEAR_DependencyDB; + $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir] = &$a; + $a->setConfig($config, $depdb); + $e = $a->assertDepsDB(); + if (PEAR::isError($e)) { + return $e; + } + } + + return $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir]; + } + + /** + * Set up the registry/location of dependency DB + * @param PEAR_Config|false + * @param string|false full path to the dependency database, or false to use default + */ + function setConfig(&$config, $depdb = false) + { + if (!$config) { + $this->_config = &PEAR_Config::singleton(); + } else { + $this->_config = &$config; + } + + $this->_registry = &$this->_config->getRegistry(); + if (!$depdb) { + $this->_depdb = $this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb'; + } else { + $this->_depdb = $depdb; + } + + $this->_lockfile = dirname($this->_depdb) . DIRECTORY_SEPARATOR . '.depdblock'; + } + // }}} + + function hasWriteAccess() + { + if (!file_exists($this->_depdb)) { + $dir = $this->_depdb; + while ($dir && $dir != '.') { + $dir = dirname($dir); // cd .. + if ($dir != '.' && file_exists($dir)) { + if (is_writeable($dir)) { + return true; + } + + return false; + } + } + + return false; + } + + return is_writeable($this->_depdb); + } + + // {{{ assertDepsDB() + + /** + * Create the dependency database, if it doesn't exist. Error if the database is + * newer than the code reading it. + * @return void|PEAR_Error + */ + function assertDepsDB() + { + if (!is_file($this->_depdb)) { + $this->rebuildDB(); + return; + } + + $depdb = $this->_getDepDB(); + // Datatype format has been changed, rebuild the Deps DB + if ($depdb['_version'] < $this->_version) { + $this->rebuildDB(); + } + + if ($depdb['_version']{0} > $this->_version{0}) { + return PEAR::raiseError('Dependency database is version ' . + $depdb['_version'] . ', and we are version ' . + $this->_version . ', cannot continue'); + } + } + + /** + * Get a list of installed packages that depend on this package + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + function getDependentPackages(&$pkg) + { + $data = $this->_getDepDB(); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + if (isset($data['packages'][$channel][$package])) { + return $data['packages'][$channel][$package]; + } + + return false; + } + + /** + * Get a list of the actual dependencies of installed packages that depend on + * a package. + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + function getDependentPackageDependencies(&$pkg) + { + $data = $this->_getDepDB(); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + $depend = $this->getDependentPackages($pkg); + if (!$depend) { + return false; + } + + $dependencies = array(); + foreach ($depend as $info) { + $temp = $this->getDependencies($info); + foreach ($temp as $dep) { + if ( + isset($dep['dep'], $dep['dep']['channel'], $dep['dep']['name']) && + strtolower($dep['dep']['channel']) == $channel && + strtolower($dep['dep']['name']) == $package + ) { + if (!isset($dependencies[$info['channel']])) { + $dependencies[$info['channel']] = array(); + } + + if (!isset($dependencies[$info['channel']][$info['package']])) { + $dependencies[$info['channel']][$info['package']] = array(); + } + $dependencies[$info['channel']][$info['package']][] = $dep; + } + } + } + + return $dependencies; + } + + /** + * Get a list of dependencies of this installed package + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + function getDependencies(&$pkg) + { + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + $data = $this->_getDepDB(); + if (isset($data['dependencies'][$channel][$package])) { + return $data['dependencies'][$channel][$package]; + } + + return false; + } + + /** + * Determine whether $parent depends on $child, near or deep + * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 + * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 + */ + function dependsOn($parent, $child) + { + $c = array(); + $this->_getDepDB(); + return $this->_dependsOn($parent, $child, $c); + } + + function _dependsOn($parent, $child, &$checked) + { + if (is_object($parent)) { + $channel = strtolower($parent->getChannel()); + $package = strtolower($parent->getPackage()); + } else { + $channel = strtolower($parent['channel']); + $package = strtolower($parent['package']); + } + + if (is_object($child)) { + $depchannel = strtolower($child->getChannel()); + $deppackage = strtolower($child->getPackage()); + } else { + $depchannel = strtolower($child['channel']); + $deppackage = strtolower($child['package']); + } + + if (isset($checked[$channel][$package][$depchannel][$deppackage])) { + return false; // avoid endless recursion + } + + $checked[$channel][$package][$depchannel][$deppackage] = true; + if (!isset($this->_cache['dependencies'][$channel][$package])) { + return false; + } + + foreach ($this->_cache['dependencies'][$channel][$package] as $info) { + if (isset($info['dep']['uri'])) { + if (is_object($child)) { + if ($info['dep']['uri'] == $child->getURI()) { + return true; + } + } elseif (isset($child['uri'])) { + if ($info['dep']['uri'] == $child['uri']) { + return true; + } + } + return false; + } + + if (strtolower($info['dep']['channel']) == $depchannel && + strtolower($info['dep']['name']) == $deppackage) { + return true; + } + } + + foreach ($this->_cache['dependencies'][$channel][$package] as $info) { + if (isset($info['dep']['uri'])) { + if ($this->_dependsOn(array( + 'uri' => $info['dep']['uri'], + 'package' => $info['dep']['name']), $child, $checked)) { + return true; + } + } else { + if ($this->_dependsOn(array( + 'channel' => $info['dep']['channel'], + 'package' => $info['dep']['name']), $child, $checked)) { + return true; + } + } + } + + return false; + } + + /** + * Register dependencies of a package that is being installed or upgraded + * @param PEAR_PackageFile_v2|PEAR_PackageFile_v2 + */ + function installPackage(&$package) + { + $data = $this->_getDepDB(); + unset($this->_cache); + $this->_setPackageDeps($data, $package); + $this->_writeDepDB($data); + } + + /** + * Remove dependencies of a package that is being uninstalled, or upgraded. + * + * Upgraded packages first uninstall, then install + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array If an array, then it must have + * indices 'channel' and 'package' + */ + function uninstallPackage(&$pkg) + { + $data = $this->_getDepDB(); + unset($this->_cache); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + if (!isset($data['dependencies'][$channel][$package])) { + return true; + } + + foreach ($data['dependencies'][$channel][$package] as $dep) { + $found = false; + $depchannel = isset($dep['dep']['uri']) ? '__uri' : strtolower($dep['dep']['channel']); + $depname = strtolower($dep['dep']['name']); + if (isset($data['packages'][$depchannel][$depname])) { + foreach ($data['packages'][$depchannel][$depname] as $i => $info) { + if ($info['channel'] == $channel && $info['package'] == $package) { + $found = true; + break; + } + } + } + + if ($found) { + unset($data['packages'][$depchannel][$depname][$i]); + if (!count($data['packages'][$depchannel][$depname])) { + unset($data['packages'][$depchannel][$depname]); + if (!count($data['packages'][$depchannel])) { + unset($data['packages'][$depchannel]); + } + } else { + $data['packages'][$depchannel][$depname] = + array_values($data['packages'][$depchannel][$depname]); + } + } + } + + unset($data['dependencies'][$channel][$package]); + if (!count($data['dependencies'][$channel])) { + unset($data['dependencies'][$channel]); + } + + if (!count($data['dependencies'])) { + unset($data['dependencies']); + } + + if (!count($data['packages'])) { + unset($data['packages']); + } + + $this->_writeDepDB($data); + } + + /** + * Rebuild the dependency DB by reading registry entries. + * @return true|PEAR_Error + */ + function rebuildDB() + { + $depdb = array('_version' => $this->_version); + if (!$this->hasWriteAccess()) { + // allow startup for read-only with older Registry + return $depdb; + } + + $packages = $this->_registry->listAllPackages(); + if (PEAR::isError($packages)) { + return $packages; + } + + foreach ($packages as $channel => $ps) { + foreach ($ps as $package) { + $package = $this->_registry->getPackage($package, $channel); + if (PEAR::isError($package)) { + return $package; + } + $this->_setPackageDeps($depdb, $package); + } + } + + $error = $this->_writeDepDB($depdb); + if (PEAR::isError($error)) { + return $error; + } + + $this->_cache = $depdb; + return true; + } + + /** + * Register usage of the dependency DB to prevent race conditions + * @param int one of the LOCK_* constants + * @return true|PEAR_Error + * @access private + */ + function _lock($mode = LOCK_EX) + { + if (stristr(php_uname(), 'Windows 9')) { + return true; + } + + if ($mode != LOCK_UN && is_resource($this->_lockFp)) { + // XXX does not check type of lock (LOCK_SH/LOCK_EX) + return true; + } + + $open_mode = 'w'; + // XXX People reported problems with LOCK_SH and 'w' + if ($mode === LOCK_SH) { + if (!file_exists($this->_lockfile)) { + touch($this->_lockfile); + } elseif (!is_file($this->_lockfile)) { + return PEAR::raiseError('could not create Dependency lock file, ' . + 'it exists and is not a regular file'); + } + $open_mode = 'r'; + } + + if (!is_resource($this->_lockFp)) { + $this->_lockFp = @fopen($this->_lockfile, $open_mode); + } + + if (!is_resource($this->_lockFp)) { + return PEAR::raiseError("could not create Dependency lock file" . + (isset($php_errormsg) ? ": " . $php_errormsg : "")); + } + + if (!(int)flock($this->_lockFp, $mode)) { + switch ($mode) { + case LOCK_SH: $str = 'shared'; break; + case LOCK_EX: $str = 'exclusive'; break; + case LOCK_UN: $str = 'unlock'; break; + default: $str = 'unknown'; break; + } + + return PEAR::raiseError("could not acquire $str lock ($this->_lockfile)"); + } + + return true; + } + + /** + * Release usage of dependency DB + * @return true|PEAR_Error + * @access private + */ + function _unlock() + { + $ret = $this->_lock(LOCK_UN); + if (is_resource($this->_lockFp)) { + fclose($this->_lockFp); + } + $this->_lockFp = null; + return $ret; + } + + /** + * Load the dependency database from disk, or return the cache + * @return array|PEAR_Error + */ + function _getDepDB() + { + if (!$this->hasWriteAccess()) { + return array('_version' => $this->_version); + } + + if (isset($this->_cache)) { + return $this->_cache; + } + + if (!$fp = fopen($this->_depdb, 'r')) { + $err = PEAR::raiseError("Could not open dependencies file `".$this->_depdb."'"); + return $err; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + fclose($fp); + $data = unserialize(file_get_contents($this->_depdb)); + set_magic_quotes_runtime($rt); + $this->_cache = $data; + return $data; + } + + /** + * Write out the dependency database to disk + * @param array the database + * @return true|PEAR_Error + * @access private + */ + function _writeDepDB(&$deps) + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + if (!$fp = fopen($this->_depdb, 'wb')) { + $this->_unlock(); + return PEAR::raiseError("Could not open dependencies file `".$this->_depdb."' for writing"); + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + fwrite($fp, serialize($deps)); + set_magic_quotes_runtime($rt); + fclose($fp); + $this->_unlock(); + $this->_cache = $deps; + return true; + } + + /** + * Register all dependencies from a package in the dependencies database, in essence + * "installing" the package's dependency information + * @param array the database + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @access private + */ + function _setPackageDeps(&$data, &$pkg) + { + $pkg->setConfig($this->_config); + if ($pkg->getPackagexmlVersion() == '1.0') { + $gen = &$pkg->getDefaultGenerator(); + $deps = $gen->dependenciesToV2(); + } else { + $deps = $pkg->getDeps(true); + } + + if (!$deps) { + return; + } + + if (!is_array($data)) { + $data = array(); + } + + if (!isset($data['dependencies'])) { + $data['dependencies'] = array(); + } + + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + + if (!isset($data['dependencies'][$channel])) { + $data['dependencies'][$channel] = array(); + } + + $data['dependencies'][$channel][$package] = array(); + if (isset($deps['required']['package'])) { + if (!isset($deps['required']['package'][0])) { + $deps['required']['package'] = array($deps['required']['package']); + } + + foreach ($deps['required']['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'required'); + } + } + + if (isset($deps['optional']['package'])) { + if (!isset($deps['optional']['package'][0])) { + $deps['optional']['package'] = array($deps['optional']['package']); + } + + foreach ($deps['optional']['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional'); + } + } + + if (isset($deps['required']['subpackage'])) { + if (!isset($deps['required']['subpackage'][0])) { + $deps['required']['subpackage'] = array($deps['required']['subpackage']); + } + + foreach ($deps['required']['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'required'); + } + } + + if (isset($deps['optional']['subpackage'])) { + if (!isset($deps['optional']['subpackage'][0])) { + $deps['optional']['subpackage'] = array($deps['optional']['subpackage']); + } + + foreach ($deps['optional']['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional'); + } + } + + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + foreach ($deps['group'] as $group) { + if (isset($group['package'])) { + if (!isset($group['package'][0])) { + $group['package'] = array($group['package']); + } + + foreach ($group['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional', + $group['attribs']['name']); + } + } + + if (isset($group['subpackage'])) { + if (!isset($group['subpackage'][0])) { + $group['subpackage'] = array($group['subpackage']); + } + + foreach ($group['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional', + $group['attribs']['name']); + } + } + } + } + + if ($data['dependencies'][$channel][$package] == array()) { + unset($data['dependencies'][$channel][$package]); + if (!count($data['dependencies'][$channel])) { + unset($data['dependencies'][$channel]); + } + } + } + + /** + * @param array the database + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param array the specific dependency + * @param required|optional whether this is a required or an optional dep + * @param string|false dependency group this dependency is from, or false for ordinary dep + */ + function _registerDep(&$data, &$pkg, $dep, $type, $group = false) + { + $info = array( + 'dep' => $dep, + 'type' => $type, + 'group' => $group + ); + + $dep = array_map('strtolower', $dep); + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (!isset($data['dependencies'])) { + $data['dependencies'] = array(); + } + + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + + if (!isset($data['dependencies'][$channel])) { + $data['dependencies'][$channel] = array(); + } + + if (!isset($data['dependencies'][$channel][$package])) { + $data['dependencies'][$channel][$package] = array(); + } + + $data['dependencies'][$channel][$package][] = $info; + if (isset($data['packages'][$depchannel][$dep['name']])) { + $found = false; + foreach ($data['packages'][$depchannel][$dep['name']] as $i => $p) { + if ($p['channel'] == $channel && $p['package'] == $package) { + $found = true; + break; + } + } + } else { + if (!isset($data['packages'])) { + $data['packages'] = array(); + } + + if (!isset($data['packages'][$depchannel])) { + $data['packages'][$depchannel] = array(); + } + + if (!isset($data['packages'][$depchannel][$dep['name']])) { + $data['packages'][$depchannel][$dep['name']] = array(); + } + + $found = false; + } + + if (!$found) { + $data['packages'][$depchannel][$dep['name']][] = array( + 'channel' => $channel, + 'package' => $package + ); + } + } +} + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Martin Jansen + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Downloader.php 287109 2009-08-11 18:50:30Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.3.0 + */ + +/** + * Needed for constants, extending + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Common.php'; + +define('PEAR_INSTALLER_OK', 1); +define('PEAR_INSTALLER_FAILED', 0); +define('PEAR_INSTALLER_SKIPPED', -1); +define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2); + +/** + * Administration class used to download anything from the internet (PEAR Packages, + * static URLs, xml files) + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Martin Jansen + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.3.0 + */ +class PEAR_Downloader extends PEAR_Common +{ + /** + * @var PEAR_Registry + * @access private + */ + var $_registry; + + /** + * Preferred Installation State (snapshot, devel, alpha, beta, stable) + * @var string|null + * @access private + */ + var $_preferredState; + + /** + * Options from command-line passed to Install. + * + * Recognized options:
    + * - onlyreqdeps : install all required dependencies as well + * - alldeps : install all dependencies, including optional + * - installroot : base relative path to install files in + * - force : force a download even if warnings would prevent it + * - nocompress : download uncompressed tarballs + * @see PEAR_Command_Install + * @access private + * @var array + */ + var $_options; + + /** + * Downloaded Packages after a call to download(). + * + * Format of each entry: + * + * + * array('pkg' => 'package_name', 'file' => '/path/to/local/file', + * 'info' => array() // parsed package.xml + * ); + * + * @access private + * @var array + */ + var $_downloadedPackages = array(); + + /** + * Packages slated for download. + * + * This is used to prevent downloading a package more than once should it be a dependency + * for two packages to be installed. + * Format of each entry: + * + *
    +     * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml,
    +     * );
    +     * 
    + * @access private + * @var array + */ + var $_toDownload = array(); + + /** + * Array of every package installed, with names lower-cased. + * + * Format: + * + * array('package1' => 0, 'package2' => 1, ); + * + * @var array + */ + var $_installed = array(); + + /** + * @var array + * @access private + */ + var $_errorStack = array(); + + /** + * @var boolean + * @access private + */ + var $_internalDownload = false; + + /** + * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()} + * @var array + * @access private + */ + var $_packageSortTree; + + /** + * Temporary directory, or configuration value where downloads will occur + * @var string + */ + var $_downloadDir; + + /** + * @param PEAR_Frontend_* + * @param array + * @param PEAR_Config + */ + function PEAR_Downloader(&$ui, $options, &$config) + { + parent::PEAR_Common(); + $this->_options = $options; + $this->config = &$config; + $this->_preferredState = $this->config->get('preferred_state'); + $this->ui = &$ui; + if (!$this->_preferredState) { + // don't inadvertantly use a non-set preferred_state + $this->_preferredState = null; + } + + if (isset($this->_options['installroot'])) { + $this->config->setInstallRoot($this->_options['installroot']); + } + $this->_registry = &$config->getRegistry(); + + if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) { + $this->_installed = $this->_registry->listAllPackages(); + foreach ($this->_installed as $key => $unused) { + if (!count($unused)) { + continue; + } + $strtolower = create_function('$a','return strtolower($a);'); + array_walk($this->_installed[$key], $strtolower); + } + } + } + + /** + * Attempt to discover a channel's remote capabilities from + * its server name + * @param string + * @return boolean + */ + function discover($channel) + { + $this->log(1, 'Attempting to discover channel "' . $channel . '"...'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $callback = $this->ui ? array(&$this, '_downloadCallback') : null; + if (!class_exists('System')) { + require_once 'phar://go-pear.phar/' . 'System.php'; + } + + $tmp = System::mktemp(array('-d')); + $a = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false); + PEAR::popErrorHandling(); + if (PEAR::isError($a)) { + // Attempt to fallback to https automatically. + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $this->log(1, 'Attempting fallback to https instead of http on channel "' . $channel . '"...'); + $a = $this->downloadHttp('https://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false); + PEAR::popErrorHandling(); + if (PEAR::isError($a)) { + return false; + } + } + + list($a, $lastmodified) = $a; + if (!class_exists('PEAR_ChannelFile')) { + require_once 'phar://go-pear.phar/' . 'PEAR/ChannelFile.php'; + } + + $b = new PEAR_ChannelFile; + if ($b->fromXmlFile($a)) { + unlink($a); + if ($this->config->get('auto_discover')) { + $this->_registry->addChannel($b, $lastmodified); + $alias = $b->getName(); + if ($b->getName() == $this->_registry->channelName($b->getAlias())) { + $alias = $b->getAlias(); + } + + $this->log(1, 'Auto-discovered channel "' . $channel . + '", alias "' . $alias . '", adding to registry'); + } + + return true; + } + + unlink($a); + return false; + } + + /** + * For simpler unit-testing + * @param PEAR_Downloader + * @return PEAR_Downloader_Package + */ + function &newDownloaderPackage(&$t) + { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Downloader/Package.php'; + } + $a = &new PEAR_Downloader_Package($t); + return $a; + } + + /** + * For simpler unit-testing + * @param PEAR_Config + * @param array + * @param array + * @param int + */ + function &getDependency2Object(&$c, $i, $p, $s) + { + if (!class_exists('PEAR_Dependency2')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Dependency2.php'; + } + $z = &new PEAR_Dependency2($c, $i, $p, $s); + return $z; + } + + function &download($params) + { + if (!count($params)) { + $a = array(); + return $a; + } + + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + $channelschecked = array(); + // convert all parameters into PEAR_Downloader_Package objects + foreach ($params as $i => $param) { + $params[$i] = &$this->newDownloaderPackage($this); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $params[$i]->initialize($param); + PEAR::staticPopErrorHandling(); + if (!$err) { + // skip parameters that were missed by preferred_state + continue; + } + + if (PEAR::isError($err)) { + if (!isset($this->_options['soft']) && $err->getMessage() !== '') { + $this->log(0, $err->getMessage()); + } + + $params[$i] = false; + if (is_object($param)) { + $param = $param->getChannel() . '/' . $param->getPackage(); + } + + if (!isset($this->_options['soft'])) { + $this->log(2, 'Package "' . $param . '" is not valid'); + } + + // Message logged above in a specific verbose mode, passing null to not show up on CLI + $this->pushError(null, PEAR_INSTALLER_SKIPPED); + } else { + do { + if ($params[$i] && $params[$i]->getType() == 'local') { + // bug #7090 skip channel.xml check for local packages + break; + } + + if ($params[$i] && !isset($channelschecked[$params[$i]->getChannel()]) && + !isset($this->_options['offline']) + ) { + $channelschecked[$params[$i]->getChannel()] = true; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!class_exists('System')) { + require_once 'phar://go-pear.phar/' . 'System.php'; + } + + $curchannel = &$this->_registry->getChannel($params[$i]->getChannel()); + if (PEAR::isError($curchannel)) { + PEAR::staticPopErrorHandling(); + return $this->raiseError($curchannel); + } + + if (PEAR::isError($dir = $this->getDownloadDir())) { + PEAR::staticPopErrorHandling(); + break; + } + + $mirror = $this->config->get('preferred_mirror', null, $params[$i]->getChannel()); + $url = 'http://' . $mirror . '/channel.xml'; + $a = $this->downloadHttp($url, $this->ui, $dir, null, $curchannel->lastModified()); + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($a) || !$a) { + // Attempt fallback to https automatically + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $a = $this->downloadHttp('https://' . $mirror . + '/channel.xml', $this->ui, $dir, null, $curchannel->lastModified()); + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($a) || !$a) { + break; + } + } + $this->log(0, 'WARNING: channel "' . $params[$i]->getChannel() . '" has ' . + 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $params[$i]->getChannel() . + '" to update'); + } + } while (false); + + if ($params[$i] && !isset($this->_options['downloadonly'])) { + if (isset($this->_options['packagingroot'])) { + $checkdir = $this->_prependPath( + $this->config->get('php_dir', null, $params[$i]->getChannel()), + $this->_options['packagingroot']); + } else { + $checkdir = $this->config->get('php_dir', + null, $params[$i]->getChannel()); + } + + while ($checkdir && $checkdir != '/' && !file_exists($checkdir)) { + $checkdir = dirname($checkdir); + } + + if ($checkdir == '.') { + $checkdir = '/'; + } + + if (!is_writeable($checkdir)) { + return PEAR::raiseError('Cannot install, php_dir for channel "' . + $params[$i]->getChannel() . '" is not writeable by the current user'); + } + } + } + } + + unset($channelschecked); + PEAR_Downloader_Package::removeDuplicates($params); + if (!count($params)) { + $a = array(); + return $a; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['offline'])) { + $reverify = true; + while ($reverify) { + $reverify = false; + foreach ($params as $i => $param) { + //PHP Bug 40768 / PEAR Bug #10944 + //Nested foreaches fail in PHP 5.2.1 + key($params); + $ret = $params[$i]->detectDependencies($params); + if (PEAR::isError($ret)) { + $reverify = true; + $params[$i] = false; + PEAR_Downloader_Package::removeDuplicates($params); + if (!isset($this->_options['soft'])) { + $this->log(0, $ret->getMessage()); + } + continue 2; + } + } + } + } + + if (isset($this->_options['offline'])) { + $this->log(3, 'Skipping dependency download check, --offline specified'); + } + + if (!count($params)) { + $a = array(); + return $a; + } + + while (PEAR_Downloader_Package::mergeDependencies($params)); + PEAR_Downloader_Package::removeDuplicates($params, true); + $errorparams = array(); + if (PEAR_Downloader_Package::detectStupidDuplicates($params, $errorparams)) { + if (count($errorparams)) { + foreach ($errorparams as $param) { + $name = $this->_registry->parsedPackageNameToString($param->getParsedPackage()); + $this->pushError('Duplicate package ' . $name . ' found', PEAR_INSTALLER_FAILED); + } + $a = array(); + return $a; + } + } + + PEAR_Downloader_Package::removeInstalled($params); + if (!count($params)) { + $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($params); + PEAR::popErrorHandling(); + if (!count($params)) { + $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + + $ret = array(); + $newparams = array(); + if (isset($this->_options['pretend'])) { + return $params; + } + + $somefailed = false; + foreach ($params as $i => $package) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$params[$i]->download(); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + if (!isset($this->_options['soft'])) { + $this->log(1, $pf->getMessage()); + $this->log(0, 'Error: cannot download "' . + $this->_registry->parsedPackageNameToString($package->getParsedPackage(), + true) . + '"'); + } + $somefailed = true; + continue; + } + + $newparams[] = &$params[$i]; + $ret[] = array( + 'file' => $pf->getArchiveFile(), + 'info' => &$pf, + 'pkg' => $pf->getPackage() + ); + } + + if ($somefailed) { + // remove params that did not download successfully + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($newparams, true); + PEAR::popErrorHandling(); + if (!count($newparams)) { + $this->pushError('Download failed', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + } + + $this->_downloadedPackages = $ret; + return $newparams; + } + + /** + * @param array all packages to be installed + */ + function analyzeDependencies(&$params, $force = false) + { + $hasfailed = $failed = false; + if (isset($this->_options['downloadonly'])) { + return; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $redo = true; + $reset = false; + while ($redo) { + $redo = false; + foreach ($params as $i => $param) { + $deps = $param->getDeps(); + if (!$deps) { + $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(), + $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING); + $send = $param->getPackageFile(); + + $installcheck = $depchecker->validatePackage($send, $this, $params); + if (PEAR::isError($installcheck)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $installcheck->getMessage()); + } + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + continue; + } + + if (!$reset && $param->alreadyValidated() && !$force) { + continue; + } + + if (count($deps)) { + $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(), + $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING); + $send = $param->getPackageFile(); + if ($send === null) { + $send = $param->getDownloadURL(); + } + + $installcheck = $depchecker->validatePackage($send, $this, $params); + if (PEAR::isError($installcheck)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $installcheck->getMessage()); + } + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + + $failed = false; + if (isset($deps['required'])) { + foreach ($deps['required'] as $type => $dep) { + // note: Dependency2 will never return a PEAR_Error if ignore-errors + // is specified, so soft is needed to turn off logging + if (!isset($dep[0])) { + if (PEAR::isError($e = $depchecker->{"validate{$type}Dependency"}($dep, + true, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + true, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + + if (isset($deps['optional'])) { + foreach ($deps['optional'] as $type => $dep) { + if (!isset($dep[0])) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($dep, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + } + + $groupname = $param->getGroup(); + if (isset($deps['group']) && $groupname) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + $found = false; + foreach ($deps['group'] as $group) { + if ($group['attribs']['name'] == $groupname) { + $found = true; + break; + } + } + + if ($found) { + unset($group['attribs']); + foreach ($group as $type => $dep) { + if (!isset($dep[0])) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($dep, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + } + } + } else { + foreach ($deps as $dep) { + if (PEAR::isError($e = $depchecker->validateDependency1($dep, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + $params[$i]->setValidated(); + } + + if ($failed) { + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + } + } + PEAR::staticPopErrorHandling(); + if ($hasfailed && (isset($this->_options['ignore-errors']) || + isset($this->_options['nodeps']))) { + // this is probably not needed, but just in case + if (!isset($this->_options['soft'])) { + $this->log(0, 'WARNING: dependencies failed'); + } + } + } + + /** + * Retrieve the directory that downloads will happen in + * @access private + * @return string + */ + function getDownloadDir() + { + if (isset($this->_downloadDir)) { + return $this->_downloadDir; + } + $downloaddir = $this->config->get('download_dir'); + if (empty($downloaddir) || (is_dir($downloaddir) && !is_writable($downloaddir))) { + if (is_dir($downloaddir) && !is_writable($downloaddir)) { + $this->log(0, 'WARNING: configuration download directory "' . $downloaddir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir to avoid this warning'); + } + if (!class_exists('System')) { + require_once 'phar://go-pear.phar/' . 'System.php'; + } + if (PEAR::isError($downloaddir = System::mktemp('-d'))) { + return $downloaddir; + } + $this->log(3, '+ tmp dir created at ' . $downloaddir); + } + if (!is_writable($downloaddir)) { + if (PEAR::isError(System::mkdir(array('-p', $downloaddir))) || + !is_writable($downloaddir)) { + return PEAR::raiseError('download directory "' . $downloaddir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir'); + } + } + return $this->_downloadDir = $downloaddir; + } + + function setDownloadDir($dir) + { + if (!@is_writable($dir)) { + if (PEAR::isError(System::mkdir(array('-p', $dir)))) { + return PEAR::raiseError('download directory "' . $dir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir'); + } + } + $this->_downloadDir = $dir; + } + + function configSet($key, $value, $layer = 'user', $channel = false) + { + $this->config->set($key, $value, $layer, $channel); + $this->_preferredState = $this->config->get('preferred_state', null, $channel); + if (!$this->_preferredState) { + // don't inadvertantly use a non-set preferred_state + $this->_preferredState = null; + } + } + + function setOptions($options) + { + $this->_options = $options; + } + + // }}} + // {{{ setOptions() + function getOptions() + { + return $this->_options; + } + + /** + * For simpler unit-testing + * @param PEAR_Config + * @param int + * @param string + */ + function &getPackagefileObject(&$c, $d, $t = false) + { + if (!class_exists('PEAR_PackageFile')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile.php'; + } + $a = &new PEAR_PackageFile($c, $d, $t); + return $a; + } + + /** + * @param array output of {@link parsePackageName()} + * @access private + */ + function _getPackageDownloadUrl($parr) + { + $curchannel = $this->config->get('default_channel'); + $this->configSet('default_channel', $parr['channel']); + // getDownloadURL returns an array. On error, it only contains information + // on the latest release as array(version, info). On success it contains + // array(version, info, download url string) + $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state'); + if (!$this->_registry->channelExists($parr['channel'])) { + do { + if ($this->config->get('auto_discover') && $this->discover($parr['channel'])) { + break; + } + + $this->configSet('default_channel', $curchannel); + return PEAR::raiseError('Unknown remote channel: ' . $parr['channel']); + } while (false); + } + + $chan = &$this->_registry->getChannel($parr['channel']); + if (PEAR::isError($chan)) { + return $chan; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $version = $this->_registry->packageInfo($parr['package'], 'version', $parr['channel']); + $stability = $this->_registry->packageInfo($parr['package'], 'stability', $parr['channel']); + // package is installed - use the installed release stability level + if (!isset($parr['state']) && $stability !== null) { + $state = $stability['release']; + } + PEAR::staticPopErrorHandling(); + $base2 = false; + + $preferred_mirror = $this->config->get('preferred_mirror'); + if (!$chan->supportsREST($preferred_mirror) || + ( + !($base2 = $chan->getBaseURL('REST1.3', $preferred_mirror)) + && + !($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + ) { + return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.'); + } + + if ($base2) { + $rest = &$this->config->getREST('1.3', $this->_options); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', $this->_options); + } + + $downloadVersion = false; + if (!isset($parr['version']) && !isset($parr['state']) && $version + && !PEAR::isError($version) + && !isset($this->_options['downloadonly']) + ) { + $downloadVersion = $version; + } + + $url = $rest->getDownloadURL($base, $parr, $state, $downloadVersion, $chan->getName()); + if (PEAR::isError($url)) { + $this->configSet('default_channel', $curchannel); + return $url; + } + + if ($parr['channel'] != $curchannel) { + $this->configSet('default_channel', $curchannel); + } + + if (!is_array($url)) { + return $url; + } + + $url['raw'] = false; // no checking is necessary for REST + if (!is_array($url['info'])) { + return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' . + 'this should never happen'); + } + + if (!isset($this->_options['force']) && + !isset($this->_options['downloadonly']) && + $version && + !PEAR::isError($version) && + !isset($parr['group']) + ) { + if (version_compare($version, $url['version'], '=')) { + return PEAR::raiseError($this->_registry->parsedPackageNameToString( + $parr, true) . ' is already installed and is the same as the ' . + 'released version ' . $url['version'], -976); + } + + if (version_compare($version, $url['version'], '>')) { + return PEAR::raiseError($this->_registry->parsedPackageNameToString( + $parr, true) . ' is already installed and is newer than detected ' . + 'released version ' . $url['version'], -976); + } + } + + if (isset($url['info']['required']) || $url['compatible']) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v2.php'; + $pf = new PEAR_PackageFile_v2; + $pf->setRawChannel($parr['channel']); + if ($url['compatible']) { + $pf->setRawCompatible($url['compatible']); + } + } else { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v1.php'; + $pf = new PEAR_PackageFile_v1; + } + + $pf->setRawPackage($url['package']); + $pf->setDeps($url['info']); + if ($url['compatible']) { + $pf->setCompatible($url['compatible']); + } + + $pf->setRawState($url['stability']); + $url['info'] = &$pf; + if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + if (is_array($url) && isset($url['url'])) { + $url['url'] .= $ext; + } + + return $url; + } + + /** + * @param array dependency array + * @access private + */ + function _getDepPackageDownloadUrl($dep, $parr) + { + $xsdversion = isset($dep['rel']) ? '1.0' : '2.0'; + $curchannel = $this->config->get('default_channel'); + if (isset($dep['uri'])) { + $xsdversion = '2.0'; + $chan = &$this->_registry->getChannel('__uri'); + if (PEAR::isError($chan)) { + return $chan; + } + + $version = $this->_registry->packageInfo($dep['name'], 'version', '__uri'); + $this->configSet('default_channel', '__uri'); + } else { + if (isset($dep['channel'])) { + $remotechannel = $dep['channel']; + } else { + $remotechannel = 'pear.php.net'; + } + + if (!$this->_registry->channelExists($remotechannel)) { + do { + if ($this->config->get('auto_discover')) { + if ($this->discover($remotechannel)) { + break; + } + } + return PEAR::raiseError('Unknown remote channel: ' . $remotechannel); + } while (false); + } + + $chan = &$this->_registry->getChannel($remotechannel); + if (PEAR::isError($chan)) { + return $chan; + } + + $version = $this->_registry->packageInfo($dep['name'], 'version', $remotechannel); + $this->configSet('default_channel', $remotechannel); + } + + $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state'); + if (isset($parr['state']) && isset($parr['version'])) { + unset($parr['state']); + } + + if (isset($dep['uri'])) { + $info = &$this->newDownloaderPackage($this); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $info->initialize($dep); + PEAR::staticPopErrorHandling(); + if (!$err) { + // skip parameters that were missed by preferred_state + return PEAR::raiseError('Cannot initialize dependency'); + } + + if (PEAR::isError($err)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $err->getMessage()); + } + + if (is_object($info)) { + $param = $info->getChannel() . '/' . $info->getPackage(); + } + return PEAR::raiseError('Package "' . $param . '" is not valid'); + } + return $info; + } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) + && $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror')) + ) { + $rest = &$this->config->getREST('1.0', $this->_options); + $url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr, + $state, $version, $chan->getName()); + if (PEAR::isError($url)) { + return $url; + } + + if ($parr['channel'] != $curchannel) { + $this->configSet('default_channel', $curchannel); + } + + if (!is_array($url)) { + return $url; + } + + $url['raw'] = false; // no checking is necessary for REST + if (!is_array($url['info'])) { + return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' . + 'this should never happen'); + } + + if (isset($url['info']['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v2.php'; + } + $pf = new PEAR_PackageFile_v2; + $pf->setRawChannel($remotechannel); + } else { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + + } + $pf->setRawPackage($url['package']); + $pf->setDeps($url['info']); + if ($url['compatible']) { + $pf->setCompatible($url['compatible']); + } + + $pf->setRawState($url['stability']); + $url['info'] = &$pf; + if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + if (is_array($url) && isset($url['url'])) { + $url['url'] .= $ext; + } + + return $url; + } + + return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.'); + } + + /** + * @deprecated in favor of _getPackageDownloadUrl + */ + function getPackageDownloadUrl($package, $version = null, $channel = false) + { + if ($version) { + $package .= "-$version"; + } + if ($this === null || $this->_registry === null) { + $package = "http://pear.php.net/get/$package"; + } else { + $chan = $this->_registry->getChannel($channel); + if (PEAR::isError($chan)) { + return ''; + } + $package = "http://" . $chan->getServer() . "/get/$package"; + } + if (!extension_loaded("zlib")) { + $package .= '?uncompress=yes'; + } + return $package; + } + + /** + * Retrieve a list of downloaded packages after a call to {@link download()}. + * + * Also resets the list of downloaded packages. + * @return array + */ + function getDownloadedPackages() + { + $ret = $this->_downloadedPackages; + $this->_downloadedPackages = array(); + $this->_toDownload = array(); + return $ret; + } + + function _downloadCallback($msg, $params = null) + { + switch ($msg) { + case 'saveas': + $this->log(1, "downloading $params ..."); + break; + case 'done': + $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes'); + break; + case 'bytesread': + static $bytes; + if (empty($bytes)) { + $bytes = 0; + } + if (!($bytes % 10240)) { + $this->log(1, '.', false); + } + $bytes += $params; + break; + case 'start': + if($params[1] == -1) { + $length = "Unknown size"; + } else { + $length = number_format($params[1], 0, '', ',')." bytes"; + } + $this->log(1, "Starting to download {$params[0]} ($length)"); + break; + } + if (method_exists($this->ui, '_downloadCallback')) + $this->ui->_downloadCallback($msg, $params); + } + + function _prependPath($path, $prepend) + { + if (strlen($prepend) > 0) { + if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) { + if (preg_match('/^[a-z]:/i', $prepend)) { + $prepend = substr($prepend, 2); + } elseif ($prepend{0} != '\\') { + $prepend = "\\$prepend"; + } + $path = substr($path, 0, 2) . $prepend . substr($path, 2); + } else { + $path = $prepend . $path; + } + } + return $path; + } + + /** + * @param string + * @param integer + */ + function pushError($errmsg, $code = -1) + { + array_push($this->_errorStack, array($errmsg, $code)); + } + + function getErrorMsgs() + { + $msgs = array(); + $errs = $this->_errorStack; + foreach ($errs as $err) { + $msgs[] = $err[0]; + } + $this->_errorStack = array(); + return $msgs; + } + + /** + * for BC + * + * @deprecated + */ + function sortPkgDeps(&$packages, $uninstall = false) + { + $uninstall ? + $this->sortPackagesForUninstall($packages) : + $this->sortPackagesForInstall($packages); + } + + /** + * Sort a list of arrays of array(downloaded packagefilename) by dependency. + * + * This uses the topological sort method from graph theory, and the + * Structures_Graph package to properly sort dependencies for installation. + * @param array an array of downloaded PEAR_Downloader_Packages + * @return array array of array(packagefilename, package.xml contents) + */ + function sortPackagesForInstall(&$packages) + { + require_once 'phar://go-pear.phar/' . 'Structures/Graph.php'; + require_once 'phar://go-pear.phar/' . 'Structures/Graph/Node.php'; + require_once 'phar://go-pear.phar/' . 'Structures/Graph/Manipulator/TopologicalSorter.php'; + $depgraph = new Structures_Graph(true); + $nodes = array(); + $reg = &$this->config->getRegistry(); + foreach ($packages as $i => $package) { + $pname = $reg->parsedPackageNameToString( + array( + 'channel' => $package->getChannel(), + 'package' => strtolower($package->getPackage()), + )); + $nodes[$pname] = new Structures_Graph_Node; + $nodes[$pname]->setData($packages[$i]); + $depgraph->addNode($nodes[$pname]); + } + + $deplinks = array(); + foreach ($nodes as $package => $node) { + $pf = &$node->getData(); + $pdeps = $pf->getDeps(true); + if (!$pdeps) { + continue; + } + + if ($pf->getPackagexmlVersion() == '1.0') { + foreach ($pdeps as $dep) { + if ($dep['type'] != 'pkg' || + (isset($dep['optional']) && $dep['optional'] == 'yes')) { + continue; + } + + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => 'pear.php.net', + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + + $deplinks[$dname][$package] = 1; + // dependency is in installed packages + continue; + } + + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => 'pecl.php.net', + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + + $deplinks[$dname][$package] = 1; + // dependency is in installed packages + continue; + } + } + } else { + // the only ordering we care about is: + // 1) subpackages must be installed before packages that depend on them + // 2) required deps must be installed before packages that depend on them + if (isset($pdeps['required']['subpackage'])) { + $t = $pdeps['required']['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['group'])) { + if (!isset($pdeps['group'][0])) { + $pdeps['group'] = array($pdeps['group']); + } + + foreach ($pdeps['group'] as $group) { + if (isset($group['subpackage'])) { + $t = $group['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + } + } + + if (isset($pdeps['optional']['subpackage'])) { + $t = $pdeps['optional']['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['required']['package'])) { + $t = $pdeps['required']['package']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['group'])) { + if (!isset($pdeps['group'][0])) { + $pdeps['group'] = array($pdeps['group']); + } + + foreach ($pdeps['group'] as $group) { + if (isset($group['package'])) { + $t = $group['package']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + } + } + } + } + + $this->_detectDepCycle($deplinks); + foreach ($deplinks as $dependent => $parents) { + foreach ($parents as $parent => $unused) { + $nodes[$dependent]->connectTo($nodes[$parent]); + } + } + + $installOrder = Structures_Graph_Manipulator_TopologicalSorter::sort($depgraph); + $ret = array(); + for ($i = 0, $count = count($installOrder); $i < $count; $i++) { + foreach ($installOrder[$i] as $index => $sortedpackage) { + $data = &$installOrder[$i][$index]->getData(); + $ret[] = &$nodes[$reg->parsedPackageNameToString( + array( + 'channel' => $data->getChannel(), + 'package' => strtolower($data->getPackage()), + ))]->getData(); + } + } + + $packages = $ret; + return; + } + + /** + * Detect recursive links between dependencies and break the cycles + * + * @param array + * @access private + */ + function _detectDepCycle(&$deplinks) + { + do { + $keepgoing = false; + foreach ($deplinks as $dep => $parents) { + foreach ($parents as $parent => $unused) { + // reset the parent cycle detector + $this->_testCycle(null, null, null); + if ($this->_testCycle($dep, $deplinks, $parent)) { + $keepgoing = true; + unset($deplinks[$dep][$parent]); + if (count($deplinks[$dep]) == 0) { + unset($deplinks[$dep]); + } + + continue 3; + } + } + } + } while ($keepgoing); + } + + function _testCycle($test, $deplinks, $dep) + { + static $visited = array(); + if ($test === null) { + $visited = array(); + return; + } + + // this happens when a parent has a dep cycle on another dependency + // but the child is not part of the cycle + if (isset($visited[$dep])) { + return false; + } + + $visited[$dep] = 1; + if ($test == $dep) { + return true; + } + + if (isset($deplinks[$dep])) { + if (in_array($test, array_keys($deplinks[$dep]), true)) { + return true; + } + + foreach ($deplinks[$dep] as $parent => $unused) { + if ($this->_testCycle($test, $deplinks, $parent)) { + return true; + } + } + } + + return false; + } + + /** + * Set up the dependency for installation parsing + * + * @param array $t dependency information + * @param PEAR_Registry $reg + * @param array $deplinks list of dependency links already established + * @param array $nodes all existing package nodes + * @param string $package parent package name + * @access private + */ + function _setupGraph($t, $reg, &$deplinks, &$nodes, $package) + { + foreach ($t as $dep) { + $depchannel = !isset($dep['channel']) ? '__uri': $dep['channel']; + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => $depchannel, + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + $deplinks[$dname][$package] = 1; + } + } + } + + function _dependsOn($a, $b) + { + return $this->_checkDepTree(strtolower($a->getChannel()), strtolower($a->getPackage()), $b); + } + + function _checkDepTree($channel, $package, $b, $checked = array()) + { + $checked[$channel][$package] = true; + if (!isset($this->_depTree[$channel][$package])) { + return false; + } + + if (isset($this->_depTree[$channel][$package][strtolower($b->getChannel())] + [strtolower($b->getPackage())])) { + return true; + } + + foreach ($this->_depTree[$channel][$package] as $ch => $packages) { + foreach ($packages as $pa => $true) { + if ($this->_checkDepTree($ch, $pa, $b, $checked)) { + return true; + } + } + } + + return false; + } + + function _sortInstall($a, $b) + { + if (!$a->getDeps() && !$b->getDeps()) { + return 0; // neither package has dependencies, order is insignificant + } + if ($a->getDeps() && !$b->getDeps()) { + return 1; // $a must be installed after $b because $a has dependencies + } + if (!$a->getDeps() && $b->getDeps()) { + return -1; // $b must be installed after $a because $b has dependencies + } + // both packages have dependencies + if ($this->_dependsOn($a, $b)) { + return 1; + } + if ($this->_dependsOn($b, $a)) { + return -1; + } + return 0; + } + + /** + * Download a file through HTTP. Considers suggested file name in + * Content-disposition: header and can run a callback function for + * different events. The callback will be called with two + * parameters: the callback type, and parameters. The implemented + * callback types are: + * + * 'setup' called at the very beginning, parameter is a UI object + * that should be used for all output + * 'message' the parameter is a string with an informational message + * 'saveas' may be used to save with a different file name, the + * parameter is the filename that is about to be used. + * If a 'saveas' callback returns a non-empty string, + * that file name will be used as the filename instead. + * Note that $save_dir will not be affected by this, only + * the basename of the file. + * 'start' download is starting, parameter is number of bytes + * that are expected, or -1 if unknown + * 'bytesread' parameter is the number of bytes read so far + * 'done' download is complete, parameter is the total number + * of bytes read + * 'connfailed' if the TCP/SSL connection fails, this callback is called + * with array(host,port,errno,errmsg) + * 'writefailed' if writing to disk fails, this callback is called + * with array(destfile,errmsg) + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param object $ui PEAR_Frontend_* instance + * @param object $config PEAR_Config instance + * @param string $save_dir directory to save file in + * @param mixed $callback function/method to call for status + * updates + * @param false|string|array $lastmodified header values to check against for caching + * use false to return the header values from this download + * @param false|array $accept Accept headers to send + * @param false|string $channel Channel to use for retrieving authentication + * @return string|array Returns the full path of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). If caching is requested, then return the header + * values. + * + * @access public + */ + function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null, + $accept = false, $channel = false) + { + static $redirect = 0; + // always reset , so we are clean case of error + $wasredirect = $redirect; + $redirect = 0; + if ($callback) { + call_user_func($callback, 'setup', array(&$ui)); + } + + $info = parse_url($url); + if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) { + return PEAR::raiseError('Cannot download non-http URL "' . $url . '"'); + } + + if (!isset($info['host'])) { + return PEAR::raiseError('Cannot download from non-URL "' . $url . '"'); + } + + $host = isset($info['host']) ? $info['host'] : null; + $port = isset($info['port']) ? $info['port'] : null; + $path = isset($info['path']) ? $info['path'] : null; + + if (isset($this)) { + $config = &$this->config; + } else { + $config = &PEAR_Config::singleton(); + } + + $proxy_host = $proxy_port = $proxy_user = $proxy_pass = ''; + if ($config->get('http_proxy') && + $proxy = parse_url($config->get('http_proxy'))) { + $proxy_host = isset($proxy['host']) ? $proxy['host'] : null; + if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') { + $proxy_host = 'ssl://' . $proxy_host; + } + $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080; + $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null; + $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null; + + if ($callback) { + call_user_func($callback, 'message', "Using HTTP proxy $host:$port"); + } + } + + if (empty($port)) { + $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80; + } + + $scheme = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http'; + + if ($proxy_host != '') { + $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr); + if (!$fp) { + if ($callback) { + call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port, + $errno, $errstr)); + } + return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno); + } + + if ($lastmodified === false || $lastmodified) { + $request = "GET $url HTTP/1.1\r\n"; + $request .= "Host: $host:$port\r\n"; + } else { + $request = "GET $url HTTP/1.0\r\n"; + $request .= "Host: $host\r\n"; + } + } else { + $network_host = $host; + if (isset($info['scheme']) && $info['scheme'] == 'https') { + $network_host = 'ssl://' . $host; + } + + $fp = @fsockopen($network_host, $port, $errno, $errstr); + if (!$fp) { + if ($callback) { + call_user_func($callback, 'connfailed', array($host, $port, + $errno, $errstr)); + } + return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno); + } + + if ($lastmodified === false || $lastmodified) { + $request = "GET $path HTTP/1.1\r\n"; + $request .= "Host: $host:$port\r\n"; + } else { + $request = "GET $path HTTP/1.0\r\n"; + $request .= "Host: $host\r\n"; + } + } + + $ifmodifiedsince = ''; + if (is_array($lastmodified)) { + if (isset($lastmodified['Last-Modified'])) { + $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n"; + } + + if (isset($lastmodified['ETag'])) { + $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n"; + } + } else { + $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : ''); + } + + $request .= $ifmodifiedsince . + "User-Agent: PEAR/1.9.0/PHP/" . PHP_VERSION . "\r\n"; + + if (isset($this)) { // only pass in authentication for non-static calls + $username = $config->get('username', null, $channel); + $password = $config->get('password', null, $channel); + if ($username && $password) { + $tmp = base64_encode("$username:$password"); + $request .= "Authorization: Basic $tmp\r\n"; + } + } + + if ($proxy_host != '' && $proxy_user != '') { + $request .= 'Proxy-Authorization: Basic ' . + base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n"; + } + + if ($accept) { + $request .= 'Accept: ' . implode(', ', $accept) . "\r\n"; + } + + $request .= "Connection: close\r\n"; + $request .= "\r\n"; + fwrite($fp, $request); + $headers = array(); + $reply = 0; + while (trim($line = fgets($fp, 1024))) { + if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) { + $headers[strtolower($matches[1])] = trim($matches[2]); + } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) { + $reply = (int)$matches[1]; + if ($reply == 304 && ($lastmodified || ($lastmodified === false))) { + return false; + } + + if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (received: $line)"); + } + } + } + + if ($reply != 200) { + if (!isset($headers['location'])) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirected but no location)"); + } + + if ($wasredirect > 4) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirection looped more than 5 times)"); + } + + $redirect = $wasredirect + 1; + return $this->downloadHttp($headers['location'], + $ui, $save_dir, $callback, $lastmodified, $accept); + } + + if (isset($headers['content-disposition']) && + preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|\\z)/', $headers['content-disposition'], $matches)) { + $save_as = basename($matches[1]); + } else { + $save_as = basename($url); + } + + if ($callback) { + $tmp = call_user_func($callback, 'saveas', $save_as); + if ($tmp) { + $save_as = $tmp; + } + } + + $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as; + if (!$wp = @fopen($dest_file, 'wb')) { + fclose($fp); + if ($callback) { + call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); + } + return PEAR::raiseError("could not open $dest_file for writing"); + } + + $length = isset($headers['content-length']) ? $headers['content-length'] : -1; + + $bytes = 0; + if ($callback) { + call_user_func($callback, 'start', array(basename($dest_file), $length)); + } + + while ($data = fread($fp, 1024)) { + $bytes += strlen($data); + if ($callback) { + call_user_func($callback, 'bytesread', $bytes); + } + if (!@fwrite($wp, $data)) { + fclose($fp); + if ($callback) { + call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); + } + return PEAR::raiseError("$dest_file: write failed ($php_errormsg)"); + } + } + + fclose($fp); + fclose($wp); + if ($callback) { + call_user_func($callback, 'done', $bytes); + } + + if ($lastmodified === false || $lastmodified) { + if (isset($headers['etag'])) { + $lastmodified = array('ETag' => $headers['etag']); + } + + if (isset($headers['last-modified'])) { + if (is_array($lastmodified)) { + $lastmodified['Last-Modified'] = $headers['last-modified']; + } else { + $lastmodified = $headers['last-modified']; + } + } + return array($dest_file, $lastmodified, $headers); + } + return $dest_file; + } +} +// }}} + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Package.php 287560 2009-08-21 22:36:18Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Error code when parameter initialization fails because no releases + * exist within preferred_state, but releases do exist + */ +define('PEAR_DOWNLOADER_PACKAGE_STATE', -1003); +/** + * Error code when parameter initialization fails because no releases + * exist that will work with the existing PHP version + */ +define('PEAR_DOWNLOADER_PACKAGE_PHPVERSION', -1004); + +/** + * Coordinates download parameters and manages their dependencies + * prior to downloading them. + * + * Input can come from three sources: + * + * - local files (archives or package.xml) + * - remote files (downloadable urls) + * - abstract package names + * + * The first two elements are handled cleanly by PEAR_PackageFile, but the third requires + * accessing pearweb's xml-rpc interface to determine necessary dependencies, and the + * format returned of dependencies is slightly different from that used in package.xml. + * + * This class hides the differences between these elements, and makes automatic + * dependency resolution a piece of cake. It also manages conflicts when + * two classes depend on incompatible dependencies, or differing versions of the same + * package dependency. In addition, download will not be attempted if the php version is + * not supported, PEAR installer version is not supported, or non-PECL extensions are not + * installed. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Downloader_Package +{ + /** + * @var PEAR_Downloader + */ + var $_downloader; + /** + * @var PEAR_Config + */ + var $_config; + /** + * @var PEAR_Registry + */ + var $_registry; + /** + * Used to implement packagingroot properly + * @var PEAR_Registry + */ + var $_installRegistry; + /** + * @var PEAR_PackageFile_v1|PEAR_PackageFile|v2 + */ + var $_packagefile; + /** + * @var array + */ + var $_parsedname; + /** + * @var array + */ + var $_downloadURL; + /** + * @var array + */ + var $_downloadDeps = array(); + /** + * @var boolean + */ + var $_valid = false; + /** + * @var boolean + */ + var $_analyzed = false; + /** + * if this or a parent package was invoked with Package-state, this is set to the + * state variable. + * + * This allows temporary reassignment of preferred_state for a parent package and all of + * its dependencies. + * @var string|false + */ + var $_explicitState = false; + /** + * If this package is invoked with Package#group, this variable will be true + */ + var $_explicitGroup = false; + /** + * Package type local|url + * @var string + */ + var $_type; + /** + * Contents of package.xml, if downloaded from a remote channel + * @var string|false + * @access private + */ + var $_rawpackagefile; + /** + * @var boolean + * @access private + */ + var $_validated = false; + + /** + * @param PEAR_Downloader + */ + function PEAR_Downloader_Package(&$downloader) + { + $this->_downloader = &$downloader; + $this->_config = &$this->_downloader->config; + $this->_registry = &$this->_config->getRegistry(); + $options = $downloader->getOptions(); + if (isset($options['packagingroot'])) { + $this->_config->setInstallRoot($options['packagingroot']); + $this->_installRegistry = &$this->_config->getRegistry(); + $this->_config->setInstallRoot(false); + } else { + $this->_installRegistry = &$this->_registry; + } + $this->_valid = $this->_analyzed = false; + } + + /** + * Parse the input and determine whether this is a local file, a remote uri, or an + * abstract package name. + * + * This is the heart of the PEAR_Downloader_Package(), and is used in + * {@link PEAR_Downloader::download()} + * @param string + * @return bool|PEAR_Error + */ + function initialize($param) + { + $origErr = $this->_fromFile($param); + if ($this->_valid) { + return true; + } + + $options = $this->_downloader->getOptions(); + if (isset($options['offline'])) { + if (PEAR::isError($origErr) && !isset($options['soft'])) { + foreach ($origErr->getUserInfo() as $userInfo) { + if (isset($userInfo['message'])) { + $this->_downloader->log(0, $userInfo['message']); + } + } + + $this->_downloader->log(0, $origErr->getMessage()); + } + + return PEAR::raiseError('Cannot download non-local package "' . $param . '"'); + } + + $err = $this->_fromUrl($param); + if (PEAR::isError($err) || !$this->_valid) { + if ($this->_type == 'url') { + if (PEAR::isError($err) && !isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + return PEAR::raiseError("Invalid or missing remote package file"); + } + + $err = $this->_fromString($param); + if (PEAR::isError($err) || !$this->_valid) { + if (PEAR::isError($err) && $err->getCode() == PEAR_DOWNLOADER_PACKAGE_STATE) { + return false; // instruct the downloader to silently skip + } + + if (isset($this->_type) && $this->_type == 'local' && PEAR::isError($origErr)) { + if (is_array($origErr->getUserInfo())) { + foreach ($origErr->getUserInfo() as $err) { + if (is_array($err)) { + $err = $err['message']; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $err); + } + } + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $origErr->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param, true); + } + + if (!isset($options['soft'])) { + $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file"); + } + + // Passing no message back - already logged above + return PEAR::raiseError(); + } + + if (PEAR::isError($err) && !isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param, true); + } + + if (!isset($options['soft'])) { + $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file"); + } + + // Passing no message back - already logged above + return PEAR::raiseError(); + } + } + + return true; + } + + /** + * Retrieve any non-local packages + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|PEAR_Error + */ + function &download() + { + if (isset($this->_packagefile)) { + return $this->_packagefile; + } + + if (isset($this->_downloadURL['url'])) { + $this->_isvalid = false; + $info = $this->getParsedPackage(); + foreach ($info as $i => $p) { + $info[$i] = strtolower($p); + } + + $err = $this->_fromUrl($this->_downloadURL['url'], + $this->_registry->parsedPackageNameToString($this->_parsedname, true)); + $newinfo = $this->getParsedPackage(); + foreach ($newinfo as $i => $p) { + $newinfo[$i] = strtolower($p); + } + + if ($info != $newinfo) { + do { + if ($info['channel'] == 'pecl.php.net' && $newinfo['channel'] == 'pear.php.net') { + $info['channel'] = 'pear.php.net'; + if ($info == $newinfo) { + // skip the channel check if a pecl package says it's a PEAR package + break; + } + } + if ($info['channel'] == 'pear.php.net' && $newinfo['channel'] == 'pecl.php.net') { + $info['channel'] = 'pecl.php.net'; + if ($info == $newinfo) { + // skip the channel check if a pecl package says it's a PEAR package + break; + } + } + + return PEAR::raiseError('CRITICAL ERROR: We are ' . + $this->_registry->parsedPackageNameToString($info) . ', but the file ' . + 'downloaded claims to be ' . + $this->_registry->parsedPackageNameToString($this->getParsedPackage())); + } while (false); + } + + if (PEAR::isError($err) || !$this->_valid) { + return $err; + } + } + + $this->_type = 'local'; + return $this->_packagefile; + } + + function &getPackageFile() + { + return $this->_packagefile; + } + + function &getDownloader() + { + return $this->_downloader; + } + + function getType() + { + return $this->_type; + } + + /** + * Like {@link initialize()}, but operates on a dependency + */ + function fromDepURL($dep) + { + $this->_downloadURL = $dep; + if (isset($dep['uri'])) { + $options = $this->_downloader->getOptions(); + if (!extension_loaded("zlib") || isset($options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->_fromUrl($dep['uri'] . $ext); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + return PEAR::raiseError('Invalid uri dependency "' . $dep['uri'] . $ext . '", ' . + 'cannot download'); + } + } else { + $this->_parsedname = + array( + 'package' => $dep['info']->getPackage(), + 'channel' => $dep['info']->getChannel(), + 'version' => $dep['version'] + ); + if (!isset($dep['nodefault'])) { + $this->_parsedname['group'] = 'default'; // download the default dependency group + $this->_explicitGroup = false; + } + + $this->_rawpackagefile = $dep['raw']; + } + } + + function detectDependencies($params) + { + $options = $this->_downloader->getOptions(); + if (isset($options['downloadonly'])) { + return; + } + + if (isset($options['offline'])) { + $this->_downloader->log(3, 'Skipping dependency download check, --offline specified'); + return; + } + + $pname = $this->getParsedPackage(); + if (!$pname) { + return; + } + + $deps = $this->getDeps(); + if (!$deps) { + return; + } + + if (isset($deps['required'])) { // package.xml 2.0 + return $this->_detect2($deps, $pname, $options, $params); + } + + return $this->_detect1($deps, $pname, $options, $params); + } + + function setValidated() + { + $this->_validated = true; + } + + function alreadyValidated() + { + return $this->_validated; + } + + /** + * Remove packages to be downloaded that are already installed + * @param array of PEAR_Downloader_Package objects + * @static + */ + function removeInstalled(&$params) + { + if (!isset($params[0])) { + return; + } + + $options = $params[0]->_downloader->getOptions(); + if (!isset($options['downloadonly'])) { + foreach ($params as $i => $param) { + $package = $param->getPackage(); + $channel = $param->getChannel(); + // remove self if already installed with this version + // this does not need any pecl magic - we only remove exact matches + if ($param->_installRegistry->packageExists($package, $channel)) { + $packageVersion = $param->_installRegistry->packageInfo($package, 'version', $channel); + if (version_compare($packageVersion, $param->getVersion(), '==')) { + if (!isset($options['force'])) { + $info = $param->getParsedPackage(); + unset($info['version']); + unset($info['state']); + if (!isset($options['soft'])) { + $param->_downloader->log(1, 'Skipping package "' . + $param->getShortName() . + '", already installed as version ' . $packageVersion); + } + $params[$i] = false; + } + } elseif (!isset($options['force']) && !isset($options['upgrade']) && + !isset($options['soft'])) { + $info = $param->getParsedPackage(); + $param->_downloader->log(1, 'Skipping package "' . + $param->getShortName() . + '", already installed as version ' . $packageVersion); + $params[$i] = false; + } + } + } + } + + PEAR_Downloader_Package::removeDuplicates($params); + } + + function _detect2($deps, $pname, $options, $params) + { + $this->_downloadDeps = array(); + $groupnotfound = false; + foreach (array('package', 'subpackage') as $packagetype) { + // get required dependency group + if (isset($deps['required'][$packagetype])) { + if (isset($deps['required'][$packagetype][0])) { + foreach ($deps['required'][$packagetype] as $dep) { + if (isset($dep['conflicts'])) { + // skip any package that this package conflicts with + continue; + } + $ret = $this->_detect2Dep($dep, $pname, 'required', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } else { + $dep = $deps['required'][$packagetype]; + if (!isset($dep['conflicts'])) { + // skip any package that this package conflicts with + $ret = $this->_detect2Dep($dep, $pname, 'required', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } + } + + // get optional dependency group, if any + if (isset($deps['optional'][$packagetype])) { + $skipnames = array(); + if (!isset($deps['optional'][$packagetype][0])) { + $deps['optional'][$packagetype] = array($deps['optional'][$packagetype]); + } + + foreach ($deps['optional'][$packagetype] as $dep) { + $skip = false; + if (!isset($options['alldeps'])) { + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->_registry->parsedPackageNameToString($this->getParsedPackage(), + true) . '" optional dependency "' . + $this->_registry->parsedPackageNameToString(array('package' => + $dep['name'], 'channel' => 'pear.php.net'), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString($dep, true); + $skip = true; + unset($dep['package']); + } + + $ret = $this->_detect2Dep($dep, $pname, 'optional', $params); + if (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + + if (!$ret) { + $dep['package'] = $dep['name']; + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + } + + if (!$skip && is_array($ret)) { + $this->_downloadDeps[] = $ret; + } + } + + if (count($skipnames)) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'Did not download optional dependencies: ' . + implode(', ', $skipnames) . + ', use --alldeps to download automatically'); + } + } + } + + // get requested dependency group, if any + $groupname = $this->getGroup(); + $explicit = $this->_explicitGroup; + if (!$groupname) { + if (!$this->canDefault()) { + continue; + } + + $groupname = 'default'; // try the default dependency group + } + + if ($groupnotfound) { + continue; + } + + if (isset($deps['group'])) { + if (isset($deps['group']['attribs'])) { + if (strtolower($deps['group']['attribs']['name']) == strtolower($groupname)) { + $group = $deps['group']; + } elseif ($explicit) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Warning: package "' . + $this->_registry->parsedPackageNameToString($pname, true) . + '" has no dependency ' . 'group named "' . $groupname . '"'); + } + + $groupnotfound = true; + continue; + } + } else { + $found = false; + foreach ($deps['group'] as $group) { + if (strtolower($group['attribs']['name']) == strtolower($groupname)) { + $found = true; + break; + } + } + + if (!$found) { + if ($explicit) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Warning: package "' . + $this->_registry->parsedPackageNameToString($pname, true) . + '" has no dependency ' . 'group named "' . $groupname . '"'); + } + } + + $groupnotfound = true; + continue; + } + } + } + + if (isset($group) && isset($group[$packagetype])) { + if (isset($group[$packagetype][0])) { + foreach ($group[$packagetype] as $dep) { + $ret = $this->_detect2Dep($dep, $pname, 'dependency group "' . + $group['attribs']['name'] . '"', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } else { + $ret = $this->_detect2Dep($group[$packagetype], $pname, + 'dependency group "' . + $group['attribs']['name'] . '"', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } + } + } + + function _detect2Dep($dep, $pname, $group, $params) + { + if (isset($dep['conflicts'])) { + return true; + } + + $options = $this->_downloader->getOptions(); + if (isset($dep['uri'])) { + return array('uri' => $dep['uri'], 'dep' => $dep);; + } + + $testdep = $dep; + $testdep['package'] = $dep['name']; + if (PEAR_Downloader_Package::willDownload($testdep, $params)) { + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", will be installed'); + } + return false; + } + + $options = $this->_downloader->getOptions(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if ($this->_explicitState) { + $pname['state'] = $this->_explicitState; + } + + $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname); + if (PEAR::isError($url)) { + PEAR::popErrorHandling(); + return $url; + } + + $dep['package'] = $dep['name']; + $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params, $group == 'optional' && + !isset($options['alldeps']), true); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + + return false; + } + + // check to see if a dep is already installed and is the same or newer + if (!isset($dep['min']) && !isset($dep['max']) && !isset($dep['recommended'])) { + $oper = 'has'; + } else { + $oper = 'gt'; + } + + // do not try to move this before getDepPackageDownloadURL + // we can't determine whether upgrade is necessary until we know what + // version would be downloaded + if (!isset($options['force']) && $this->isInstalled($ret, $oper)) { + $version = $this->_installRegistry->packageInfo($dep['name'], 'version', $dep['channel']); + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '" version ' . $url['version'] . ', already installed as version ' . + $version); + } + + return false; + } + + if (isset($dep['nodefault'])) { + $ret['nodefault'] = true; + } + + return $ret; + } + + function _detect1($deps, $pname, $options, $params) + { + $this->_downloadDeps = array(); + $skipnames = array(); + foreach ($deps as $dep) { + $nodownload = false; + if (isset ($dep['type']) && $dep['type'] === 'pkg') { + $dep['channel'] = 'pear.php.net'; + $dep['package'] = $dep['name']; + switch ($dep['rel']) { + case 'not' : + continue 2; + case 'ge' : + case 'eq' : + case 'gt' : + case 'has' : + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + if (PEAR_Downloader_Package::willDownload($dep, $params)) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group + . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", will be installed'); + continue 2; + } + $fakedp = new PEAR_PackageFile_v1; + $fakedp->setPackage($dep['name']); + // skip internet check if we are not upgrading (bug #5810) + if (!isset($options['upgrade']) && $this->isInstalled( + $fakedp, $dep['rel'])) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group + . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", is already installed'); + continue 2; + } + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if ($this->_explicitState) { + $pname['state'] = $this->_explicitState; + } + + $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname); + $chan = 'pear.php.net'; + if (PEAR::isError($url)) { + // check to see if this is a pecl package that has jumped + // from pear.php.net to pecl.php.net channel + if (!class_exists('PEAR_Dependency2')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Dependency2.php'; + } + + $newdep = PEAR_Dependency2::normalizeDep($dep); + $newdep = $newdep[0]; + $newdep['channel'] = 'pecl.php.net'; + $chan = 'pecl.php.net'; + $url = $this->_downloader->_getDepPackageDownloadUrl($newdep, $pname); + $obj = &$this->_installRegistry->getPackage($dep['name']); + if (PEAR::isError($url)) { + PEAR::popErrorHandling(); + if ($obj !== null && $this->isInstalled($obj, $dep['rel'])) { + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . + ': Skipping ' . $group . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", already installed as version ' . $obj->getVersion()); + } + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + continue; + } else { + if (isset($dep['optional']) && $dep['optional'] == 'yes') { + $this->_downloader->log(2, $this->getShortName() . + ': Skipping optional dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", no releases exist'); + continue; + } else { + return $url; + } + } + } + } + + PEAR::popErrorHandling(); + if (!isset($options['alldeps'])) { + if (isset($dep['optional']) && $dep['optional'] == 'yes') { + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->getShortName() . + '" optional dependency "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true); + $nodownload = true; + } + } + + if (!isset($options['alldeps']) && !isset($options['onlyreqdeps'])) { + if (!isset($dep['optional']) || $dep['optional'] == 'no') { + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->getShortName() . + '" required dependency "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true); + $nodownload = true; + } + } + + // check to see if a dep is already installed + // do not try to move this before getDepPackageDownloadURL + // we can't determine whether upgrade is necessary until we know what + // version would be downloaded + if (!isset($options['force']) && $this->isInstalled( + $url, $dep['rel'])) { + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + $dep['package'] = $dep['name']; + if (isset($newdep)) { + $version = $this->_installRegistry->packageInfo($newdep['name'], 'version', $newdep['channel']); + } else { + $version = $this->_installRegistry->packageInfo($dep['name'], 'version'); + } + + $dep['version'] = $url['version']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", already installed as version ' . $version); + } + + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + + continue; + } + + if ($nodownload) { + continue; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (isset($newdep)) { + $dep = $newdep; + } + + $dep['package'] = $dep['name']; + $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params, + isset($dep['optional']) && $dep['optional'] == 'yes' && + !isset($options['alldeps']), true); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + continue; + } + + $this->_downloadDeps[] = $ret; + } + } + + if (count($skipnames)) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'Did not download dependencies: ' . + implode(', ', $skipnames) . + ', use --alldeps or --onlyreqdeps to download automatically'); + } + } + } + + function setDownloadURL($pkg) + { + $this->_downloadURL = $pkg; + } + + /** + * Set the package.xml object for this downloaded package + * + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 $pkg + */ + function setPackageFile(&$pkg) + { + $this->_packagefile = &$pkg; + } + + function getShortName() + { + return $this->_registry->parsedPackageNameToString(array('channel' => $this->getChannel(), + 'package' => $this->getPackage()), true); + } + + function getParsedPackage() + { + if (isset($this->_packagefile) || isset($this->_parsedname)) { + return array('channel' => $this->getChannel(), + 'package' => $this->getPackage(), + 'version' => $this->getVersion()); + } + + return false; + } + + function getDownloadURL() + { + return $this->_downloadURL; + } + + function canDefault() + { + if (isset($this->_downloadURL) && isset($this->_downloadURL['nodefault'])) { + return false; + } + + return true; + } + + function getPackage() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackage(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackage(); + } + + return false; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function isSubpackage(&$pf) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isSubpackage($pf); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->isSubpackage($pf); + } + + return false; + } + + function getPackageType() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackageType(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackageType(); + } + + return false; + } + + function isBundle() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackageType() == 'bundle'; + } + + return false; + } + + function getPackageXmlVersion() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackagexmlVersion(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackagexmlVersion(); + } + + return '1.0'; + } + + function getChannel() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getChannel(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getChannel(); + } + + return false; + } + + function getURI() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getURI(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getURI(); + } + + return false; + } + + function getVersion() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getVersion(); + } elseif (isset($this->_downloadURL['version'])) { + return $this->_downloadURL['version']; + } + + return false; + } + + function isCompatible($pf) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isCompatible($pf); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->isCompatible($pf); + } + + return true; + } + + function setGroup($group) + { + $this->_parsedname['group'] = $group; + } + + function getGroup() + { + if (isset($this->_parsedname['group'])) { + return $this->_parsedname['group']; + } + + return ''; + } + + function isExtension($name) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isExtension($name); + } elseif (isset($this->_downloadURL['info'])) { + if ($this->_downloadURL['info']->getPackagexmlVersion() == '2.0') { + return $this->_downloadURL['info']->getProvidesExtension() == $name; + } + + return false; + } + + return false; + } + + function getDeps() + { + if (isset($this->_packagefile)) { + $ver = $this->_packagefile->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + return $this->_packagefile->getDeps(true); + } + + return $this->_packagefile->getDeps(); + } elseif (isset($this->_downloadURL['info'])) { + $ver = $this->_downloadURL['info']->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + return $this->_downloadURL['info']->getDeps(true); + } + + return $this->_downloadURL['info']->getDeps(); + } + + return array(); + } + + /** + * @param array Parsed array from {@link PEAR_Registry::parsePackageName()} or a dependency + * returned from getDepDownloadURL() + */ + function isEqual($param) + { + if (is_object($param)) { + $channel = $param->getChannel(); + $package = $param->getPackage(); + if ($param->getURI()) { + $param = array( + 'channel' => $param->getChannel(), + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + 'uri' => $param->getURI(), + ); + } else { + $param = array( + 'channel' => $param->getChannel(), + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + ); + } + } else { + if (isset($param['uri'])) { + if ($this->getChannel() != '__uri') { + return false; + } + return $param['uri'] == $this->getURI(); + } + + $package = isset($param['package']) ? $param['package'] : $param['info']->getPackage(); + $channel = isset($param['channel']) ? $param['channel'] : $param['info']->getChannel(); + if (isset($param['rel'])) { + if (!class_exists('PEAR_Dependency2')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Dependency2.php'; + } + + $newdep = PEAR_Dependency2::normalizeDep($param); + $newdep = $newdep[0]; + } elseif (isset($param['min'])) { + $newdep = $param; + } + } + + if (isset($newdep)) { + if (!isset($newdep['min'])) { + $newdep['min'] = '0'; + } + + if (!isset($newdep['max'])) { + $newdep['max'] = '100000000000000000000'; + } + + // use magic to support pecl packages suddenly jumping to the pecl channel + // we need to support both dependency possibilities + if ($channel == 'pear.php.net' && $this->getChannel() == 'pecl.php.net') { + if ($package == $this->getPackage()) { + $channel = 'pecl.php.net'; + } + } + if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') { + if ($package == $this->getPackage()) { + $channel = 'pear.php.net'; + } + } + + return (strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel() && + version_compare($newdep['min'], $this->getVersion(), '<=') && + version_compare($newdep['max'], $this->getVersion(), '>=')); + } + + // use magic to support pecl packages suddenly jumping to the pecl channel + if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') { + if (strtolower($package) == strtolower($this->getPackage())) { + $channel = 'pear.php.net'; + } + } + + if (isset($param['version'])) { + return (strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel() && + $param['version'] == $this->getVersion()); + } + + return strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel(); + } + + function isInstalled($dep, $oper = '==') + { + if (!$dep) { + return false; + } + + if ($oper != 'ge' && $oper != 'gt' && $oper != 'has' && $oper != '==') { + return false; + } + + if (is_object($dep)) { + $package = $dep->getPackage(); + $channel = $dep->getChannel(); + if ($dep->getURI()) { + $dep = array( + 'uri' => $dep->getURI(), + 'version' => $dep->getVersion(), + ); + } else { + $dep = array( + 'version' => $dep->getVersion(), + ); + } + } else { + if (isset($dep['uri'])) { + $channel = '__uri'; + $package = $dep['dep']['name']; + } else { + $channel = $dep['info']->getChannel(); + $package = $dep['info']->getPackage(); + } + } + + $options = $this->_downloader->getOptions(); + $test = $this->_installRegistry->packageExists($package, $channel); + if (!$test && $channel == 'pecl.php.net') { + // do magic to allow upgrading from old pecl packages to new ones + $test = $this->_installRegistry->packageExists($package, 'pear.php.net'); + $channel = 'pear.php.net'; + } + + if ($test) { + if (isset($dep['uri'])) { + if ($this->_installRegistry->packageInfo($package, 'uri', '__uri') == $dep['uri']) { + return true; + } + } + + if (isset($options['upgrade'])) { + $packageVersion = $this->_installRegistry->packageInfo($package, 'version', $channel); + if (version_compare($packageVersion, $dep['version'], '>=')) { + return true; + } + + return false; + } + + return true; + } + + return false; + } + + /** + * Detect duplicate package names with differing versions + * + * If a user requests to install Date 1.4.6 and Date 1.4.7, + * for instance, this is a logic error. This method + * detects this situation. + * + * @param array $params array of PEAR_Downloader_Package objects + * @param array $errorparams empty array + * @return array array of stupid duplicated packages in PEAR_Downloader_Package obejcts + */ + function detectStupidDuplicates($params, &$errorparams) + { + $existing = array(); + foreach ($params as $i => $param) { + $package = $param->getPackage(); + $channel = $param->getChannel(); + $group = $param->getGroup(); + if (!isset($existing[$channel . '/' . $package])) { + $existing[$channel . '/' . $package] = array(); + } + + if (!isset($existing[$channel . '/' . $package][$group])) { + $existing[$channel . '/' . $package][$group] = array(); + } + + $existing[$channel . '/' . $package][$group][] = $i; + } + + $indices = array(); + foreach ($existing as $package => $groups) { + foreach ($groups as $group => $dupes) { + if (count($dupes) > 1) { + $indices = $indices + $dupes; + } + } + } + + $indices = array_unique($indices); + foreach ($indices as $index) { + $errorparams[] = $params[$index]; + } + + return count($errorparams); + } + + /** + * @param array + * @param bool ignore install groups - for final removal of dupe packages + * @static + */ + function removeDuplicates(&$params, $ignoreGroups = false) + { + $pnames = array(); + foreach ($params as $i => $param) { + if (!$param) { + continue; + } + + if ($param->getPackage()) { + $group = $ignoreGroups ? '' : $param->getGroup(); + $pnames[$i] = $param->getChannel() . '/' . + $param->getPackage() . '-' . $param->getVersion() . '#' . $group; + } + } + + $pnames = array_unique($pnames); + $unset = array_diff(array_keys($params), array_keys($pnames)); + $testp = array_flip($pnames); + foreach ($params as $i => $param) { + if (!$param) { + $unset[] = $i; + continue; + } + + if (!is_a($param, 'PEAR_Downloader_Package')) { + $unset[] = $i; + continue; + } + + $group = $ignoreGroups ? '' : $param->getGroup(); + if (!isset($testp[$param->getChannel() . '/' . $param->getPackage() . '-' . + $param->getVersion() . '#' . $group])) { + $unset[] = $i; + } + } + + foreach ($unset as $i) { + unset($params[$i]); + } + + $ret = array(); + foreach ($params as $i => $param) { + $ret[] = &$params[$i]; + } + + $params = array(); + foreach ($ret as $i => $param) { + $params[] = &$ret[$i]; + } + } + + function explicitState() + { + return $this->_explicitState; + } + + function setExplicitState($s) + { + $this->_explicitState = $s; + } + + /** + * @static + */ + function mergeDependencies(&$params) + { + $bundles = $newparams = array(); + foreach ($params as $i => $param) { + if (!$param->isBundle()) { + continue; + } + + $bundles[] = $i; + $pf = &$param->getPackageFile(); + $newdeps = array(); + $contents = $pf->getBundledPackages(); + if (!is_array($contents)) { + $contents = array($contents); + } + + foreach ($contents as $file) { + $filecontents = $pf->getFileContents($file); + $dl = &$param->getDownloader(); + $options = $dl->getOptions(); + if (PEAR::isError($dir = $dl->getDownloadDir())) { + return $dir; + } + + $fp = @fopen($dir . DIRECTORY_SEPARATOR . $file, 'wb'); + if (!$fp) { + continue; + } + + fwrite($fp, $filecontents, strlen($filecontents)); + fclose($fp); + if ($s = $params[$i]->explicitState()) { + $obj->setExplicitState($s); + } + + $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader()); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($dir = $dl->getDownloadDir())) { + PEAR::popErrorHandling(); + return $dir; + } + + $e = $obj->_fromFile($a = $dir . DIRECTORY_SEPARATOR . $file); + PEAR::popErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $dl->log(0, $e->getMessage()); + } + continue; + } + + $j = &$obj; + if (!PEAR_Downloader_Package::willDownload($j, + array_merge($params, $newparams)) && !$param->isInstalled($j)) { + $newparams[] = &$j; + } + } + } + + foreach ($bundles as $i) { + unset($params[$i]); // remove bundles - only their contents matter for installation + } + + PEAR_Downloader_Package::removeDuplicates($params); // strip any unset indices + if (count($newparams)) { // add in bundled packages for install + foreach ($newparams as $i => $unused) { + $params[] = &$newparams[$i]; + } + $newparams = array(); + } + + foreach ($params as $i => $param) { + $newdeps = array(); + foreach ($param->_downloadDeps as $dep) { + $merge = array_merge($params, $newparams); + if (!PEAR_Downloader_Package::willDownload($dep, $merge) + && !$param->isInstalled($dep) + ) { + $newdeps[] = $dep; + } else { + //var_dump($dep); + // detect versioning conflicts here + } + } + + // convert the dependencies into PEAR_Downloader_Package objects for the next time around + $params[$i]->_downloadDeps = array(); + foreach ($newdeps as $dep) { + $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader()); + if ($s = $params[$i]->explicitState()) { + $obj->setExplicitState($s); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $e = $obj->fromDepURL($dep); + PEAR::popErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $obj->_downloader->log(0, $e->getMessage()); + } + continue; + } + + $e = $obj->detectDependencies($params); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $obj->_downloader->log(0, $e->getMessage()); + } + } + + $j = &$obj; + $newparams[] = &$j; + } + } + + if (count($newparams)) { + foreach ($newparams as $i => $unused) { + $params[] = &$newparams[$i]; + } + return true; + } + + return false; + } + + + /** + * @static + */ + function willDownload($param, $params) + { + if (!is_array($params)) { + return false; + } + + foreach ($params as $obj) { + if ($obj->isEqual($param)) { + return true; + } + } + + return false; + } + + /** + * For simpler unit-testing + * @param PEAR_Config + * @param int + * @param string + */ + function &getPackagefileObject(&$c, $d, $t = false) + { + $a = &new PEAR_PackageFile($c, $d, $t); + return $a; + } + + + /** + * This will retrieve from a local file if possible, and parse out + * a group name as well. The original parameter will be modified to reflect this. + * @param string|array can be a parsed package name as well + * @access private + */ + function _fromFile(&$param) + { + $saveparam = $param; + if (is_string($param)) { + if (!@file_exists($param)) { + $test = explode('#', $param); + $group = array_pop($test); + if (@file_exists(implode('#', $test))) { + $this->setGroup($group); + $param = implode('#', $test); + $this->_explicitGroup = true; + } + } + + if (@is_file($param)) { + $this->_type = 'local'; + $options = $this->_downloader->getOptions(); + if (isset($options['downloadonly'])) { + $pkg = &$this->getPackagefileObject($this->_config, + $this->_downloader->_debug); + } else { + if (PEAR::isError($dir = $this->_downloader->getDownloadDir())) { + return $dir; + } + $pkg = &$this->getPackagefileObject($this->_config, + $this->_downloader->_debug, $dir); + } + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$pkg->fromAnyFile($param, PEAR_VALIDATE_INSTALLING); + PEAR::popErrorHandling(); + if (PEAR::isError($pf)) { + $this->_valid = false; + $param = $saveparam; + return $pf; + } + $this->_packagefile = &$pf; + if (!$this->getGroup()) { + $this->setGroup('default'); // install the default dependency group + } + return $this->_valid = true; + } + } + $param = $saveparam; + return $this->_valid = false; + } + + function _fromUrl($param, $saveparam = '') + { + if (!is_array($param) && (preg_match('#^(http|https|ftp)://#', $param))) { + $options = $this->_downloader->getOptions(); + $this->_type = 'url'; + $callback = $this->_downloader->ui ? + array(&$this->_downloader, '_downloadCallback') : null; + $this->_downloader->pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($dir = $this->_downloader->getDownloadDir())) { + $this->_downloader->popErrorHandling(); + return $dir; + } + + $this->_downloader->log(3, 'Downloading "' . $param . '"'); + $file = $this->_downloader->downloadHttp($param, $this->_downloader->ui, + $dir, $callback, null, false, $this->getChannel()); + $this->_downloader->popErrorHandling(); + if (PEAR::isError($file)) { + if (!empty($saveparam)) { + $saveparam = ", cannot download \"$saveparam\""; + } + $err = PEAR::raiseError('Could not download from "' . $param . + '"' . $saveparam . ' (' . $file->getMessage() . ')'); + return $err; + } + + if ($this->_rawpackagefile) { + require_once 'phar://go-pear.phar/' . 'Archive/Tar.php'; + $tar = &new Archive_Tar($file); + $packagexml = $tar->extractInString('package2.xml'); + if (!$packagexml) { + $packagexml = $tar->extractInString('package.xml'); + } + + if (str_replace(array("\n", "\r"), array('',''), $packagexml) != + str_replace(array("\n", "\r"), array('',''), $this->_rawpackagefile)) { + if ($this->getChannel() != 'pear.php.net') { + return PEAR::raiseError('CRITICAL ERROR: package.xml downloaded does ' . + 'not match value returned from xml-rpc'); + } + + // be more lax for the existing PEAR packages that have not-ok + // characters in their package.xml + $this->_downloader->log(0, 'CRITICAL WARNING: The "' . + $this->getPackage() . '" package has invalid characters in its ' . + 'package.xml. The next version of PEAR may not be able to install ' . + 'this package for security reasons. Please open a bug report at ' . + 'http://pear.php.net/package/' . $this->getPackage() . '/bugs'); + } + } + + // whew, download worked! + if (isset($options['downloadonly'])) { + $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug); + } else { + $dir = $this->_downloader->getDownloadDir(); + if (PEAR::isError($dir)) { + return $dir; + } + $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug, $dir); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$pkg->fromAnyFile($file, PEAR_VALIDATE_INSTALLING); + PEAR::popErrorHandling(); + if (PEAR::isError($pf)) { + if (is_array($pf->getUserInfo())) { + foreach ($pf->getUserInfo() as $err) { + if (is_array($err)) { + $err = $err['message']; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, "Validation Error: $err"); + } + } + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pf->getMessage()); + } + + ///FIXME need to pass back some error code that we can use to match with to cancel all further operations + /// At least stop all deps of this package from being installed + $out = $saveparam ? $saveparam : $param; + $err = PEAR::raiseError('Download of "' . $out . '" succeeded, but it is not a valid package archive'); + $this->_valid = false; + return $err; + } + + $this->_packagefile = &$pf; + $this->setGroup('default'); // install the default dependency group + return $this->_valid = true; + } + + return $this->_valid = false; + } + + /** + * + * @param string|array pass in an array of format + * array( + * 'package' => 'pname', + * ['channel' => 'channame',] + * ['version' => 'version',] + * ['state' => 'state',]) + * or a string of format [channame/]pname[-version|-state] + */ + function _fromString($param) + { + $options = $this->_downloader->getOptions(); + $channel = $this->_config->get('default_channel'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pname = $this->_registry->parsePackageName($param, $channel); + PEAR::popErrorHandling(); + if (PEAR::isError($pname)) { + if ($pname->getCode() == 'invalid') { + $this->_valid = false; + return false; + } + + if ($pname->getCode() == 'channel') { + $parsed = $pname->getUserInfo(); + if ($this->_downloader->discover($parsed['channel'])) { + if ($this->_config->get('auto_discover')) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pname = $this->_registry->parsePackageName($param, $channel); + PEAR::popErrorHandling(); + } else { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Channel "' . $parsed['channel'] . + '" is not initialized, use ' . + '"pear channel-discover ' . $parsed['channel'] . '" to initialize' . + 'or pear config-set auto_discover 1'); + } + } + } + + if (PEAR::isError($pname)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pname->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param); + } + + $err = PEAR::raiseError('invalid package name/package file "' . $param . '"'); + $this->_valid = false; + return $err; + } + } else { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pname->getMessage()); + } + + $err = PEAR::raiseError('invalid package name/package file "' . $param . '"'); + $this->_valid = false; + return $err; + } + } + + if (!isset($this->_type)) { + $this->_type = 'rest'; + } + + $this->_parsedname = $pname; + $this->_explicitState = isset($pname['state']) ? $pname['state'] : false; + $this->_explicitGroup = isset($pname['group']) ? true : false; + + $info = $this->_downloader->_getPackageDownloadUrl($pname); + if (PEAR::isError($info)) { + if ($info->getCode() != -976 && $pname['channel'] == 'pear.php.net') { + // try pecl + $pname['channel'] = 'pecl.php.net'; + if ($test = $this->_downloader->_getPackageDownloadUrl($pname)) { + if (!PEAR::isError($test)) { + $info = PEAR::raiseError($info->getMessage() . ' - package ' . + $this->_registry->parsedPackageNameToString($pname, true) . + ' can be installed with "pecl install ' . $pname['package'] . + '"'); + } else { + $pname['channel'] = 'pear.php.net'; + } + } else { + $pname['channel'] = 'pear.php.net'; + } + } + + return $info; + } + + $this->_rawpackagefile = $info['raw']; + $ret = $this->_analyzeDownloadURL($info, $param, $pname); + if (PEAR::isError($ret)) { + return $ret; + } + + if ($ret) { + $this->_downloadURL = $ret; + return $this->_valid = (bool) $ret; + } + } + + /** + * @param array output of package.getDownloadURL + * @param string|array|object information for detecting packages to be downloaded, and + * for errors + * @param array name information of the package + * @param array|null packages to be downloaded + * @param bool is this an optional dependency? + * @param bool is this any kind of dependency? + * @access private + */ + function _analyzeDownloadURL($info, $param, $pname, $params = null, $optional = false, + $isdependency = false) + { + if (!is_string($param) && PEAR_Downloader_Package::willDownload($param, $params)) { + return false; + } + + if ($info === false) { + $saveparam = !is_string($param) ? ", cannot download \"$param\"" : ''; + + // no releases exist + return PEAR::raiseError('No releases for package "' . + $this->_registry->parsedPackageNameToString($pname, true) . '" exist' . $saveparam); + } + + if (strtolower($info['info']->getChannel()) != strtolower($pname['channel'])) { + $err = false; + if ($pname['channel'] == 'pecl.php.net') { + if ($info['info']->getChannel() != 'pear.php.net') { + $err = true; + } + } elseif ($info['info']->getChannel() == 'pecl.php.net') { + if ($pname['channel'] != 'pear.php.net') { + $err = true; + } + } else { + $err = true; + } + + if ($err) { + return PEAR::raiseError('SECURITY ERROR: package in channel "' . $pname['channel'] . + '" retrieved another channel\'s name for download! ("' . + $info['info']->getChannel() . '")'); + } + } + + $preferred_state = $this->_config->get('preferred_state'); + if (!isset($info['url'])) { + $package_version = $this->_registry->packageInfo($info['info']->getPackage(), + 'version', $info['info']->getChannel()); + if ($this->isInstalled($info)) { + if ($isdependency && version_compare($info['version'], $package_version, '<=')) { + // ignore bogus errors of "failed to download dependency" + // if it is already installed and the one that would be + // downloaded is older or the same version (Bug #7219) + return false; + } + } + + if ($info['version'] === $package_version) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . '-' . $package_version. ', additionally the suggested version' . + ' (' . $package_version . ') is the same as the locally installed one.'); + } + + return false; + } + + if (version_compare($info['version'], $package_version, '<=')) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . '-' . $package_version . ', additionally the suggested version' . + ' (' . $info['version'] . ') is a lower version than the locally installed one (' . $package_version . ').'); + } + + return false; + } + + $instead = ', will instead download version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '"'; + // releases exist, but we failed to get any + if (isset($this->_downloader->_options['force'])) { + if (isset($pname['version'])) { + $vs = ', version "' . $pname['version'] . '"'; + } elseif (isset($pname['state'])) { + $vs = ', stability "' . $pname['state'] . '"'; + } elseif ($param == 'dependency') { + if (!class_exists('PEAR_Common')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Common.php'; + } + + if (!in_array($info['info']->getState(), + PEAR_Common::betterStates($preferred_state, true))) { + if ($optional) { + // don't spit out confusing error message + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = ' within preferred state "' . $preferred_state . + '"'; + } else { + if (!class_exists('PEAR_Dependency2')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Dependency2.php'; + } + + if ($optional) { + // don't spit out confusing error message + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = PEAR_Dependency2::_getExtraString($pname); + $instead = ''; + } + } else { + $vs = ' within preferred state "' . $preferred_state . '"'; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . $vs . $instead); + } + + // download the latest release + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } else { + if (isset($info['php']) && $info['php']) { + $err = PEAR::raiseError('Failed to download ' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], + 'package' => $pname['package']), + true) . + ', latest release is version ' . $info['php']['v'] . + ', but it requires PHP version "' . + $info['php']['m'] . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['php']['v'])) . '" to install', + PEAR_DOWNLOADER_PACKAGE_PHPVERSION); + return $err; + } + + // construct helpful error message + if (isset($pname['version'])) { + $vs = ', version "' . $pname['version'] . '"'; + } elseif (isset($pname['state'])) { + $vs = ', stability "' . $pname['state'] . '"'; + } elseif ($param == 'dependency') { + if (!class_exists('PEAR_Common')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Common.php'; + } + + if (!in_array($info['info']->getState(), + PEAR_Common::betterStates($preferred_state, true))) { + if ($optional) { + // don't spit out confusing error message, and don't die on + // optional dep failure! + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = ' within preferred state "' . $preferred_state . '"'; + } else { + if (!class_exists('PEAR_Dependency2')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Dependency2.php'; + } + + if ($optional) { + // don't spit out confusing error message, and don't die on + // optional dep failure! + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = PEAR_Dependency2::_getExtraString($pname); + } + } else { + $vs = ' within preferred state "' . $this->_downloader->config->get('preferred_state') . '"'; + } + + $options = $this->_downloader->getOptions(); + // this is only set by the "download-all" command + if (isset($options['ignorepreferred_state'])) { + $err = PEAR::raiseError( + 'Failed to download ' . $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package']), + true) + . $vs . + ', latest release is version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['version'])) . '" to install', + PEAR_DOWNLOADER_PACKAGE_STATE); + return $err; + } + + // Checks if the user has a package installed already and checks the release against + // the state against the installed package, this allows upgrades for packages + // with lower stability than the preferred_state + $stability = $this->_registry->packageInfo($pname['package'], 'stability', $pname['channel']); + if (!$this->isInstalled($info) + || !in_array($info['info']->getState(), PEAR_Common::betterStates($stability['release'], true)) + ) { + $err = PEAR::raiseError( + 'Failed to download ' . $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package']), + true) + . $vs . + ', latest release is version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['version'])) . '" to install'); + return $err; + } + } + } + + if (isset($info['deprecated']) && $info['deprecated']) { + $this->_downloader->log(0, + 'WARNING: "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $info['info']->getChannel(), + 'package' => $info['info']->getPackage()), true) . + '" is deprecated in favor of "' . + $this->_registry->parsedPackageNameToString($info['deprecated'], true) . + '"'); + } + + return $info; + } +} + * @copyright 2004-2008 Greg Beaver + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: ErrorStack.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR_ErrorStack + */ + +/** + * Singleton storage + * + * Format: + *
    + * array(
    + *  'package1' => PEAR_ErrorStack object,
    + *  'package2' => PEAR_ErrorStack object,
    + *  ...
    + * )
    + * 
    + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] + */ +$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array(); + +/** + * Global error callback (default) + * + * This is only used if set to non-false. * is the default callback for + * all packages, whereas specific packages may set a default callback + * for all instances, regardless of whether they are a singleton or not. + * + * To exclude non-singletons, only set the local callback for the singleton + * @see PEAR_ErrorStack::setDefaultCallback() + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] + */ +$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array( + '*' => false, +); + +/** + * Global Log object (default) + * + * This is only used if set to non-false. Use to set a default log object for + * all stacks, regardless of instantiation order or location + * @see PEAR_ErrorStack::setDefaultLogger() + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] + */ +$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false; + +/** + * Global Overriding Callback + * + * This callback will override any error callbacks that specific loggers have set. + * Use with EXTREME caution + * @see PEAR_ErrorStack::staticPushCallback() + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] + */ +$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array(); + +/**#@+ + * One of four possible return values from the error Callback + * @see PEAR_ErrorStack::_errorCallback() + */ +/** + * If this is returned, then the error will be both pushed onto the stack + * and logged. + */ +define('PEAR_ERRORSTACK_PUSHANDLOG', 1); +/** + * If this is returned, then the error will only be pushed onto the stack, + * and not logged. + */ +define('PEAR_ERRORSTACK_PUSH', 2); +/** + * If this is returned, then the error will only be logged, but not pushed + * onto the error stack. + */ +define('PEAR_ERRORSTACK_LOG', 3); +/** + * If this is returned, then the error is completely ignored. + */ +define('PEAR_ERRORSTACK_IGNORE', 4); +/** + * If this is returned, then the error is logged and die() is called. + */ +define('PEAR_ERRORSTACK_DIE', 5); +/**#@-*/ + +/** + * Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in + * the singleton method. + */ +define('PEAR_ERRORSTACK_ERR_NONCLASS', 1); + +/** + * Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()} + * that has no __toString() method + */ +define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2); +/** + * Error Stack Implementation + * + * Usage: + * + * // global error stack + * $global_stack = &PEAR_ErrorStack::singleton('MyPackage'); + * // local error stack + * $local_stack = new PEAR_ErrorStack('MyPackage'); + * + * @author Greg Beaver + * @version 1.9.0 + * @package PEAR_ErrorStack + * @category Debugging + * @copyright 2004-2008 Greg Beaver + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: ErrorStack.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR_ErrorStack + */ +class PEAR_ErrorStack { + /** + * Errors are stored in the order that they are pushed on the stack. + * @since 0.4alpha Errors are no longer organized by error level. + * This renders pop() nearly unusable, and levels could be more easily + * handled in a callback anyway + * @var array + * @access private + */ + var $_errors = array(); + + /** + * Storage of errors by level. + * + * Allows easy retrieval and deletion of only errors from a particular level + * @since PEAR 1.4.0dev + * @var array + * @access private + */ + var $_errorsByLevel = array(); + + /** + * Package name this error stack represents + * @var string + * @access protected + */ + var $_package; + + /** + * Determines whether a PEAR_Error is thrown upon every error addition + * @var boolean + * @access private + */ + var $_compat = false; + + /** + * If set to a valid callback, this will be used to generate the error + * message from the error code, otherwise the message passed in will be + * used + * @var false|string|array + * @access private + */ + var $_msgCallback = false; + + /** + * If set to a valid callback, this will be used to generate the error + * context for an error. For PHP-related errors, this will be a file + * and line number as retrieved from debug_backtrace(), but can be + * customized for other purposes. The error might actually be in a separate + * configuration file, or in a database query. + * @var false|string|array + * @access protected + */ + var $_contextCallback = false; + + /** + * If set to a valid callback, this will be called every time an error + * is pushed onto the stack. The return value will be used to determine + * whether to allow an error to be pushed or logged. + * + * The return value must be one an PEAR_ERRORSTACK_* constant + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @var false|string|array + * @access protected + */ + var $_errorCallback = array(); + + /** + * PEAR::Log object for logging errors + * @var false|Log + * @access protected + */ + var $_logger = false; + + /** + * Error messages - designed to be overridden + * @var array + * @abstract + */ + var $_errorMsgs = array(); + + /** + * Set up a new error stack + * + * @param string $package name of the package this error stack represents + * @param callback $msgCallback callback used for error message generation + * @param callback $contextCallback callback used for context generation, + * defaults to {@link getFileLine()} + * @param boolean $throwPEAR_Error + */ + function PEAR_ErrorStack($package, $msgCallback = false, $contextCallback = false, + $throwPEAR_Error = false) + { + $this->_package = $package; + $this->setMessageCallback($msgCallback); + $this->setContextCallback($contextCallback); + $this->_compat = $throwPEAR_Error; + } + + /** + * Return a single error stack for this package. + * + * Note that all parameters are ignored if the stack for package $package + * has already been instantiated + * @param string $package name of the package this error stack represents + * @param callback $msgCallback callback used for error message generation + * @param callback $contextCallback callback used for context generation, + * defaults to {@link getFileLine()} + * @param boolean $throwPEAR_Error + * @param string $stackClass class to instantiate + * @static + * @return PEAR_ErrorStack + */ + function &singleton($package, $msgCallback = false, $contextCallback = false, + $throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack') + { + if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]; + } + if (!class_exists($stackClass)) { + if (function_exists('debug_backtrace')) { + $trace = debug_backtrace(); + } + PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS, + 'exception', array('stackclass' => $stackClass), + 'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)', + false, $trace); + } + $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] = + new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error); + + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]; + } + + /** + * Internal error handler for PEAR_ErrorStack class + * + * Dies if the error is an exception (and would have died anyway) + * @access private + */ + function _handleError($err) + { + if ($err['level'] == 'exception') { + $message = $err['message']; + if (isset($_SERVER['REQUEST_URI'])) { + echo '
    '; + } else { + echo "\n"; + } + var_dump($err['context']); + die($message); + } + } + + /** + * Set up a PEAR::Log object for all error stacks that don't have one + * @param Log $log + * @static + */ + function setDefaultLogger(&$log) + { + if (is_object($log) && method_exists($log, 'log') ) { + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log; + } elseif (is_callable($log)) { + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log; + } + } + + /** + * Set up a PEAR::Log object for this error stack + * @param Log $log + */ + function setLogger(&$log) + { + if (is_object($log) && method_exists($log, 'log') ) { + $this->_logger = &$log; + } elseif (is_callable($log)) { + $this->_logger = &$log; + } + } + + /** + * Set an error code => error message mapping callback + * + * This method sets the callback that can be used to generate error + * messages for any instance + * @param array|string Callback function/method + */ + function setMessageCallback($msgCallback) + { + if (!$msgCallback) { + $this->_msgCallback = array(&$this, 'getErrorMessage'); + } else { + if (is_callable($msgCallback)) { + $this->_msgCallback = $msgCallback; + } + } + } + + /** + * Get an error code => error message mapping callback + * + * This method returns the current callback that can be used to generate error + * messages + * @return array|string|false Callback function/method or false if none + */ + function getMessageCallback() + { + return $this->_msgCallback; + } + + /** + * Sets a default callback to be used by all error stacks + * + * This method sets the callback that can be used to generate error + * messages for a singleton + * @param array|string Callback function/method + * @param string Package name, or false for all packages + * @static + */ + function setDefaultCallback($callback = false, $package = false) + { + if (!is_callable($callback)) { + $callback = false; + } + $package = $package ? $package : '*'; + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback; + } + + /** + * Set a callback that generates context information (location of error) for an error stack + * + * This method sets the callback that can be used to generate context + * information for an error. Passing in NULL will disable context generation + * and remove the expensive call to debug_backtrace() + * @param array|string|null Callback function/method + */ + function setContextCallback($contextCallback) + { + if ($contextCallback === null) { + return $this->_contextCallback = false; + } + if (!$contextCallback) { + $this->_contextCallback = array(&$this, 'getFileLine'); + } else { + if (is_callable($contextCallback)) { + $this->_contextCallback = $contextCallback; + } + } + } + + /** + * Set an error Callback + * If set to a valid callback, this will be called every time an error + * is pushed onto the stack. The return value will be used to determine + * whether to allow an error to be pushed or logged. + * + * The return value must be one of the ERRORSTACK_* constants. + * + * This functionality can be used to emulate PEAR's pushErrorHandling, and + * the PEAR_ERROR_CALLBACK mode, without affecting the integrity of + * the error stack or logging + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @see popCallback() + * @param string|array $cb + */ + function pushCallback($cb) + { + array_push($this->_errorCallback, $cb); + } + + /** + * Remove a callback from the error callback stack + * @see pushCallback() + * @return array|string|false + */ + function popCallback() + { + if (!count($this->_errorCallback)) { + return false; + } + return array_pop($this->_errorCallback); + } + + /** + * Set a temporary overriding error callback for every package error stack + * + * Use this to temporarily disable all existing callbacks (can be used + * to emulate the @ operator, for instance) + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @see staticPopCallback(), pushCallback() + * @param string|array $cb + * @static + */ + function staticPushCallback($cb) + { + array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb); + } + + /** + * Remove a temporary overriding error callback + * @see staticPushCallback() + * @return array|string|false + * @static + */ + function staticPopCallback() + { + $ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']); + if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) { + $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array(); + } + return $ret; + } + + /** + * Add an error to the stack + * + * If the message generator exists, it is called with 2 parameters. + * - the current Error Stack object + * - an array that is in the same format as an error. Available indices + * are 'code', 'package', 'time', 'params', 'level', and 'context' + * + * Next, if the error should contain context information, this is + * handled by the context grabbing method. + * Finally, the error is pushed onto the proper error stack + * @param int $code Package-specific error code + * @param string $level Error level. This is NOT spell-checked + * @param array $params associative array of error parameters + * @param string $msg Error message, or a portion of it if the message + * is to be generated + * @param array $repackage If this error re-packages an error pushed by + * another package, place the array returned from + * {@link pop()} in this parameter + * @param array $backtrace Protected parameter: use this to pass in the + * {@link debug_backtrace()} that should be used + * to find error context + * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also + * thrown. If a PEAR_Error is returned, the userinfo + * property is set to the following array: + * + * + * array( + * 'code' => $code, + * 'params' => $params, + * 'package' => $this->_package, + * 'level' => $level, + * 'time' => time(), + * 'context' => $context, + * 'message' => $msg, + * //['repackage' => $err] repackaged error array/Exception class + * ); + * + * + * Normally, the previous array is returned. + */ + function push($code, $level = 'error', $params = array(), $msg = false, + $repackage = false, $backtrace = false) + { + $context = false; + // grab error context + if ($this->_contextCallback) { + if (!$backtrace) { + $backtrace = debug_backtrace(); + } + $context = call_user_func($this->_contextCallback, $code, $params, $backtrace); + } + + // save error + $time = explode(' ', microtime()); + $time = $time[1] + $time[0]; + $err = array( + 'code' => $code, + 'params' => $params, + 'package' => $this->_package, + 'level' => $level, + 'time' => $time, + 'context' => $context, + 'message' => $msg, + ); + + if ($repackage) { + $err['repackage'] = $repackage; + } + + // set up the error message, if necessary + if ($this->_msgCallback) { + $msg = call_user_func_array($this->_msgCallback, + array(&$this, $err)); + $err['message'] = $msg; + } + $push = $log = true; + $die = false; + // try the overriding callback first + $callback = $this->staticPopCallback(); + if ($callback) { + $this->staticPushCallback($callback); + } + if (!is_callable($callback)) { + // try the local callback next + $callback = $this->popCallback(); + if (is_callable($callback)) { + $this->pushCallback($callback); + } else { + // try the default callback + $callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ? + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] : + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*']; + } + } + if (is_callable($callback)) { + switch(call_user_func($callback, $err)){ + case PEAR_ERRORSTACK_IGNORE: + return $err; + break; + case PEAR_ERRORSTACK_PUSH: + $log = false; + break; + case PEAR_ERRORSTACK_LOG: + $push = false; + break; + case PEAR_ERRORSTACK_DIE: + $die = true; + break; + // anything else returned has the same effect as pushandlog + } + } + if ($push) { + array_unshift($this->_errors, $err); + if (!isset($this->_errorsByLevel[$err['level']])) { + $this->_errorsByLevel[$err['level']] = array(); + } + $this->_errorsByLevel[$err['level']][] = &$this->_errors[0]; + } + if ($log) { + if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) { + $this->_log($err); + } + } + if ($die) { + die(); + } + if ($this->_compat && $push) { + return $this->raiseError($msg, $code, null, null, $err); + } + return $err; + } + + /** + * Static version of {@link push()} + * + * @param string $package Package name this error belongs to + * @param int $code Package-specific error code + * @param string $level Error level. This is NOT spell-checked + * @param array $params associative array of error parameters + * @param string $msg Error message, or a portion of it if the message + * is to be generated + * @param array $repackage If this error re-packages an error pushed by + * another package, place the array returned from + * {@link pop()} in this parameter + * @param array $backtrace Protected parameter: use this to pass in the + * {@link debug_backtrace()} that should be used + * to find error context + * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also + * thrown. see docs for {@link push()} + * @static + */ + function staticPush($package, $code, $level = 'error', $params = array(), + $msg = false, $repackage = false, $backtrace = false) + { + $s = &PEAR_ErrorStack::singleton($package); + if ($s->_contextCallback) { + if (!$backtrace) { + if (function_exists('debug_backtrace')) { + $backtrace = debug_backtrace(); + } + } + } + return $s->push($code, $level, $params, $msg, $repackage, $backtrace); + } + + /** + * Log an error using PEAR::Log + * @param array $err Error array + * @param array $levels Error level => Log constant map + * @access protected + */ + function _log($err) + { + if ($this->_logger) { + $logger = &$this->_logger; + } else { + $logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']; + } + if (is_a($logger, 'Log')) { + $levels = array( + 'exception' => PEAR_LOG_CRIT, + 'alert' => PEAR_LOG_ALERT, + 'critical' => PEAR_LOG_CRIT, + 'error' => PEAR_LOG_ERR, + 'warning' => PEAR_LOG_WARNING, + 'notice' => PEAR_LOG_NOTICE, + 'info' => PEAR_LOG_INFO, + 'debug' => PEAR_LOG_DEBUG); + if (isset($levels[$err['level']])) { + $level = $levels[$err['level']]; + } else { + $level = PEAR_LOG_INFO; + } + $logger->log($err['message'], $level, $err); + } else { // support non-standard logs + call_user_func($logger, $err); + } + } + + + /** + * Pop an error off of the error stack + * + * @return false|array + * @since 0.4alpha it is no longer possible to specify a specific error + * level to return - the last error pushed will be returned, instead + */ + function pop() + { + $err = @array_shift($this->_errors); + if (!is_null($err)) { + @array_pop($this->_errorsByLevel[$err['level']]); + if (!count($this->_errorsByLevel[$err['level']])) { + unset($this->_errorsByLevel[$err['level']]); + } + } + return $err; + } + + /** + * Pop an error off of the error stack, static method + * + * @param string package name + * @return boolean + * @since PEAR1.5.0a1 + */ + function staticPop($package) + { + if ($package) { + if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { + return false; + } + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop(); + } + } + + /** + * Determine whether there are any errors on the stack + * @param string|array Level name. Use to determine if any errors + * of level (string), or levels (array) have been pushed + * @return boolean + */ + function hasErrors($level = false) + { + if ($level) { + return isset($this->_errorsByLevel[$level]); + } + return count($this->_errors); + } + + /** + * Retrieve all errors since last purge + * + * @param boolean set in order to empty the error stack + * @param string level name, to return only errors of a particular severity + * @return array + */ + function getErrors($purge = false, $level = false) + { + if (!$purge) { + if ($level) { + if (!isset($this->_errorsByLevel[$level])) { + return array(); + } else { + return $this->_errorsByLevel[$level]; + } + } else { + return $this->_errors; + } + } + if ($level) { + $ret = $this->_errorsByLevel[$level]; + foreach ($this->_errorsByLevel[$level] as $i => $unused) { + // entries are references to the $_errors array + $this->_errorsByLevel[$level][$i] = false; + } + // array_filter removes all entries === false + $this->_errors = array_filter($this->_errors); + unset($this->_errorsByLevel[$level]); + return $ret; + } + $ret = $this->_errors; + $this->_errors = array(); + $this->_errorsByLevel = array(); + return $ret; + } + + /** + * Determine whether there are any errors on a single error stack, or on any error stack + * + * The optional parameter can be used to test the existence of any errors without the need of + * singleton instantiation + * @param string|false Package name to check for errors + * @param string Level name to check for a particular severity + * @return boolean + * @static + */ + function staticHasErrors($package = false, $level = false) + { + if ($package) { + if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { + return false; + } + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level); + } + foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) { + if ($obj->hasErrors($level)) { + return true; + } + } + return false; + } + + /** + * Get a list of all errors since last purge, organized by package + * @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be + * @param boolean $purge Set to purge the error stack of existing errors + * @param string $level Set to a level name in order to retrieve only errors of a particular level + * @param boolean $merge Set to return a flat array, not organized by package + * @param array $sortfunc Function used to sort a merged array - default + * sorts by time, and should be good for most cases + * @static + * @return array + */ + function staticGetErrors($purge = false, $level = false, $merge = false, + $sortfunc = array('PEAR_ErrorStack', '_sortErrors')) + { + $ret = array(); + if (!is_callable($sortfunc)) { + $sortfunc = array('PEAR_ErrorStack', '_sortErrors'); + } + foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) { + $test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level); + if ($test) { + if ($merge) { + $ret = array_merge($ret, $test); + } else { + $ret[$package] = $test; + } + } + } + if ($merge) { + usort($ret, $sortfunc); + } + return $ret; + } + + /** + * Error sorting function, sorts by time + * @access private + */ + function _sortErrors($a, $b) + { + if ($a['time'] == $b['time']) { + return 0; + } + if ($a['time'] < $b['time']) { + return 1; + } + return -1; + } + + /** + * Standard file/line number/function/class context callback + * + * This function uses a backtrace generated from {@link debug_backtrace()} + * and so will not work at all in PHP < 4.3.0. The frame should + * reference the frame that contains the source of the error. + * @return array|false either array('file' => file, 'line' => line, + * 'function' => function name, 'class' => class name) or + * if this doesn't work, then false + * @param unused + * @param integer backtrace frame. + * @param array Results of debug_backtrace() + * @static + */ + function getFileLine($code, $params, $backtrace = null) + { + if ($backtrace === null) { + return false; + } + $frame = 0; + $functionframe = 1; + if (!isset($backtrace[1])) { + $functionframe = 0; + } else { + while (isset($backtrace[$functionframe]['function']) && + $backtrace[$functionframe]['function'] == 'eval' && + isset($backtrace[$functionframe + 1])) { + $functionframe++; + } + } + if (isset($backtrace[$frame])) { + if (!isset($backtrace[$frame]['file'])) { + $frame++; + } + $funcbacktrace = $backtrace[$functionframe]; + $filebacktrace = $backtrace[$frame]; + $ret = array('file' => $filebacktrace['file'], + 'line' => $filebacktrace['line']); + // rearrange for eval'd code or create function errors + if (strpos($filebacktrace['file'], '(') && + preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'], + $matches)) { + $ret['file'] = $matches[1]; + $ret['line'] = $matches[2] + 0; + } + if (isset($funcbacktrace['function']) && isset($backtrace[1])) { + if ($funcbacktrace['function'] != 'eval') { + if ($funcbacktrace['function'] == '__lambda_func') { + $ret['function'] = 'create_function() code'; + } else { + $ret['function'] = $funcbacktrace['function']; + } + } + } + if (isset($funcbacktrace['class']) && isset($backtrace[1])) { + $ret['class'] = $funcbacktrace['class']; + } + return $ret; + } + return false; + } + + /** + * Standard error message generation callback + * + * This method may also be called by a custom error message generator + * to fill in template values from the params array, simply + * set the third parameter to the error message template string to use + * + * The special variable %__msg% is reserved: use it only to specify + * where a message passed in by the user should be placed in the template, + * like so: + * + * Error message: %msg% - internal error + * + * If the message passed like so: + * + * + * $stack->push(ERROR_CODE, 'error', array(), 'server error 500'); + * + * + * The returned error message will be "Error message: server error 500 - + * internal error" + * @param PEAR_ErrorStack + * @param array + * @param string|false Pre-generated error message template + * @static + * @return string + */ + function getErrorMessage(&$stack, $err, $template = false) + { + if ($template) { + $mainmsg = $template; + } else { + $mainmsg = $stack->getErrorMessageTemplate($err['code']); + } + $mainmsg = str_replace('%__msg%', $err['message'], $mainmsg); + if (is_array($err['params']) && count($err['params'])) { + foreach ($err['params'] as $name => $val) { + if (is_array($val)) { + // @ is needed in case $val is a multi-dimensional array + $val = @implode(', ', $val); + } + if (is_object($val)) { + if (method_exists($val, '__toString')) { + $val = $val->__toString(); + } else { + PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING, + 'warning', array('obj' => get_class($val)), + 'object %obj% passed into getErrorMessage, but has no __toString() method'); + $val = 'Object'; + } + } + $mainmsg = str_replace('%' . $name . '%', $val, $mainmsg); + } + } + return $mainmsg; + } + + /** + * Standard Error Message Template generator from code + * @return string + */ + function getErrorMessageTemplate($code) + { + if (!isset($this->_errorMsgs[$code])) { + return '%__msg%'; + } + return $this->_errorMsgs[$code]; + } + + /** + * Set the Error Message Template array + * + * The array format must be: + *
    +     * array(error code => 'message template',...)
    +     * 
    + * + * Error message parameters passed into {@link push()} will be used as input + * for the error message. If the template is 'message %foo% was %bar%', and the + * parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will + * be 'message one was six' + * @return string + */ + function setErrorMessageTemplate($template) + { + $this->_errorMsgs = $template; + } + + + /** + * emulate PEAR::raiseError() + * + * @return PEAR_Error + */ + function raiseError() + { + require_once 'phar://go-pear.phar/' . 'PEAR.php'; + $args = func_get_args(); + return call_user_func_array(array('PEAR', 'raiseError'), $args); + } +} +$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack'); +$stack->pushCallback(array('PEAR_ErrorStack', '_handleError')); +?> + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Frontend.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Include error handling + */ +//require_once 'PEAR.php'; + +/** + * Which user interface class is being used. + * @var string class name + */ +$GLOBALS['_PEAR_FRONTEND_CLASS'] = 'PEAR_Frontend_CLI'; + +/** + * Instance of $_PEAR_Command_uiclass. + * @var object + */ +$GLOBALS['_PEAR_FRONTEND_SINGLETON'] = null; + +/** + * Singleton-based frontend for PEAR user input/output + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Frontend extends PEAR +{ + /** + * Retrieve the frontend object + * @return PEAR_Frontend_CLI|PEAR_Frontend_Web|PEAR_Frontend_Gtk + * @static + */ + function &singleton($type = null) + { + if ($type === null) { + if (!isset($GLOBALS['_PEAR_FRONTEND_SINGLETON'])) { + $a = false; + return $a; + } + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + $a = PEAR_Frontend::setFrontendClass($type); + return $a; + } + + /** + * Set the frontend class that will be used by calls to {@link singleton()} + * + * Frontends are expected to conform to the PEAR naming standard of + * _ => DIRECTORY_SEPARATOR (PEAR_Frontend_CLI is in PEAR/Frontend/CLI.php) + * @param string $uiclass full class name + * @return PEAR_Frontend + * @static + */ + function &setFrontendClass($uiclass) + { + if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) && + is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], $uiclass)) { + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + if (!class_exists($uiclass)) { + $file = 'phar://go-pear.phar/' . str_replace('_', '/', $uiclass) . '.php'; + if (PEAR_Frontend::isIncludeable($file)) { + include_once $file; + } + } + + if (class_exists($uiclass)) { + $obj = &new $uiclass; + // quick test to see if this class implements a few of the most + // important frontend methods + if (is_a($obj, 'PEAR_Frontend')) { + $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$obj; + $GLOBALS['_PEAR_FRONTEND_CLASS'] = $uiclass; + return $obj; + } + + $err = PEAR::raiseError("not a frontend class: $uiclass"); + return $err; + } + + $err = PEAR::raiseError("no such class: $uiclass"); + return $err; + } + + /** + * Set the frontend class that will be used by calls to {@link singleton()} + * + * Frontends are expected to be a descendant of PEAR_Frontend + * @param PEAR_Frontend + * @return PEAR_Frontend + * @static + */ + function &setFrontendObject($uiobject) + { + if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) && + is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], get_class($uiobject))) { + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + if (!is_a($uiobject, 'PEAR_Frontend')) { + $err = PEAR::raiseError('not a valid frontend class: (' . + get_class($uiobject) . ')'); + return $err; + } + + $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$uiobject; + $GLOBALS['_PEAR_FRONTEND_CLASS'] = get_class($uiobject); + return $uiobject; + } + + /** + * @param string $path relative or absolute include path + * @return boolean + * @static + */ + function isIncludeable($path) + { + if (file_exists($path) && is_readable($path)) { + return true; + } + + $fp = @fopen($path, 'r', true); + if ($fp) { + fclose($fp); + return true; + } + + return false; + } + + /** + * @param PEAR_Config + */ + function setConfig(&$config) + { + } + + /** + * This can be overridden to allow session-based temporary file management + * + * By default, all files are deleted at the end of a session. The web installer + * needs to be able to sustain a list over many sessions in order to support + * user interaction with install scripts + */ + function addTempFile($file) + { + $GLOBALS['_PEAR_Common_tempfiles'][] = $file; + } + + /** + * Log an action + * + * @param string $msg the message to log + * @param boolean $append_crlf + * @return boolean true + * @abstract + */ + function log($msg, $append_crlf = true) + { + } + + /** + * Run a post-installation script + * + * @param array $scripts array of post-install scripts + * @abstract + */ + function runPostinstallScripts(&$scripts) + { + } + + /** + * Display human-friendly output formatted depending on the + * $command parameter. + * + * This should be able to handle basic output data with no command + * @param mixed $data data structure containing the information to display + * @param string $command command from which this method was called + * @abstract + */ + function outputData($data, $command = '_default') + { + } + + /** + * Display a modal form dialog and return the given input + * + * A frontend that requires multiple requests to retrieve and process + * data must take these needs into account, and implement the request + * handling code. + * @param string $command command from which this method was called + * @param array $prompts associative array. keys are the input field names + * and values are the description + * @param array $types array of input field types (text, password, + * etc.) keys have to be the same like in $prompts + * @param array $defaults array of default values. again keys have + * to be the same like in $prompts. Do not depend + * on a default value being set. + * @return array input sent by the user + * @abstract + */ + function userDialog($command, $prompts, $types = array(), $defaults = array()) + { + } +} + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: CLI.php 278236 2009-04-04 00:09:14Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ +/** + * base class + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Frontend.php'; + +/** + * Command-line Frontend for the PEAR Installer + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Frontend_CLI extends PEAR_Frontend +{ + /** + * What type of user interface this frontend is for. + * @var string + * @access public + */ + var $type = 'CLI'; + var $lp = ''; // line prefix + + var $params = array(); + var $term = array( + 'bold' => '', + 'normal' => '', + ); + + function PEAR_Frontend_CLI() + { + parent::PEAR(); + $term = getenv('TERM'); //(cox) $_ENV is empty for me in 4.1.1 + if (function_exists('posix_isatty') && !posix_isatty(1)) { + // output is being redirected to a file or through a pipe + } elseif ($term) { + if (preg_match('/^(xterm|vt220|linux)/', $term)) { + $this->term['bold'] = sprintf("%c%c%c%c", 27, 91, 49, 109); + $this->term['normal'] = sprintf("%c%c%c", 27, 91, 109); + } elseif (preg_match('/^vt100/', $term)) { + $this->term['bold'] = sprintf("%c%c%c%c%c%c", 27, 91, 49, 109, 0, 0); + $this->term['normal'] = sprintf("%c%c%c%c%c", 27, 91, 109, 0, 0); + } + } elseif (OS_WINDOWS) { + // XXX add ANSI codes here + } + } + + /** + * @param object PEAR_Error object + */ + function displayError($e) + { + return $this->_displayLine($e->getMessage()); + } + + /** + * @param object PEAR_Error object + */ + function displayFatalError($eobj) + { + $this->displayError($eobj); + if (class_exists('PEAR_Config')) { + $config = &PEAR_Config::singleton(); + if ($config->get('verbose') > 5) { + if (function_exists('debug_print_backtrace')) { + debug_print_backtrace(); + exit(1); + } + + $raised = false; + foreach (debug_backtrace() as $i => $frame) { + if (!$raised) { + if (isset($frame['class']) + && strtolower($frame['class']) == 'pear' + && strtolower($frame['function']) == 'raiseerror' + ) { + $raised = true; + } else { + continue; + } + } + + $frame['class'] = !isset($frame['class']) ? '' : $frame['class']; + $frame['type'] = !isset($frame['type']) ? '' : $frame['type']; + $frame['function'] = !isset($frame['function']) ? '' : $frame['function']; + $frame['line'] = !isset($frame['line']) ? '' : $frame['line']; + $this->_displayLine("#$i: $frame[class]$frame[type]$frame[function] $frame[line]"); + } + } + } + + exit(1); + } + + /** + * Instruct the runInstallScript method to skip a paramgroup that matches the + * id value passed in. + * + * This method is useful for dynamically configuring which sections of a post-install script + * will be run based on the user's setup, which is very useful for making flexible + * post-install scripts without losing the cross-Frontend ability to retrieve user input + * @param string + */ + function skipParamgroup($id) + { + $this->_skipSections[$id] = true; + } + + function runPostinstallScripts(&$scripts) + { + foreach ($scripts as $i => $script) { + $this->runInstallScript($scripts[$i]->_params, $scripts[$i]->_obj); + } + } + + /** + * @param array $xml contents of postinstallscript tag + * @param object $script post-installation script + * @param string install|upgrade + */ + function runInstallScript($xml, &$script) + { + $this->_skipSections = array(); + if (!is_array($xml) || !isset($xml['paramgroup'])) { + $script->run(array(), '_default'); + return; + } + + $completedPhases = array(); + if (!isset($xml['paramgroup'][0])) { + $xml['paramgroup'] = array($xml['paramgroup']); + } + + foreach ($xml['paramgroup'] as $group) { + if (isset($this->_skipSections[$group['id']])) { + // the post-install script chose to skip this section dynamically + continue; + } + + if (isset($group['name'])) { + $paramname = explode('::', $group['name']); + if ($lastgroup['id'] != $paramname[0]) { + continue; + } + + $group['name'] = $paramname[1]; + if (!isset($answers)) { + return; + } + + if (isset($answers[$group['name']])) { + switch ($group['conditiontype']) { + case '=' : + if ($answers[$group['name']] != $group['value']) { + continue 2; + } + break; + case '!=' : + if ($answers[$group['name']] == $group['value']) { + continue 2; + } + break; + case 'preg_match' : + if (!@preg_match('/' . $group['value'] . '/', + $answers[$group['name']])) { + continue 2; + } + break; + default : + return; + } + } + } + + $lastgroup = $group; + if (isset($group['instructions'])) { + $this->_display($group['instructions']); + } + + if (!isset($group['param'][0])) { + $group['param'] = array($group['param']); + } + + if (isset($group['param'])) { + if (method_exists($script, 'postProcessPrompts')) { + $prompts = $script->postProcessPrompts($group['param'], $group['id']); + if (!is_array($prompts) || count($prompts) != count($group['param'])) { + $this->outputData('postinstall', 'Error: post-install script did not ' . + 'return proper post-processed prompts'); + $prompts = $group['param']; + } else { + foreach ($prompts as $i => $var) { + if (!is_array($var) || !isset($var['prompt']) || + !isset($var['name']) || + ($var['name'] != $group['param'][$i]['name']) || + ($var['type'] != $group['param'][$i]['type']) + ) { + $this->outputData('postinstall', 'Error: post-install script ' . + 'modified the variables or prompts, severe security risk. ' . + 'Will instead use the defaults from the package.xml'); + $prompts = $group['param']; + } + } + } + + $answers = $this->confirmDialog($prompts); + } else { + $answers = $this->confirmDialog($group['param']); + } + } + + if ((isset($answers) && $answers) || !isset($group['param'])) { + if (!isset($answers)) { + $answers = array(); + } + + array_unshift($completedPhases, $group['id']); + if (!$script->run($answers, $group['id'])) { + $script->run($completedPhases, '_undoOnError'); + return; + } + } else { + $script->run($completedPhases, '_undoOnError'); + return; + } + } + } + + /** + * Ask for user input, confirm the answers and continue until the user is satisfied + * @param array an array of arrays, format array('name' => 'paramname', 'prompt' => + * 'text to display', 'type' => 'string'[, default => 'default value']) + * @return array + */ + function confirmDialog($params) + { + $answers = $prompts = $types = array(); + foreach ($params as $param) { + $prompts[$param['name']] = $param['prompt']; + $types[$param['name']] = $param['type']; + $answers[$param['name']] = isset($param['default']) ? $param['default'] : ''; + } + + $tried = false; + do { + if ($tried) { + $i = 1; + foreach ($answers as $var => $value) { + if (!strlen($value)) { + echo $this->bold("* Enter an answer for #" . $i . ": ({$prompts[$var]})\n"); + } + $i++; + } + } + + $answers = $this->userDialog('', $prompts, $types, $answers); + $tried = true; + } while (is_array($answers) && count(array_filter($answers)) != count($prompts)); + + return $answers; + } + + function userDialog($command, $prompts, $types = array(), $defaults = array(), $screensize = 20) + { + if (!is_array($prompts)) { + return array(); + } + + $testprompts = array_keys($prompts); + $result = $defaults; + + reset($prompts); + if (count($prompts) === 1) { + foreach ($prompts as $key => $prompt) { + $type = $types[$key]; + $default = @$defaults[$key]; + print "$prompt "; + if ($default) { + print "[$default] "; + } + print ": "; + + $line = fgets(STDIN, 2048); + $result[$key] = ($default && trim($line) == '') ? $default : trim($line); + } + + return $result; + } + + $first_run = true; + while (true) { + $descLength = max(array_map('strlen', $prompts)); + $descFormat = "%-{$descLength}s"; + $last = count($prompts); + + $i = 0; + foreach ($prompts as $n => $var) { + $res = isset($result[$n]) ? $result[$n] : null; + printf("%2d. $descFormat : %s\n", ++$i, $prompts[$n], $res); + } + print "\n1-$last, 'all', 'abort', or Enter to continue: "; + + $tmp = trim(fgets(STDIN, 1024)); + if (empty($tmp)) { + break; + } + + if ($tmp == 'abort') { + return false; + } + + if (isset($testprompts[(int)$tmp - 1])) { + $var = $testprompts[(int)$tmp - 1]; + $desc = $prompts[$var]; + $current = @$result[$var]; + print "$desc [$current] : "; + $tmp = trim(fgets(STDIN, 1024)); + if ($tmp !== '') { + $result[$var] = $tmp; + } + } elseif ($tmp == 'all') { + foreach ($prompts as $var => $desc) { + $current = $result[$var]; + print "$desc [$current] : "; + $tmp = trim(fgets(STDIN, 1024)); + if (trim($tmp) !== '') { + $result[$var] = trim($tmp); + } + } + } + + $first_run = false; + } + + return $result; + } + + function userConfirm($prompt, $default = 'yes') + { + trigger_error("PEAR_Frontend_CLI::userConfirm not yet converted", E_USER_ERROR); + static $positives = array('y', 'yes', 'on', '1'); + static $negatives = array('n', 'no', 'off', '0'); + print "$this->lp$prompt [$default] : "; + $fp = fopen("php://stdin", "r"); + $line = fgets($fp, 2048); + fclose($fp); + $answer = strtolower(trim($line)); + if (empty($answer)) { + $answer = $default; + } + if (in_array($answer, $positives)) { + return true; + } + if (in_array($answer, $negatives)) { + return false; + } + if (in_array($default, $positives)) { + return true; + } + return false; + } + + function outputData($data, $command = '_default') + { + switch ($command) { + case 'channel-info': + foreach ($data as $type => $section) { + if ($type == 'main') { + $section['data'] = array_values($section['data']); + } + + $this->outputData($section); + } + break; + case 'install': + case 'upgrade': + case 'upgrade-all': + if (isset($data['release_warnings'])) { + $this->_displayLine(''); + $this->_startTable(array( + 'border' => false, + 'caption' => 'Release Warnings' + )); + $this->_tableRow(array($data['release_warnings']), null, array(1 => array('wrap' => 55))); + $this->_endTable(); + $this->_displayLine(''); + } + + $this->_displayLine($data['data']); + break; + case 'search': + $this->_startTable($data); + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55))); + } + + foreach($data['data'] as $category) { + foreach($category as $pkg) { + $this->_tableRow($pkg, null, array(1 => array('wrap' => 55))); + } + } + + $this->_endTable(); + break; + case 'list-all': + if (!isset($data['data'])) { + $this->_displayLine('No packages in channel'); + break; + } + + $this->_startTable($data); + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55))); + } + + foreach($data['data'] as $category) { + foreach($category as $pkg) { + unset($pkg[4], $pkg[5]); + $this->_tableRow($pkg, null, array(1 => array('wrap' => 55))); + } + } + + $this->_endTable(); + break; + case 'config-show': + $data['border'] = false; + $opts = array( + 0 => array('wrap' => 30), + 1 => array('wrap' => 20), + 2 => array('wrap' => 35) + ); + + $this->_startTable($data); + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], array('bold' => true), $opts); + } + + foreach ($data['data'] as $group) { + foreach ($group as $value) { + if ($value[2] == '') { + $value[2] = ""; + } + + $this->_tableRow($value, null, $opts); + } + } + + $this->_endTable(); + break; + case 'remote-info': + $d = $data; + $data = array( + 'caption' => 'Package details:', + 'border' => false, + 'data' => array( + array("Latest", $data['stable']), + array("Installed", $data['installed']), + array("Package", $data['name']), + array("License", $data['license']), + array("Category", $data['category']), + array("Summary", $data['summary']), + array("Description", $data['description']), + ), + ); + + if (isset($d['deprecated']) && $d['deprecated']) { + $conf = &PEAR_Config::singleton(); + $reg = $conf->getRegistry(); + $name = $reg->parsedPackageNameToString($d['deprecated'], true); + $data['data'][] = array('Deprecated! use', $name); + } + default: { + if (is_array($data)) { + $this->_startTable($data); + $count = count($data['data'][0]); + if ($count == 2) { + $opts = array(0 => array('wrap' => 25), + 1 => array('wrap' => 48) + ); + } elseif ($count == 3) { + $opts = array(0 => array('wrap' => 30), + 1 => array('wrap' => 20), + 2 => array('wrap' => 35) + ); + } else { + $opts = null; + } + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], + array('bold' => true), + $opts); + } + + foreach($data['data'] as $row) { + $this->_tableRow($row, null, $opts); + } + $this->_endTable(); + } else { + $this->_displayLine($data); + } + } + } + } + + function log($text, $append_crlf = true) + { + if ($append_crlf) { + return $this->_displayLine($text); + } + + return $this->_display($text); + } + + function bold($text) + { + if (empty($this->term['bold'])) { + return strtoupper($text); + } + + return $this->term['bold'] . $text . $this->term['normal']; + } + + function _displayHeading($title) + { + print $this->lp.$this->bold($title)."\n"; + print $this->lp.str_repeat("=", strlen($title))."\n"; + } + + function _startTable($params = array()) + { + $params['table_data'] = array(); + $params['widest'] = array(); // indexed by column + $params['highest'] = array(); // indexed by row + $params['ncols'] = 0; + $this->params = $params; + } + + function _tableRow($columns, $rowparams = array(), $colparams = array()) + { + $highest = 1; + for ($i = 0; $i < count($columns); $i++) { + $col = &$columns[$i]; + if (isset($colparams[$i]) && !empty($colparams[$i]['wrap'])) { + $col = wordwrap($col, $colparams[$i]['wrap']); + } + + if (strpos($col, "\n") !== false) { + $multiline = explode("\n", $col); + $w = 0; + foreach ($multiline as $n => $line) { + $len = strlen($line); + if ($len > $w) { + $w = $len; + } + } + $lines = count($multiline); + } else { + $w = strlen($col); + } + + if (isset($this->params['widest'][$i])) { + if ($w > $this->params['widest'][$i]) { + $this->params['widest'][$i] = $w; + } + } else { + $this->params['widest'][$i] = $w; + } + + $tmp = count_chars($columns[$i], 1); + // handle unix, mac and windows formats + $lines = (isset($tmp[10]) ? $tmp[10] : (isset($tmp[13]) ? $tmp[13] : 0)) + 1; + if ($lines > $highest) { + $highest = $lines; + } + } + + if (count($columns) > $this->params['ncols']) { + $this->params['ncols'] = count($columns); + } + + $new_row = array( + 'data' => $columns, + 'height' => $highest, + 'rowparams' => $rowparams, + 'colparams' => $colparams, + ); + $this->params['table_data'][] = $new_row; + } + + function _endTable() + { + extract($this->params); + if (!empty($caption)) { + $this->_displayHeading($caption); + } + + if (count($table_data) === 0) { + return; + } + + if (!isset($width)) { + $width = $widest; + } else { + for ($i = 0; $i < $ncols; $i++) { + if (!isset($width[$i])) { + $width[$i] = $widest[$i]; + } + } + } + + $border = false; + if (empty($border)) { + $cellstart = ''; + $cellend = ' '; + $rowend = ''; + $padrowend = false; + $borderline = ''; + } else { + $cellstart = '| '; + $cellend = ' '; + $rowend = '|'; + $padrowend = true; + $borderline = '+'; + foreach ($width as $w) { + $borderline .= str_repeat('-', $w + strlen($cellstart) + strlen($cellend) - 1); + $borderline .= '+'; + } + } + + if ($borderline) { + $this->_displayLine($borderline); + } + + for ($i = 0; $i < count($table_data); $i++) { + extract($table_data[$i]); + if (!is_array($rowparams)) { + $rowparams = array(); + } + + if (!is_array($colparams)) { + $colparams = array(); + } + + $rowlines = array(); + if ($height > 1) { + for ($c = 0; $c < count($data); $c++) { + $rowlines[$c] = preg_split('/(\r?\n|\r)/', $data[$c]); + if (count($rowlines[$c]) < $height) { + $rowlines[$c] = array_pad($rowlines[$c], $height, ''); + } + } + } else { + for ($c = 0; $c < count($data); $c++) { + $rowlines[$c] = array($data[$c]); + } + } + + for ($r = 0; $r < $height; $r++) { + $rowtext = ''; + for ($c = 0; $c < count($data); $c++) { + if (isset($colparams[$c])) { + $attribs = array_merge($rowparams, $colparams); + } else { + $attribs = $rowparams; + } + + $w = isset($width[$c]) ? $width[$c] : 0; + //$cell = $data[$c]; + $cell = $rowlines[$c][$r]; + $l = strlen($cell); + if ($l > $w) { + $cell = substr($cell, 0, $w); + } + + if (isset($attribs['bold'])) { + $cell = $this->bold($cell); + } + + if ($l < $w) { + // not using str_pad here because we may + // add bold escape characters to $cell + $cell .= str_repeat(' ', $w - $l); + } + + $rowtext .= $cellstart . $cell . $cellend; + } + + if (!$border) { + $rowtext = rtrim($rowtext); + } + + $rowtext .= $rowend; + $this->_displayLine($rowtext); + } + } + + if ($borderline) { + $this->_displayLine($borderline); + } + } + + function _displayLine($text) + { + print "$this->lp$text\n"; + } + + function _display($text) + { + print $text; + } +}package2.xml100664 764 764 14772 100664 6346 + + Archive_Tar + pear.php.net + Tar file management class + This class provides handling of tar files in PHP. +It supports creating, listing, extracting and adding to tar files. +Gzip support is available if PHP has the zlib extension built-in or +loaded. Bz2 compression is also supported with the bz2 extension loaded. + + Gregory Beaver + cellog + cellog@php.net + yes + + + Vincent Blavet + vblavet + vincent@phpconcept.net + no + + + Stig Bakken + ssb + stig@php.net + no + + 2009-03-27 + + + 1.3.3 + 1.3.1 + + + stable + stable + + New BSD License + +Change the license to New BSD license + + minor bugfix release + * fix Bug #9921 compression with bzip2 fails [cellog] + * fix Bug #11594 _readLongHeader leaves 0 bytes in filename [jamessas] + * fix Bug #11769 Incorrect symlink handing [fajar99] + + + + + + + + + PEAR + pear.php.net + 1.4.0 + 1.5.0RC2 + + + + + 4.3.0 + + + 1.5.4 + + + + + + + + 1.3.2 + 1.3.1 + + + stable + stable + + 2007-01-03 + PHP License + +Correct Bug #4016 +Remove duplicate remove error display with '@' +Correct Bug #3909 : Check existence of OS_WINDOWS constant +Correct Bug #5452 fix for "lone zero block" when untarring packages +Change filemode (from pear-core/Archive/Tar.php v.1.21) +Correct Bug #6486 Can not extract symlinks +Correct Bug #6933 Archive_Tar (Tar file management class) Directory traversal +Correct Bug #8114 Files added on-the-fly not storing date +Correct Bug #9352 Bug on _dirCheck function over nfs path + + + + + 1.3.1 + 1.3.1 + + + stable + stable + + 2005-03-17 + PHP License + +Correct Bug #3855 + + + + + 1.3.0 + 1.3.0 + + + stable + stable + + 2005-03-06 + PHP License + +Bugs correction (2475, 2488, 2135, 2176) + + + + + 1.2 + 1.2 + + + stable + stable + + 2004-05-08 + PHP License + +Add support for other separator than the space char and bug + correction + + + + + 1.1 + 1.1 + + + stable + stable + + 2003-05-28 + PHP License + +* Add support for BZ2 compression +* Add support for add and extract without using temporary files : methods addString() and extractInString() + + + + + 1.0 + 1.0 + + + stable + stable + + 2003-01-24 + PHP License + +Change status to stable + + + + + 0.10-b1 + 0.10-b1 + + + beta + beta + + 2003-01-08 + PHP License + +Add support for long filenames (greater than 99 characters) + + + + + 0.9 + 0.9 + + + stable + stable + + 2002-05-27 + PHP License + +Auto-detect gzip'ed files + + + + + 0.4 + 0.4 + + + stable + stable + + 2002-05-20 + PHP License + +Windows bugfix: use forward slashes inside archives + + + + + 0.2 + 0.2 + + + stable + stable + + 2002-02-18 + PHP License + +From initial commit to stable + + + + + 0.3 + 0.3 + + + stable + stable + + 2002-04-13 + PHP License + +Windows bugfix: used wrong directory separators + + + + +Archive_Tar-1.3.3/Archive/Tar.php100777 764 764 171523 100777 11646 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * @category File_Formats + * @package Archive_Tar + * @author Vincent Blavet + * @copyright 1997-2008 The Authors + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Tar.php,v 1.43 2008/10/30 17:58:42 dufuz Exp $ + * @link http://pear.php.net/package/Archive_Tar + */ + +require_once 'PEAR.php'; + + +define ('ARCHIVE_TAR_ATT_SEPARATOR', 90001); +define ('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); + +/** +* Creates a (compressed) Tar archive +* +* @author Vincent Blavet +* @version $Revision: 1.43 $ +* @license http://www.opensource.org/licenses/bsd-license.php New BSD License +* @package Archive_Tar +*/ +class Archive_Tar extends PEAR +{ + /** + * @var string Name of the Tar + */ + var $_tarname=''; + + /** + * @var boolean if true, the Tar file will be gzipped + */ + var $_compress=false; + + /** + * @var string Type of compression : 'none', 'gz' or 'bz2' + */ + var $_compress_type='none'; + + /** + * @var string Explode separator + */ + var $_separator=' '; + + /** + * @var file descriptor + */ + var $_file=0; + + /** + * @var string Local Tar name of a remote Tar (http:// or ftp://) + */ + var $_temp_tarname=''; + + // {{{ constructor + /** + * Archive_Tar Class constructor. This flavour of the constructor only + * declare a new Archive_Tar object, identifying it by the name of the + * tar file. + * If the compress argument is set the tar will be read or created as a + * gzip or bz2 compressed TAR file. + * + * @param string $p_tarname The name of the tar archive to create + * @param string $p_compress can be null, 'gz' or 'bz2'. This + * parameter indicates if gzip or bz2 compression + * is required. For compatibility reason the + * boolean value 'true' means 'gz'. + * @access public + */ + function Archive_Tar($p_tarname, $p_compress = null) + { + $this->PEAR(); + $this->_compress = false; + $this->_compress_type = 'none'; + if (($p_compress === null) || ($p_compress == '')) { + if (@file_exists($p_tarname)) { + if ($fp = @fopen($p_tarname, "rb")) { + // look for gzip magic cookie + $data = fread($fp, 2); + fclose($fp); + if ($data == "\37\213") { + $this->_compress = true; + $this->_compress_type = 'gz'; + // No sure it's enought for a magic code .... + } elseif ($data == "BZ") { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } + } + } else { + // probably a remote file or some file accessible + // through a stream interface + if (substr($p_tarname, -2) == 'gz') { + $this->_compress = true; + $this->_compress_type = 'gz'; + } elseif ((substr($p_tarname, -3) == 'bz2') || + (substr($p_tarname, -2) == 'bz')) { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } + } + } else { + if (($p_compress === true) || ($p_compress == 'gz')) { + $this->_compress = true; + $this->_compress_type = 'gz'; + } else if ($p_compress == 'bz2') { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } else { + die("Unsupported compression type '$p_compress'\n". + "Supported types are 'gz' and 'bz2'.\n"); + return false; + } + } + $this->_tarname = $p_tarname; + if ($this->_compress) { // assert zlib or bz2 extension support + if ($this->_compress_type == 'gz') + $extname = 'zlib'; + else if ($this->_compress_type == 'bz2') + $extname = 'bz2'; + + if (!extension_loaded($extname)) { + PEAR::loadExtension($extname); + } + if (!extension_loaded($extname)) { + die("The extension '$extname' couldn't be found.\n". + "Please make sure your version of PHP was built ". + "with '$extname' support.\n"); + return false; + } + } + } + // }}} + + // {{{ destructor + function _Archive_Tar() + { + $this->_close(); + // ----- Look for a local copy to delete + if ($this->_temp_tarname != '') + @unlink($this->_temp_tarname); + $this->_PEAR(); + } + // }}} + + // {{{ create() + /** + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If a file with the same name exist and is writable, it is replaced + * by the new tar. + * The method return false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * For each directory added in the archive, the files and + * sub-directories are also added. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * @return true on success, false on error. + * @see createModify() + * @access public + */ + function create($p_filelist) + { + return $this->createModify($p_filelist, '', ''); + } + // }}} + + // {{{ add() + /** + * This method add the files / directories that are listed in $p_filelist in + * the archive. If the archive does not exist it is created. + * The method return false and a PEAR error text. + * The files and directories listed are only added at the end of the archive, + * even if a file with the same name is already archived. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * @return true on success, false on error. + * @see createModify() + * @access public + */ + function add($p_filelist) + { + return $this->addModify($p_filelist, '', ''); + } + // }}} + + // {{{ extract() + function extract($p_path='') + { + return $this->extractModify($p_path, ''); + } + // }}} + + // {{{ listContent() + function listContent() + { + $v_list_detail = array(); + + if ($this->_openRead()) { + if (!$this->_extractList('', $v_list_detail, "list", '', '')) { + unset($v_list_detail); + $v_list_detail = 0; + } + $this->_close(); + } + + return $v_list_detail; + } + // }}} + + // {{{ createModify() + /** + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If the file already exists and is writable, it is replaced by the + * new tar. It is a create and not an add. If the file exists and is + * read-only or is a directory it is not replaced. The method return + * false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * See also addModify() method for file adding properties. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated by + * a single blank space. + * @param string $p_add_dir A string which contains a path to be added + * to the memorized path of each element in + * the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of each + * element in the list, when relevant. + * @return boolean true on success, false on error. + * @access public + * @see addModify() + */ + function createModify($p_filelist, $p_add_dir, $p_remove_dir='') + { + $v_result = true; + + if (!$this->_openWrite()) + return false; + + if ($p_filelist != '') { + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_cleanFile(); + $this->_error('Invalid file list'); + return false; + } + + $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir); + } + + if ($v_result) { + $this->_writeFooter(); + $this->_close(); + } else + $this->_cleanFile(); + + return $v_result; + } + // }}} + + // {{{ addModify() + /** + * This method add the files / directories listed in $p_filelist at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * If a file/dir is already in the archive it will only be added at the + * end of the archive. There is no update of the existing archived + * file/dir. However while extracting the archive, the last file will + * replace the first one. This results in a none optimization of the + * archive size. + * If a file/dir does not exist the file/dir is ignored. However an + * error text is send to PEAR error. + * If a file/dir is not readable the file/dir is ignored. However an + * error text is send to PEAR error. + * + * @param array $p_filelist An array of filenames and directory + * names, or a single string with names + * separated by a single blank space. + * @param string $p_add_dir A string which contains a path to be + * added to the memorized path of each + * element in the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of + * each element in the list, when + * relevant. + * @return true on success, false on error. + * @access public + */ + function addModify($p_filelist, $p_add_dir, $p_remove_dir='') + { + $v_result = true; + + if (!$this->_isArchive()) + $v_result = $this->createModify($p_filelist, $p_add_dir, + $p_remove_dir); + else { + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_error('Invalid file list'); + return false; + } + + $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir); + } + + return $v_result; + } + // }}} + + // {{{ addString() + /** + * This method add a single string as a file at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * + * @param string $p_filename A string which contains the full + * filename path that will be associated + * with the string. + * @param string $p_string The content of the file added in + * the archive. + * @return true on success, false on error. + * @access public + */ + function addString($p_filename, $p_string) + { + $v_result = true; + + if (!$this->_isArchive()) { + if (!$this->_openWrite()) { + return false; + } + $this->_close(); + } + + if (!$this->_openAppend()) + return false; + + // Need to check the get back to the temporary file ? .... + $v_result = $this->_addString($p_filename, $p_string); + + $this->_writeFooter(); + + $this->_close(); + + return $v_result; + } + // }}} + + // {{{ extractModify() + /** + * This method extract all the content of the archive in the directory + * indicated by $p_path. When relevant the memorized path of the + * files/dir can be modified by removing the $p_remove_path path at the + * beginning of the file/dir path. + * While extracting a file, if the directory path does not exists it is + * created. + * While extracting a file, if the file already exists it is replaced + * without looking for last modification date. + * While extracting a file, if the file already exists and is write + * protected, the extraction is aborted. + * While extracting a file, if a directory with the same name already + * exists, the extraction is aborted. + * While extracting a directory, if a file with the same name already + * exists, the extraction is aborted. + * While extracting a file/directory if the destination directory exist + * and is write protected, or does not exist but can not be created, + * the extraction is aborted. + * If after extraction an extracted file does not show the correct + * stored file size, the extraction is aborted. + * When the extraction is aborted, a PEAR error text is set and false + * is returned. However the result can be a partial extraction that may + * need to be manually cleaned. + * + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @return boolean true on success, false on error. + * @access public + * @see extractList() + */ + function extractModify($p_path, $p_remove_path) + { + $v_result = true; + $v_list_detail = array(); + + if ($v_result = $this->_openRead()) { + $v_result = $this->_extractList($p_path, $v_list_detail, + "complete", 0, $p_remove_path); + $this->_close(); + } + + return $v_result; + } + // }}} + + // {{{ extractInString() + /** + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or NULL on error. + * @param string $p_filename The path of the file to extract in a string. + * @return a string with the file content or NULL. + * @access public + */ + function extractInString($p_filename) + { + if ($this->_openRead()) { + $v_result = $this->_extractInString($p_filename); + $this->_close(); + } else { + $v_result = NULL; + } + + return $v_result; + } + // }}} + + // {{{ extractList() + /** + * This method extract from the archive only the files indicated in the + * $p_filelist. These files are extracted in the current directory or + * in the directory indicated by the optional $p_path parameter. + * If indicated the $p_remove_path can be used in the same way as it is + * used in extractModify() method. + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated + * by a single blank space. + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @return true on success, false on error. + * @access public + * @see extractModify() + */ + function extractList($p_filelist, $p_path='', $p_remove_path='') + { + $v_result = true; + $v_list_detail = array(); + + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_error('Invalid string list'); + return false; + } + + if ($v_result = $this->_openRead()) { + $v_result = $this->_extractList($p_path, $v_list_detail, "partial", + $v_list, $p_remove_path); + $this->_close(); + } + + return $v_result; + } + // }}} + + // {{{ setAttribute() + /** + * This method set specific attributes of the archive. It uses a variable + * list of parameters, in the format attribute code + attribute values : + * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); + * @param mixed $argv variable list of attributes and values + * @return true on success, false on error. + * @access public + */ + function setAttribute() + { + $v_result = true; + + // ----- Get the number of variable list of arguments + if (($v_size = func_num_args()) == 0) { + return true; + } + + // ----- Get the arguments + $v_att_list = &func_get_args(); + + // ----- Read the attributes + $i=0; + while ($i<$v_size) { + + // ----- Look for next option + switch ($v_att_list[$i]) { + // ----- Look for options that request a string value + case ARCHIVE_TAR_ATT_SEPARATOR : + // ----- Check the number of parameters + if (($i+1) >= $v_size) { + $this->_error('Invalid number of parameters for ' + .'attribute ARCHIVE_TAR_ATT_SEPARATOR'); + return false; + } + + // ----- Get the value + $this->_separator = $v_att_list[$i+1]; + $i++; + break; + + default : + $this->_error('Unknow attribute code '.$v_att_list[$i].''); + return false; + } + + // ----- Next attribute + $i++; + } + + return $v_result; + } + // }}} + + // {{{ _error() + function _error($p_message) + { + // ----- To be completed + $this->raiseError($p_message); + } + // }}} + + // {{{ _warning() + function _warning($p_message) + { + // ----- To be completed + $this->raiseError($p_message); + } + // }}} + + // {{{ _isArchive() + function _isArchive($p_filename=NULL) + { + if ($p_filename == NULL) { + $p_filename = $this->_tarname; + } + clearstatcache(); + return @is_file($p_filename) && !@is_link($p_filename); + } + // }}} + + // {{{ _openWrite() + function _openWrite() + { + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($this->_tarname, "wb9"); + else if ($this->_compress_type == 'bz2') + $this->_file = @bzopen($this->_tarname, "w"); + else if ($this->_compress_type == 'none') + $this->_file = @fopen($this->_tarname, "wb"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in write mode \'' + .$this->_tarname.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _openRead() + function _openRead() + { + if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') { + + // ----- Look if a local copy need to be done + if ($this->_temp_tarname == '') { + $this->_temp_tarname = uniqid('tar').'.tmp'; + if (!$v_file_from = @fopen($this->_tarname, 'rb')) { + $this->_error('Unable to open in read mode \'' + .$this->_tarname.'\''); + $this->_temp_tarname = ''; + return false; + } + if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { + $this->_error('Unable to open in write mode \'' + .$this->_temp_tarname.'\''); + $this->_temp_tarname = ''; + return false; + } + while ($v_data = @fread($v_file_from, 1024)) + @fwrite($v_file_to, $v_data); + @fclose($v_file_from); + @fclose($v_file_to); + } + + // ----- File to open if the local copy + $v_filename = $this->_temp_tarname; + + } else + // ----- File to open if the normal Tar file + $v_filename = $this->_tarname; + + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($v_filename, "rb"); + else if ($this->_compress_type == 'bz2') + $this->_file = @bzopen($v_filename, "r"); + else if ($this->_compress_type == 'none') + $this->_file = @fopen($v_filename, "rb"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in read mode \''.$v_filename.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _openReadWrite() + function _openReadWrite() + { + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($this->_tarname, "r+b"); + else if ($this->_compress_type == 'bz2') { + $this->_error('Unable to open bz2 in read/write mode \'' + .$this->_tarname.'\' (limitation of bz2 extension)'); + return false; + } else if ($this->_compress_type == 'none') + $this->_file = @fopen($this->_tarname, "r+b"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in read/write mode \'' + .$this->_tarname.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _close() + function _close() + { + //if (isset($this->_file)) { + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') + @gzclose($this->_file); + else if ($this->_compress_type == 'bz2') + @bzclose($this->_file); + else if ($this->_compress_type == 'none') + @fclose($this->_file); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + $this->_file = 0; + } + + // ----- Look if a local copy need to be erase + // Note that it might be interesting to keep the url for a time : ToDo + if ($this->_temp_tarname != '') { + @unlink($this->_temp_tarname); + $this->_temp_tarname = ''; + } + + return true; + } + // }}} + + // {{{ _cleanFile() + function _cleanFile() + { + $this->_close(); + + // ----- Look for a local copy + if ($this->_temp_tarname != '') { + // ----- Remove the local copy but not the remote tarname + @unlink($this->_temp_tarname); + $this->_temp_tarname = ''; + } else { + // ----- Remove the local tarname file + @unlink($this->_tarname); + } + $this->_tarname = ''; + + return true; + } + // }}} + + // {{{ _writeBlock() + function _writeBlock($p_binary_data, $p_len=null) + { + if (is_resource($this->_file)) { + if ($p_len === null) { + if ($this->_compress_type == 'gz') + @gzputs($this->_file, $p_binary_data); + else if ($this->_compress_type == 'bz2') + @bzwrite($this->_file, $p_binary_data); + else if ($this->_compress_type == 'none') + @fputs($this->_file, $p_binary_data); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'gz') + @gzputs($this->_file, $p_binary_data, $p_len); + else if ($this->_compress_type == 'bz2') + @bzwrite($this->_file, $p_binary_data, $p_len); + else if ($this->_compress_type == 'none') + @fputs($this->_file, $p_binary_data, $p_len); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + } + } + return true; + } + // }}} + + // {{{ _readBlock() + function _readBlock() + { + $v_block = null; + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') + $v_block = @gzread($this->_file, 512); + else if ($this->_compress_type == 'bz2') + $v_block = @bzread($this->_file, 512); + else if ($this->_compress_type == 'none') + $v_block = @fread($this->_file, 512); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + } + return $v_block; + } + // }}} + + // {{{ _jumpBlock() + function _jumpBlock($p_len=null) + { + if (is_resource($this->_file)) { + if ($p_len === null) + $p_len = 1; + + if ($this->_compress_type == 'gz') { + @gzseek($this->_file, gztell($this->_file)+($p_len*512)); + } + else if ($this->_compress_type == 'bz2') { + // ----- Replace missing bztell() and bzseek() + for ($i=0; $i<$p_len; $i++) + $this->_readBlock(); + } else if ($this->_compress_type == 'none') + @fseek($this->_file, ftell($this->_file)+($p_len*512)); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + } + return true; + } + // }}} + + // {{{ _writeFooter() + function _writeFooter() + { + if (is_resource($this->_file)) { + // ----- Write the last 0 filled block for end of archive + $v_binary_data = pack('a1024', ''); + $this->_writeBlock($v_binary_data); + } + return true; + } + // }}} + + // {{{ _addList() + function _addList($p_list, $p_add_dir, $p_remove_dir) + { + $v_result=true; + $v_header = array(); + + // ----- Remove potential windows directory separator + $p_add_dir = $this->_translateWinPath($p_add_dir); + $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); + + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if (sizeof($p_list) == 0) + return true; + + foreach ($p_list as $v_filename) { + if (!$v_result) { + break; + } + + // ----- Skip the current tar name + if ($v_filename == $this->_tarname) + continue; + + if ($v_filename == '') + continue; + + if (!file_exists($v_filename)) { + $this->_warning("File '$v_filename' does not exist"); + continue; + } + + // ----- Add the file or directory header + if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) + return false; + + if (@is_dir($v_filename) && !@is_link($v_filename)) { + if (!($p_hdir = opendir($v_filename))) { + $this->_warning("Directory '$v_filename' can not be read"); + continue; + } + while (false !== ($p_hitem = readdir($p_hdir))) { + if (($p_hitem != '.') && ($p_hitem != '..')) { + if ($v_filename != ".") + $p_temp_list[0] = $v_filename.'/'.$p_hitem; + else + $p_temp_list[0] = $p_hitem; + + $v_result = $this->_addList($p_temp_list, + $p_add_dir, + $p_remove_dir); + } + } + + unset($p_temp_list); + unset($p_hdir); + unset($p_hitem); + } + } + + return $v_result; + } + // }}} + + // {{{ _addFile() + function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false);; + $v_stored_filename = $p_filename; + if (strcmp($p_filename, $p_remove_dir) == 0) { + return true; + } + if ($p_remove_dir != '') { + if (substr($p_remove_dir, -1) != '/') + $p_remove_dir .= '/'; + + if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) + $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); + } + $v_stored_filename = $this->_translateWinPath($v_stored_filename); + if ($p_add_dir != '') { + if (substr($p_add_dir, -1) == '/') + $v_stored_filename = $p_add_dir.$v_stored_filename; + else + $v_stored_filename = $p_add_dir.'/'.$v_stored_filename; + } + + $v_stored_filename = $this->_pathReduction($v_stored_filename); + + if ($this->_isArchive($p_filename)) { + if (($v_file = @fopen($p_filename, "rb")) == 0) { + $this->_warning("Unable to open file '".$p_filename + ."' in binary read mode"); + return true; + } + + if (!$this->_writeHeader($p_filename, $v_stored_filename)) + return false; + + while (($v_buffer = fread($v_file, 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + fclose($v_file); + + } else { + // ----- Only header for dir + if (!$this->_writeHeader($p_filename, $v_stored_filename)) + return false; + } + + return true; + } + // }}} + + // {{{ _addString() + function _addString($p_filename, $p_string) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false);; + + if (!$this->_writeHeaderBlock($p_filename, strlen($p_string), + time(), 384, "", 0, 0)) + return false; + + $i=0; + while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') { + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + return true; + } + // }}} + + // {{{ _writeHeader() + function _writeHeader($p_filename, $p_stored_filename) + { + if ($p_stored_filename == '') + $p_stored_filename = $p_filename; + $v_reduce_filename = $this->_pathReduction($p_stored_filename); + + if (strlen($v_reduce_filename) > 99) { + if (!$this->_writeLongHeader($v_reduce_filename)) + return false; + } + + $v_info = lstat($p_filename); + $v_uid = sprintf("%6s ", DecOct($v_info[4])); + $v_gid = sprintf("%6s ", DecOct($v_info[5])); + $v_perms = sprintf("%6s ", DecOct($v_info['mode'])); + + $v_mtime = sprintf("%11s", DecOct($v_info['mode'])); + + $v_linkname = ''; + + if (@is_link($p_filename)) { + $v_typeflag = '2'; + $v_linkname = readlink($p_filename); + $v_size = sprintf("%11s ", DecOct(0)); + } elseif (@is_dir($p_filename)) { + $v_typeflag = "5"; + $v_size = sprintf("%11s ", DecOct(0)); + } else { + $v_typeflag = ''; + clearstatcache(); + $v_size = sprintf("%11s ", DecOct($v_info['size'])); + } + + $v_magic = ''; + + $v_version = ''; + + $v_uname = ''; + + $v_gname = ''; + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12A12", + $v_reduce_filename, $v_perms, $v_uid, + $v_gid, $v_size, $v_mtime); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%6s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + return true; + } + // }}} + + // {{{ _writeHeaderBlock() + function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0, + $p_type='', $p_uid=0, $p_gid=0) + { + $p_filename = $this->_pathReduction($p_filename); + + if (strlen($p_filename) > 99) { + if (!$this->_writeLongHeader($p_filename)) + return false; + } + + if ($p_type == "5") { + $v_size = sprintf("%11s ", DecOct(0)); + } else { + $v_size = sprintf("%11s ", DecOct($p_size)); + } + + $v_uid = sprintf("%6s ", DecOct($p_uid)); + $v_gid = sprintf("%6s ", DecOct($p_gid)); + $v_perms = sprintf("%6s ", DecOct($p_perms)); + + $v_mtime = sprintf("%11s", DecOct($p_mtime)); + + $v_linkname = ''; + + $v_magic = ''; + + $v_version = ''; + + $v_uname = ''; + + $v_gname = ''; + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12A12", + $p_filename, $v_perms, $v_uid, $v_gid, + $v_size, $v_mtime); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $p_type, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%6s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + return true; + } + // }}} + + // {{{ _writeLongHeader() + function _writeLongHeader($p_filename) + { + $v_size = sprintf("%11s ", DecOct(strlen($p_filename))); + + $v_typeflag = 'L'; + + $v_linkname = ''; + + $v_magic = ''; + + $v_version = ''; + + $v_uname = ''; + + $v_gname = ''; + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12A12", + '././@LongLink', 0, 0, 0, $v_size, 0); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%6s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + // ----- Write the filename as content of the block + $i=0; + while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + return true; + } + // }}} + + // {{{ _readHeader() + function _readHeader($v_binary_data, &$v_header) + { + if (strlen($v_binary_data)==0) { + $v_header['filename'] = ''; + return true; + } + + if (strlen($v_binary_data) != 512) { + $v_header['filename'] = ''; + $this->_error('Invalid block size : '.strlen($v_binary_data)); + return false; + } + + if (!is_array($v_header)) { + $v_header = array(); + } + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum+=ord(substr($v_binary_data,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156; $i<512; $i++) + $v_checksum+=ord(substr($v_binary_data,$i,1)); + + $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" + ."a8checksum/a1typeflag/a100link/a6magic/a2version/" + ."a32uname/a32gname/a8devmajor/a8devminor", + $v_binary_data); + + // ----- Extract the checksum + $v_header['checksum'] = OctDec(trim($v_data['checksum'])); + if ($v_header['checksum'] != $v_checksum) { + $v_header['filename'] = ''; + + // ----- Look for last block (empty block) + if (($v_checksum == 256) && ($v_header['checksum'] == 0)) + return true; + + $this->_error('Invalid checksum for file "'.$v_data['filename'] + .'" : '.$v_checksum.' calculated, ' + .$v_header['checksum'].' expected'); + return false; + } + + // ----- Extract the properties + $v_header['filename'] = trim($v_data['filename']); + if ($this->_maliciousFilename($v_header['filename'])) { + $this->_error('Malicious .tar detected, file "' . $v_header['filename'] . + '" will not install in desired directory tree'); + return false; + } + $v_header['mode'] = OctDec(trim($v_data['mode'])); + $v_header['uid'] = OctDec(trim($v_data['uid'])); + $v_header['gid'] = OctDec(trim($v_data['gid'])); + $v_header['size'] = OctDec(trim($v_data['size'])); + $v_header['mtime'] = OctDec(trim($v_data['mtime'])); + if (($v_header['typeflag'] = $v_data['typeflag']) == "5") { + $v_header['size'] = 0; + } + $v_header['link'] = trim($v_data['link']); + /* ----- All these fields are removed form the header because + they do not carry interesting info + $v_header[magic] = trim($v_data[magic]); + $v_header[version] = trim($v_data[version]); + $v_header[uname] = trim($v_data[uname]); + $v_header[gname] = trim($v_data[gname]); + $v_header[devmajor] = trim($v_data[devmajor]); + $v_header[devminor] = trim($v_data[devminor]); + */ + + return true; + } + // }}} + + // {{{ _maliciousFilename() + /** + * Detect and report a malicious file name + * + * @param string $file + * @return bool + * @access private + */ + function _maliciousFilename($file) + { + if (strpos($file, '/../') !== false) { + return true; + } + if (strpos($file, '../') === 0) { + return true; + } + return false; + } + // }}} + + // {{{ _readLongHeader() + function _readLongHeader(&$v_header) + { + $v_filename = ''; + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_content = $this->_readBlock(); + $v_filename .= $v_content; + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_filename .= $v_content; + } + + // ----- Read the next header + $v_binary_data = $this->_readBlock(); + + if (!$this->_readHeader($v_binary_data, $v_header)) + return false; + + $v_filename = trim($v_filename); + $v_header['filename'] = $v_filename; + if ($this->_maliciousFilename($v_filename)) { + $this->_error('Malicious .tar detected, file "' . $v_filename . + '" will not install in desired directory tree'); + return false; + } + + return true; + } + // }}} + + // {{{ _extractInString() + /** + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or NULL on error. + * @param string $p_filename The path of the file to extract in a string. + * @return a string with the file content or NULL. + * @access private + */ + function _extractInString($p_filename) + { + $v_result_str = ""; + + While (strlen($v_binary_data = $this->_readBlock()) != 0) + { + if (!$this->_readHeader($v_binary_data, $v_header)) + return NULL; + + if ($v_header['filename'] == '') + continue; + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) + return NULL; + } + + if ($v_header['filename'] == $p_filename) { + if ($v_header['typeflag'] == "5") { + $this->_error('Unable to extract in string a directory ' + .'entry {'.$v_header['filename'].'}'); + return NULL; + } else { + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_result_str .= $this->_readBlock(); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_result_str .= substr($v_content, 0, + ($v_header['size'] % 512)); + } + return $v_result_str; + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + } + + return NULL; + } + // }}} + + // {{{ _extractList() + function _extractList($p_path, &$p_list_detail, $p_mode, + $p_file_list, $p_remove_path) + { + $v_result=true; + $v_nb = 0; + $v_extract_all = true; + $v_listing = false; + + $p_path = $this->_translateWinPath($p_path, false); + if ($p_path == '' || (substr($p_path, 0, 1) != '/' + && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) { + $p_path = "./".$p_path; + } + $p_remove_path = $this->_translateWinPath($p_remove_path); + + // ----- Look for path to remove format (should end by /) + if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) + $p_remove_path .= '/'; + $p_remove_path_size = strlen($p_remove_path); + + switch ($p_mode) { + case "complete" : + $v_extract_all = TRUE; + $v_listing = FALSE; + break; + case "partial" : + $v_extract_all = FALSE; + $v_listing = FALSE; + break; + case "list" : + $v_extract_all = FALSE; + $v_listing = TRUE; + break; + default : + $this->_error('Invalid extract mode ('.$p_mode.')'); + return false; + } + + clearstatcache(); + + while (strlen($v_binary_data = $this->_readBlock()) != 0) + { + $v_extract_file = FALSE; + $v_extraction_stopped = 0; + + if (!$this->_readHeader($v_binary_data, $v_header)) + return false; + + if ($v_header['filename'] == '') { + continue; + } + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) + return false; + } + + if ((!$v_extract_all) && (is_array($p_file_list))) { + // ----- By default no unzip if the file is not found + $v_extract_file = false; + + for ($i=0; $i strlen($p_file_list[$i])) + && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) + == $p_file_list[$i])) { + $v_extract_file = TRUE; + break; + } + } + + // ----- It is a file, so compare the file names + elseif ($p_file_list[$i] == $v_header['filename']) { + $v_extract_file = TRUE; + break; + } + } + } else { + $v_extract_file = TRUE; + } + + // ----- Look if this file need to be extracted + if (($v_extract_file) && (!$v_listing)) + { + if (($p_remove_path != '') + && (substr($v_header['filename'], 0, $p_remove_path_size) + == $p_remove_path)) + $v_header['filename'] = substr($v_header['filename'], + $p_remove_path_size); + if (($p_path != './') && ($p_path != '/')) { + while (substr($p_path, -1) == '/') + $p_path = substr($p_path, 0, strlen($p_path)-1); + + if (substr($v_header['filename'], 0, 1) == '/') + $v_header['filename'] = $p_path.$v_header['filename']; + else + $v_header['filename'] = $p_path.'/'.$v_header['filename']; + } + if (file_exists($v_header['filename'])) { + if ( (@is_dir($v_header['filename'])) + && ($v_header['typeflag'] == '')) { + $this->_error('File '.$v_header['filename'] + .' already exists as a directory'); + return false; + } + if ( ($this->_isArchive($v_header['filename'])) + && ($v_header['typeflag'] == "5")) { + $this->_error('Directory '.$v_header['filename'] + .' already exists as a file'); + return false; + } + if (!is_writeable($v_header['filename'])) { + $this->_error('File '.$v_header['filename'] + .' already exists and is write protected'); + return false; + } + if (filemtime($v_header['filename']) > $v_header['mtime']) { + // To be completed : An error or silent no replace ? + } + } + + // ----- Check the directory availability and create it if necessary + elseif (($v_result + = $this->_dirCheck(($v_header['typeflag'] == "5" + ?$v_header['filename'] + :dirname($v_header['filename'])))) != 1) { + $this->_error('Unable to create path for '.$v_header['filename']); + return false; + } + + if ($v_extract_file) { + if ($v_header['typeflag'] == "5") { + if (!@file_exists($v_header['filename'])) { + if (!@mkdir($v_header['filename'], 0777)) { + $this->_error('Unable to create directory {' + .$v_header['filename'].'}'); + return false; + } + } + } elseif ($v_header['typeflag'] == "2") { + if (@file_exists($v_header['filename'])) { + @unlink($v_header['filename']); + } + if (!@symlink($v_header['link'], $v_header['filename'])) { + $this->_error('Unable to extract symbolic link {' + .$v_header['filename'].'}'); + return false; + } + } else { + if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { + $this->_error('Error while opening {'.$v_header['filename'] + .'} in write binary mode'); + return false; + } else { + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, 512); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); + } + + @fclose($v_dest_file); + + // ----- Change the file mode, mtime + @touch($v_header['filename'], $v_header['mtime']); + if ($v_header['mode'] & 0111) { + // make file executable, obey umask + $mode = fileperms($v_header['filename']) | (~umask() & 0111); + @chmod($v_header['filename'], $mode); + } + } + + // ----- Check the file size + clearstatcache(); + if (filesize($v_header['filename']) != $v_header['size']) { + $this->_error('Extracted file '.$v_header['filename'] + .' does not have the correct file size \'' + .filesize($v_header['filename']) + .'\' ('.$v_header['size'] + .' expected). Archive may be corrupted.'); + return false; + } + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + + /* TBC : Seems to be unused ... + if ($this->_compress) + $v_end_of_file = @gzeof($this->_file); + else + $v_end_of_file = @feof($this->_file); + */ + + if ($v_listing || $v_extract_file || $v_extraction_stopped) { + // ----- Log extracted files + if (($v_file_dir = dirname($v_header['filename'])) + == $v_header['filename']) + $v_file_dir = ''; + if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) + $v_file_dir = '/'; + + $p_list_detail[$v_nb++] = $v_header; + if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { + return true; + } + } + } + + return true; + } + // }}} + + // {{{ _openAppend() + function _openAppend() + { + if (filesize($this->_tarname) == 0) + return $this->_openWrite(); + + if ($this->_compress) { + $this->_close(); + + if (!@rename($this->_tarname, $this->_tarname.".tmp")) { + $this->_error('Error while renaming \''.$this->_tarname + .'\' to temporary file \''.$this->_tarname + .'.tmp\''); + return false; + } + + if ($this->_compress_type == 'gz') + $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb"); + elseif ($this->_compress_type == 'bz2') + $v_temp_tar = @bzopen($this->_tarname.".tmp", "r"); + + if ($v_temp_tar == 0) { + $this->_error('Unable to open file \''.$this->_tarname + .'.tmp\' in binary read mode'); + @rename($this->_tarname.".tmp", $this->_tarname); + return false; + } + + if (!$this->_openWrite()) { + @rename($this->_tarname.".tmp", $this->_tarname); + return false; + } + + if ($this->_compress_type == 'gz') { + while (!@gzeof($v_temp_tar)) { + $v_buffer = @gzread($v_temp_tar, 512); + if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + // do not copy end blocks, we will re-make them + // after appending + continue; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + @gzclose($v_temp_tar); + } + elseif ($this->_compress_type == 'bz2') { + while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + continue; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + @bzclose($v_temp_tar); + } + + if (!@unlink($this->_tarname.".tmp")) { + $this->_error('Error while deleting temporary file \'' + .$this->_tarname.'.tmp\''); + } + + } else { + // ----- For not compressed tar, just add files before the last + // one or two 512 bytes block + if (!$this->_openReadWrite()) + return false; + + clearstatcache(); + $v_size = filesize($this->_tarname); + + // We might have zero, one or two end blocks. + // The standard is two, but we should try to handle + // other cases. + fseek($this->_file, $v_size - 1024); + if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + fseek($this->_file, $v_size - 1024); + } + elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + fseek($this->_file, $v_size - 512); + } + } + + return true; + } + // }}} + + // {{{ _append() + function _append($p_filelist, $p_add_dir='', $p_remove_dir='') + { + if (!$this->_openAppend()) + return false; + + if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) + $this->_writeFooter(); + + $this->_close(); + + return true; + } + // }}} + + // {{{ _dirCheck() + + /** + * Check if a directory exists and create it (including parent + * dirs) if not. + * + * @param string $p_dir directory to check + * + * @return bool TRUE if the directory exists or was created + */ + function _dirCheck($p_dir) + { + clearstatcache(); + if ((@is_dir($p_dir)) || ($p_dir == '')) + return true; + + $p_parent_dir = dirname($p_dir); + + if (($p_parent_dir != $p_dir) && + ($p_parent_dir != '') && + (!$this->_dirCheck($p_parent_dir))) + return false; + + if (!@mkdir($p_dir, 0777)) { + $this->_error("Unable to create directory '$p_dir'"); + return false; + } + + return true; + } + + // }}} + + // {{{ _pathReduction() + + /** + * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar", + * rand emove double slashes. + * + * @param string $p_dir path to reduce + * + * @return string reduced path + * + * @access private + * + */ + function _pathReduction($p_dir) + { + $v_result = ''; + + // ----- Look for not empty path + if ($p_dir != '') { + // ----- Explode path by directory names + $v_list = explode('/', $p_dir); + + // ----- Study directories from last to first + for ($i=sizeof($v_list)-1; $i>=0; $i--) { + // ----- Look for current path + if ($v_list[$i] == ".") { + // ----- Ignore this directory + // Should be the first $i=0, but no check is done + } + else if ($v_list[$i] == "..") { + // ----- Ignore it and ignore the $i-1 + $i--; + } + else if ( ($v_list[$i] == '') + && ($i!=(sizeof($v_list)-1)) + && ($i!=0)) { + // ----- Ignore only the double '//' in path, + // but not the first and last / + } else { + $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/' + .$v_result:''); + } + } + } + $v_result = strtr($v_result, '\\', '/'); + return $v_result; + } + + // }}} + + // {{{ _translateWinPath() + function _translateWinPath($p_path, $p_remove_disk_letter=true) + { + if (defined('OS_WINDOWS') && OS_WINDOWS) { + // ----- Look for potential disk letter + if ( ($p_remove_disk_letter) + && (($v_position = strpos($p_path, ':')) != false)) { + $p_path = substr($p_path, $v_position+1); + } + // ----- Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) { + $p_path = strtr($p_path, '\\', '/'); + } + } + return $p_path; + } + // }}} + +} +?> +Archive_Tar-1.3.3/docs/Archive_Tar.txt100777 764 764 43673 100777 12672 Documentation for class Archive_Tar +=================================== +Last update : 2001-08-15 + + + +Overview : +---------- + + The Archive_Tar class helps in creating and managing GNU TAR format + files compressed by GNU ZIP or not. + The class offers basic functions like creating an archive, adding + files in the archive, extracting files from the archive and listing + the archive content. + It also provide advanced functions that allow the adding and + extraction of files with path manipulation. + + +Sample : +-------- + + // ----- Creating the object (uncompressed archive) + $tar_object = new Archive_Tar("tarname.tar"); + $tar_object->setErrorHandling(PEAR_ERROR_PRINT); + + // ----- Creating the archive + $v_list[0]="file.txt"; + $v_list[1]="data/"; + $v_list[2]="file.log"; + $tar_object->create($v_list); + + // ----- Adding files + $v_list[0]="dev/file.txt"; + $v_list[1]="dev/data/"; + $v_list[2]="log/file.log"; + $tar_object->add($v_list); + + // ----- Adding more files + $tar_object->add("release/newfile.log release/readme.txt"); + + // ----- Listing the content + if (($v_list = $tar_object->listContent()) != 0) + for ($i=0; $i"; + echo " .size :'".$v_list[$i][size]."'
    "; + echo " .mtime :'".$v_list[$i][mtime]."' (".date("l dS of F Y h:i:s A", $v_list[$i][mtime]).")
    "; + echo " .mode :'".$v_list[$i][mode]."'
    "; + echo " .uid :'".$v_list[$i][uid]."'
    "; + echo " .gid :'".$v_list[$i][gid]."'
    "; + echo " .typeflag :'".$v_list[$i][typeflag]."'
    "; + } + + // ----- Extracting the archive in directory "install" + $tar_object->extract("install"); + + +Public arguments : +------------------ + +None + + +Public Methods : +---------------- + +Method : Archive_Tar($p_tarname, $compress = null) +Description : + Archive_Tar Class constructor. This flavour of the constructor only + declare a new Archive_Tar object, identifying it by the name of the + tar file. + If the compress argument is set the tar will be read or created as a + gzip or bz2 compressed TAR file. +Arguments : + $p_tarname : A valid filename for the tar archive file. + $p_compress : can be null, 'gz' or 'bz2'. For + compatibility reason it can also be true. This + parameter indicates if gzip or bz2 compression + is required. +Return value : + The Archive_Tar object. +Sample : + $tar_object = new Archive_Tar("tarname.tar"); + $tar_object_compressed = new Archive_Tar("tarname.tgz", true); +How it works : + Initialize the object. + +Method : create($p_filelist) +Description : + This method creates the archive file and add the files / directories + that are listed in $p_filelist. + If the file already exists and is writable, it is replaced by the + new tar. It is a create and not an add. If the file exists and is + read-only or is a directory it is not replaced. The method return + false and a PEAR error text. + The $p_filelist parameter can be an array of string, each string + representing a filename or a directory name with their path if + needed. It can also be a single string with names separated by a + single blank. + See also createModify() method for more details. +Arguments : + $p_filelist : An array of filenames and directory names, or a single + string with names separated by a single blank space. +Return value : + true on success, false on error. +Sample 1 : + $tar_object = new Archive_Tar("tarname.tar"); + $tar_object->setErrorHandling(PEAR_ERROR_PRINT); // Optional error handling + $v_list[0]="file.txt"; + $v_list[1]="data/"; (Optional '/' at the end) + $v_list[2]="file.log"; + $tar_object->create($v_list); +Sample 2 : + $tar_object = new Archive_Tar("tarname.tar"); + $tar_object->setErrorHandling(PEAR_ERROR_PRINT); // Optional error handling + $tar_object->create("file.txt data/ file.log"); +How it works : + Just calling the createModify() method with the right parameters. + +Method : createModify($p_filelist, $p_add_dir, $p_remove_dir = "") +Description : + This method creates the archive file and add the files / directories + that are listed in $p_filelist. + If the file already exists and is writable, it is replaced by the + new tar. It is a create and not an add. If the file exists and is + read-only or is a directory it is not replaced. The method return + false and a PEAR error text. + The $p_filelist parameter can be an array of string, each string + representing a filename or a directory name with their path if + needed. It can also be a single string with names separated by a + single blank. + The path indicated in $p_remove_dir will be removed from the + memorized path of each file / directory listed when this path + exists. By default nothing is removed (empty path "") + The path indicated in $p_add_dir will be added at the beginning of + the memorized path of each file / directory listed. However it can + be set to empty "". The adding of a path is done after the removing + of path. + The path add/remove ability enables the user to prepare an archive + for extraction in a different path than the origin files are. + See also addModify() method for file adding properties. +Arguments : + $p_filelist : An array of filenames and directory names, or a single + string with names separated by a single blank space. + $p_add_dir : A string which contains a path to be added to the + memorized path of each element in the list. + $p_remove_dir : A string which contains a path to be removed from + the memorized path of each element in the list, when + relevant. +Return value : + true on success, false on error. +Sample 1 : + $tar_object = new Archive_Tar("tarname.tar"); + $tar_object->setErrorHandling(PEAR_ERROR_PRINT); // Optional error handling + $v_list[0]="file.txt"; + $v_list[1]="data/"; (Optional '/' at the end) + $v_list[2]="file.log"; + $tar_object->createModify($v_list, "install"); + // files are stored in the archive as : + // install/file.txt + // install/data + // install/data/file1.txt + // install/data/... all the files and sub-dirs of data/ + // install/file.log +Sample 2 : + $tar_object = new Archive_Tar("tarname.tar"); + $tar_object->setErrorHandling(PEAR_ERROR_PRINT); // Optional error handling + $v_list[0]="dev/file.txt"; + $v_list[1]="dev/data/"; (Optional '/' at the end) + $v_list[2]="log/file.log"; + $tar_object->createModify($v_list, "install", "dev"); + // files are stored in the archive as : + // install/file.txt + // install/data + // install/data/file1.txt + // install/data/... all the files and sub-dirs of data/ + // install/log/file.log +How it works : + Open the file in write mode (erasing the existing one if one), + call the _addList() method for adding the files in an empty archive, + add the tar footer (512 bytes block), close the tar file. + + +Method : addModify($p_filelist, $p_add_dir, $p_remove_dir="") +Description : + This method add the files / directories listed in $p_filelist at the + end of the existing archive. If the archive does not yet exists it + is created. + The $p_filelist parameter can be an array of string, each string + representing a filename or a directory name with their path if + needed. It can also be a single string with names separated by a + single blank. + The path indicated in $p_remove_dir will be removed from the + memorized path of each file / directory listed when this path + exists. By default nothing is removed (empty path "") + The path indicated in $p_add_dir will be added at the beginning of + the memorized path of each file / directory listed. However it can + be set to empty "". The adding of a path is done after the removing + of path. + The path add/remove ability enables the user to prepare an archive + for extraction in a different path than the origin files are. + If a file/dir is already in the archive it will only be added at the + end of the archive. There is no update of the existing archived + file/dir. However while extracting the archive, the last file will + replace the first one. This results in a none optimization of the + archive size. + If a file/dir does not exist the file/dir is ignored. However an + error text is send to PEAR error. + If a file/dir is not readable the file/dir is ignored. However an + error text is send to PEAR error. + If the resulting filename/dirname (after the add/remove option or + not) string is greater than 99 char, the file/dir is + ignored. However an error text is send to PEAR error. +Arguments : + $p_filelist : An array of filenames and directory names, or a single + string with names separated by a single blank space. + $p_add_dir : A string which contains a path to be added to the + memorized path of each element in the list. + $p_remove_dir : A string which contains a path to be removed from + the memorized path of each element in the list, when + relevant. +Return value : + true on success, false on error. +Sample 1 : + $tar_object = new Archive_Tar("tarname.tar"); + [...] + $v_list[0]="dev/file.txt"; + $v_list[1]="dev/data/"; (Optional '/' at the end) + $v_list[2]="log/file.log"; + $tar_object->addModify($v_list, "install"); + // files are stored in the archive as : + // install/file.txt + // install/data + // install/data/file1.txt + // install/data/... all the files and sub-dirs of data/ + // install/file.log +Sample 2 : + $tar_object = new Archive_Tar("tarname.tar"); + [...] + $v_list[0]="dev/file.txt"; + $v_list[1]="dev/data/"; (Optional '/' at the end) + $v_list[2]="log/file.log"; + $tar_object->addModify($v_list, "install", "dev"); + // files are stored in the archive as : + // install/file.txt + // install/data + // install/data/file1.txt + // install/data/... all the files and sub-dirs of data/ + // install/log/file.log +How it works : + If the archive does not exists it create it and add the files. + If the archive does exists and is not compressed, it open it, jump + before the last empty 512 bytes block (tar footer) and add the files + at this point. + If the archive does exists and is compressed, a temporary copy file + is created. This temporary file is then 'gzip' read block by block + until the last empty block. The new files are then added in the + compressed file. + The adding of files is done by going through the file/dir list, + adding files per files, in a recursive way through the + directory. Each time a path need to be added/removed it is done + before writing the file header in the archive. + +Method : add($p_filelist) +Description : + This method add the files / directories listed in $p_filelist at the + end of the existing archive. If the archive does not yet exists it + is created. + The $p_filelist parameter can be an array of string, each string + representing a filename or a directory name with their path if + needed. It can also be a single string with names separated by a + single blank. + See addModify() method for details and limitations. +Arguments : + $p_filelist : An array of filenames and directory names, or a single + string with names separated by a single blank space. +Return value : + true on success, false on error. +Sample 1 : + $tar_object = new Archive_Tar("tarname.tar"); + [...] + $v_list[0]="dev/file.txt"; + $v_list[1]="dev/data/"; (Optional '/' at the end) + $v_list[2]="log/file.log"; + $tar_object->add($v_list); +Sample 2 : + $tar_object = new Archive_Tar("tarname.tgz", true); + [...] + $v_list[0]="dev/file.txt"; + $v_list[1]="dev/data/"; (Optional '/' at the end) + $v_list[2]="log/file.log"; + $tar_object->add($v_list); +How it works : + Simply call the addModify() method with the right parameters. + +Method : addString($p_filename, $p_string) +Description : + This method add a single string as a file at the + end of the existing archive. If the archive does not yet exists it + is created. +Arguments : + $p_filename : A string which contains the full filename path + that will be associated with the string. + $p_string : The content of the file added in the archive. +Return value : + true on success, false on error. +Sample 1 : + $v_archive = & new Archive_Tar($p_filename); + $v_archive->setErrorHandling(PEAR_ERROR_PRINT); + $v_result = $v_archive->addString('data/test.txt', 'This is the text of the string'); + + +Method : extract($p_path = "") +Description : + This method extract all the content of the archive in the directory + indicated by $p_path.If $p_path is optional, if not set the archive + is extracted in the current directory. + While extracting a file, if the directory path does not exists it is + created. + See extractModify() for details and limitations. +Arguments : + $p_path : Optional path where the files/dir need to by extracted. +Return value : + true on success, false on error. +Sample : + $tar_object = new Archive_Tar("tarname.tar"); + $tar_object->extract(); +How it works : + Simply call the extractModify() method with appropriate parameters. + +Method : extractModify($p_path, $p_remove_path) +Description : + This method extract all the content of the archive in the directory + indicated by $p_path. When relevant the memorized path of the + files/dir can be modified by removing the $p_remove_path path at the + beginning of the file/dir path. + While extracting a file, if the directory path does not exists it is + created. + While extracting a file, if the file already exists it is replaced + without looking for last modification date. + While extracting a file, if the file already exists and is write + protected, the extraction is aborted. + While extracting a file, if a directory with the same name already + exists, the extraction is aborted. + While extracting a directory, if a file with the same name already + exists, the extraction is aborted. + While extracting a file/directory if the destination directory exist + and is write protected, or does not exist but can not be created, + the extraction is aborted. + If after extraction an extracted file does not show the correct + stored file size, the extraction is aborted. + When the extraction is aborted, a PEAR error text is set and false + is returned. However the result can be a partial extraction that may + need to be manually cleaned. +Arguments : + $p_path : The path of the directory where the files/dir need to by + extracted. + $p_remove_path : Part of the memorized path that can be removed if + present at the beginning of the file/dir path. +Return value : + true on success, false on error. +Sample : + // Imagine tarname.tar with files : + // dev/data/file.txt + // dev/data/log.txt + // readme.txt + $tar_object = new Archive_Tar("tarname.tar"); + $tar_object->extractModify("install", "dev"); + // Files will be extracted there : + // install/data/file.txt + // install/data/log.txt + // install/readme.txt +How it works : + Open the archive and call a more generic function that can extract + only a part of the archive or all the archive. + See extractList() method for more details. + +Method : extractInString($p_filename) +Description : + This method extract from the archive one file identified by $p_filename. + The return value is a string with the file content, or NULL on error. +Arguments : + $p_filename : The path of the file to extract in a string. +Return value : + a string with the file content or NULL. +Sample : + // Imagine tarname.tar with files : + // dev/data/file.txt + // dev/data/log.txt + // dev/readme.txt + $v_archive = & new Archive_Tar('tarname.tar'); + $v_archive->setErrorHandling(PEAR_ERROR_PRINT); + $v_string = $v_archive->extractInString('dev/readme.txt'); + echo $v_string; + +Method : listContent() +Description : + This method returns an array of arrays that describe each + file/directory present in the archive. + The array is not sorted, so it show the position of the file in the + archive. + The file informations are : + $file[filename] : Name and path of the file/dir. + $file[mode] : File permissions (result of fileperms()) + $file[uid] : user id + $file[gid] : group id + $file[size] : filesize + $file[mtime] : Last modification time (result of filemtime()) + $file[typeflag] : "" for file, "5" for directory +Arguments : +Return value : + An array of arrays or 0 on error. +Sample : + $tar_object = new Archive_Tar("tarname.tar"); + if (($v_list = $tar_object->listContent()) != 0) + for ($i=0; $i"; + echo " .size :'".$v_list[$i][size]."'
    "; + echo " .mtime :'".$v_list[$i][mtime]."' (". + date("l dS of F Y h:i:s A", $v_list[$i][mtime]).")
    "; + echo " .mode :'".$v_list[$i][mode]."'
    "; + echo " .uid :'".$v_list[$i][uid]."'
    "; + echo " .gid :'".$v_list[$i][gid]."'
    "; + echo " .typeflag :'".$v_list[$i][typeflag]."'
    "; + } +How it works : + Call the same function as an extract however with a flag to only go + through the archive without extracting the files. + +Method : extractList($p_filelist, $p_path = "", $p_remove_path = "") +Description : + This method extract from the archive only the files indicated in the + $p_filelist. These files are extracted in the current directory or + in the directory indicated by the optional $p_path parameter. + If indicated the $p_remove_path can be used in the same way as it is + used in extractModify() method. +Arguments : + $p_filelist : An array of filenames and directory names, or a single + string with names separated by a single blank space. + $p_path : The path of the directory where the files/dir need to by + extracted. + $p_remove_path : Part of the memorized path that can be removed if + present at the beginning of the file/dir path. +Return value : + true on success, false on error. +Sample : + // Imagine tarname.tar with files : + // dev/data/file.txt + // dev/data/log.txt + // readme.txt + $tar_object = new Archive_Tar("tarname.tar"); + $tar_object->extractList("dev/data/file.txt readme.txt", "install", + "dev"); + // Files will be extracted there : + // install/data/file.txt + // install/readme.txt +How it works : + Go through the archive and extract only the files present in the + list. + +package.xml100664 764 764 11522 100664 6252 + + + Archive_Tar + Tar file management class + This class provides handling of tar files in PHP. +It supports creating, listing, extracting and adding to tar files. +Gzip support is available if PHP has the zlib extension built-in or +loaded. Bz2 compression is also supported with the bz2 extension loaded. + + + + cellog + Gregory Beaver + cellog@php.net + lead + + + vblavet + Vincent Blavet + vincent@phpconcept.net + lead + + + ssb + Stig Bakken + stig@php.net + helper + + + + 1.3.3 + 2009-03-27 + New BSD License + stable + minor bugfix release +* fix Bug #9921 compression with bzip2 fails [cellog] +* fix Bug #11594 _readLongHeader leaves 0 bytes in filename [jamessas] +* fix Bug #11769 Incorrect symlink handing [fajar99] + + + + + + + + + + + + + + + + + + + + + 1.3.3 + 2007-08-25 + PHP License + stable + + + 1.3.2 + 2007-01-03 + PHP License + stable + Correct Bug #4016 +Remove duplicate remove error display with '@' +Correct Bug #3909 : Check existence of OS_WINDOWS constant +Correct Bug #5452 fix for "lone zero block" when untarring packages +Change filemode (from pear-core/Archive/Tar.php v.1.21) +Correct Bug #6486 Can not extract symlinks +Correct Bug #6933 Archive_Tar (Tar file management class) Directory traversal +Correct Bug #8114 Files added on-the-fly not storing date +Correct Bug #9352 Bug on _dirCheck function over nfs path + + + + 1.3.1 + 2005-03-17 + PHP License + stable + Correct Bug #3855 + + + + 1.3.0 + 2005-03-06 + stable + Bugs correction (2475, 2488, 2135, 2176) + + + + 1.2 + 2004-05-08 + stable + Add support for other separator than the space char and bug + correction + + + + 1.1 + 2003-05-28 + stable + * Add support for BZ2 compression +* Add support for add and extract without using temporary files : methods addString() and extractInString() + + + + 1.0 + 2003-01-24 + stable + Change status to stable + + + + 0.10-b1 + 2003-01-08 + beta + Add support for long filenames (greater than 99 characters) + + + + 0.9 + 2002-05-27 + stable + Auto-detect gzip'ed files + + + + 0.4 + 2002-05-20 + stable + Windows bugfix: use forward slashes inside archives + + + + 0.2 + 2002-02-18 + stable + From initial commit to stable + + + + 0.3 + 2002-04-13 + stable + Windows bugfix: used wrong directory separators + + + + +package.xml100644 1750 1750 10425 10633574607 6452 + + Console_Getopt + pear.php.net + Command-line option parser + This is a PHP implementation of "getopt" supporting both +short and long options. + + Andrei Zmievski + andrei + andrei@php.net + yes + + + Stig Bakken + ssb + stig@php.net + no + + + Greg Beaver + cellog + cellog@php.net + yes + + 2007-06-12 + + + 1.2.3 + 1.2.1 + + + stable + stable + + PHP License + * fix Bug #11068: No way to read plain "-" option [cardoe] + + + + + + + PEAR + pear.php.net + 1.4.0 + 1.6.0 + + + + + 4.3.0 + + + 1.4.3 + + + + + + + + 1.2.2 + 1.2.1 + + + stable + stable + + 2007-02-17 + PHP License + * fix Bug #4475: An ambiguous error occurred when specifying similar longoption name. +* fix Bug #10055: Not failing properly on short options missing required values + + + + 1.2.1 + 1.2.1 + + + stable + stable + + 2006-12-08 + PHP License + Fixed bugs #4448 (Long parameter values truncated with longoption parameter) and #7444 (Trailing spaces after php closing tag) + + + + 1.2 + 1.2 + + + stable + stable + + 2003-12-11 + PHP License + Fix to preserve BC with 1.0 and allow correct behaviour for new users + + + + 1.0 + 1.0 + + + stable + stable + + 2002-09-13 + PHP License + Stable release + + + + 0.11 + 0.11 + + + beta + beta + + 2002-05-26 + PHP License + POSIX getopt compatibility fix: treat first element of args + array as command name + + + + 0.10 + 0.10 + + + beta + beta + + 2002-05-12 + PHP License + Packaging fix + + + + 0.9 + 0.9 + + + beta + beta + + 2002-05-12 + PHP License + Initial release + + + +Console_Getopt-1.2.3/Console/Getopt.php100644 1750 1750 25332 10633574607 13300 | +// +----------------------------------------------------------------------+ +// +// $Id: Getopt.php,v 1.4 2007/06/12 14:58:56 cellog Exp $ + +require_once 'PEAR.php'; + +/** + * Command-line options parsing class. + * + * @author Andrei Zmievski + * + */ +class Console_Getopt { + /** + * Parses the command-line options. + * + * The first parameter to this function should be the list of command-line + * arguments without the leading reference to the running program. + * + * The second parameter is a string of allowed short options. Each of the + * option letters can be followed by a colon ':' to specify that the option + * requires an argument, or a double colon '::' to specify that the option + * takes an optional argument. + * + * The third argument is an optional array of allowed long options. The + * leading '--' should not be included in the option name. Options that + * require an argument should be followed by '=', and options that take an + * option argument should be followed by '=='. + * + * The return value is an array of two elements: the list of parsed + * options and the list of non-option command-line arguments. Each entry in + * the list of parsed options is a pair of elements - the first one + * specifies the option, and the second one specifies the option argument, + * if there was one. + * + * Long and short options can be mixed. + * + * Most of the semantics of this function are based on GNU getopt_long(). + * + * @param array $args an array of command-line arguments + * @param string $short_options specifies the list of allowed short options + * @param array $long_options specifies the list of allowed long options + * + * @return array two-element array containing the list of parsed options and + * the non-option arguments + * + * @access public + * + */ + function getopt2($args, $short_options, $long_options = null) + { + return Console_Getopt::doGetopt(2, $args, $short_options, $long_options); + } + + /** + * This function expects $args to start with the script name (POSIX-style). + * Preserved for backwards compatibility. + * @see getopt2() + */ + function getopt($args, $short_options, $long_options = null) + { + return Console_Getopt::doGetopt(1, $args, $short_options, $long_options); + } + + /** + * The actual implementation of the argument parsing code. + */ + function doGetopt($version, $args, $short_options, $long_options = null) + { + // in case you pass directly readPHPArgv() as the first arg + if (PEAR::isError($args)) { + return $args; + } + if (empty($args)) { + return array(array(), array()); + } + $opts = array(); + $non_opts = array(); + + settype($args, 'array'); + + if ($long_options) { + sort($long_options); + } + + /* + * Preserve backwards compatibility with callers that relied on + * erroneous POSIX fix. + */ + if ($version < 2) { + if (isset($args[0]{0}) && $args[0]{0} != '-') { + array_shift($args); + } + } + + reset($args); + while (list($i, $arg) = each($args)) { + + /* The special element '--' means explicit end of + options. Treat the rest of the arguments as non-options + and end the loop. */ + if ($arg == '--') { + $non_opts = array_merge($non_opts, array_slice($args, $i + 1)); + break; + } + + if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$long_options)) { + $non_opts = array_merge($non_opts, array_slice($args, $i)); + break; + } elseif (strlen($arg) > 1 && $arg{1} == '-') { + $error = Console_Getopt::_parseLongOption(substr($arg, 2), $long_options, $opts, $args); + if (PEAR::isError($error)) + return $error; + } elseif ($arg == '-') { + // - is stdin + $non_opts = array_merge($non_opts, array_slice($args, $i)); + break; + } else { + $error = Console_Getopt::_parseShortOption(substr($arg, 1), $short_options, $opts, $args); + if (PEAR::isError($error)) + return $error; + } + } + + return array($opts, $non_opts); + } + + /** + * @access private + * + */ + function _parseShortOption($arg, $short_options, &$opts, &$args) + { + for ($i = 0; $i < strlen($arg); $i++) { + $opt = $arg{$i}; + $opt_arg = null; + + /* Try to find the short option in the specifier string. */ + if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':') + { + return PEAR::raiseError("Console_Getopt: unrecognized option -- $opt"); + } + + if (strlen($spec) > 1 && $spec{1} == ':') { + if (strlen($spec) > 2 && $spec{2} == ':') { + if ($i + 1 < strlen($arg)) { + /* Option takes an optional argument. Use the remainder of + the arg string if there is anything left. */ + $opts[] = array($opt, substr($arg, $i + 1)); + break; + } + } else { + /* Option requires an argument. Use the remainder of the arg + string if there is anything left. */ + if ($i + 1 < strlen($arg)) { + $opts[] = array($opt, substr($arg, $i + 1)); + break; + } else if (list(, $opt_arg) = each($args)) { + /* Else use the next argument. */; + if (Console_Getopt::_isShortOpt($opt_arg) || Console_Getopt::_isLongOpt($opt_arg)) { + return PEAR::raiseError("Console_Getopt: option requires an argument -- $opt"); + } + } else { + return PEAR::raiseError("Console_Getopt: option requires an argument -- $opt"); + } + } + } + + $opts[] = array($opt, $opt_arg); + } + } + + /** + * @access private + * + */ + function _isShortOpt($arg) + { + return strlen($arg) == 2 && $arg[0] == '-' && preg_match('/[a-zA-Z]/', $arg[1]); + } + + /** + * @access private + * + */ + function _isLongOpt($arg) + { + return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' && + preg_match('/[a-zA-Z]+$/', substr($arg, 2)); + } + + /** + * @access private + * + */ + function _parseLongOption($arg, $long_options, &$opts, &$args) + { + @list($opt, $opt_arg) = explode('=', $arg, 2); + $opt_len = strlen($opt); + + for ($i = 0; $i < count($long_options); $i++) { + $long_opt = $long_options[$i]; + $opt_start = substr($long_opt, 0, $opt_len); + $long_opt_name = str_replace('=', '', $long_opt); + + /* Option doesn't match. Go on to the next one. */ + if ($long_opt_name != $opt) { + continue; + } + + $opt_rest = substr($long_opt, $opt_len); + + /* Check that the options uniquely matches one of the allowed + options. */ + if ($i + 1 < count($long_options)) { + $next_option_rest = substr($long_options[$i + 1], $opt_len); + } else { + $next_option_rest = ''; + } + if ($opt_rest != '' && $opt{0} != '=' && + $i + 1 < count($long_options) && + $opt == substr($long_options[$i+1], 0, $opt_len) && + $next_option_rest != '' && + $next_option_rest{0} != '=') { + return PEAR::raiseError("Console_Getopt: option --$opt is ambiguous"); + } + + if (substr($long_opt, -1) == '=') { + if (substr($long_opt, -2) != '==') { + /* Long option requires an argument. + Take the next argument if one wasn't specified. */; + if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) { + return PEAR::raiseError("Console_Getopt: option --$opt requires an argument"); + } + if (Console_Getopt::_isShortOpt($opt_arg) || Console_Getopt::_isLongOpt($opt_arg)) { + return PEAR::raiseError("Console_Getopt: option requires an argument --$opt"); + } + } + } else if ($opt_arg) { + return PEAR::raiseError("Console_Getopt: option --$opt doesn't allow an argument"); + } + + $opts[] = array('--' . $opt, $opt_arg); + return; + } + + return PEAR::raiseError("Console_Getopt: unrecognized option --$opt"); + } + + /** + * Safely read the $argv PHP array across different PHP configurations. + * Will take care on register_globals and register_argc_argv ini directives + * + * @access public + * @return mixed the $argv PHP array or PEAR error if not registered + */ + function readPHPArgv() + { + global $argv; + if (!is_array($argv)) { + if (!@is_array($_SERVER['argv'])) { + if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) { + return PEAR::raiseError("Console_Getopt: Could not read cmd args (register_argc_argv=Off?)"); + } + return $GLOBALS['HTTP_SERVER_VARS']['argv']; + } + return $_SERVER['argv']; + } + return $argv; + } + +} + +?> +package2.xml100664 764 764 117255 100664 6366 + + PEAR + pear.php.net + PEAR Base System + The PEAR package contains: + * the PEAR installer, for creating, distributing + and installing packages + * the PEAR_Exception PHP5 error handling mechanism + * the PEAR_ErrorStack advanced error handling mechanism + * the PEAR_Error error handling mechanism + * the OS_Guess class for retrieving info about the OS + where PHP is running on + * the System class for quick handling of common operations + with files and directories + * the PEAR base class + Features in a nutshell: + * full support for channels + * pre-download dependency validation + * new package.xml 2.0 format allows tremendous flexibility while maintaining BC + * support for optional dependency groups and limited support for sub-packaging + * robust dependency support + * full dependency validation on uninstall + * remote install for hosts with only ftp access - no more problems with + restricted host installation + * full support for mirroring + * support for bundling several packages into a single tarball + * support for static dependencies on a url-based package + * support for custom file roles and installation tasks + + Greg Beaver + cellog + cellog@php.net + no + + + Pierre-Alain Joye + pajoye + pierre@php.net + no + + + Stig Bakken + ssb + stig@php.net + no + + + Tomas V.V.Cox + cox + cox@idecnet.com + no + + + Helgi Thormar + dufuz + dufuz@php.net + yes + + + Tias Guns + tias + tias@php.net + yes + + + Tim Jackson + timj + timj@php.net + no + + + Bertrand Gugger + toggg + toggg@php.net + no + + + Martin Jansen + mj + mj@php.net + no + + 2009-09-03 + + + 1.9.0 + 1.9.0 + + + stable + stable + + New BSD License + +* Fix Bug #16547: The phar for PEAR installer uses ereg() which is deprecated [dufuz] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.4.0 + 5.0 + 5.1.0 + 5.1.1 + 5.1.2 + 5.1.3 + 5.1.4 + 5.1.5 + + + 1.4.3 + + + Archive_Tar + pear.php.net + 1.1 + 1.3.3 + 1.3.0 + + + Structures_Graph + pear.php.net + 1.0.2 + 1.0.2 + + + Console_Getopt + pear.php.net + 1.2 + 1.2.3 + + + XML_Util + pear.php.net + 1.2.0 + 1.2.1 + + + PEAR_Frontend_Web + pear.php.net + 0.4 + + + + PEAR_Frontend_Gtk + pear.php.net + 0.4.0 + 0.4.0 + + + + xml + + + pcre + + + + + PEAR_Frontend_Web + pear.php.net + 0.5.1 + + + + + PEAR_Frontend_Gtk + pear.php.net + 0.4.0 + + + + + PEAR_Frontend_Gtk2 + pear.php.net + + + + + + + windows + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.8.0alpha1 + 1.8.0 + + + alpha + stable + + 2009-03-09 + New BSD License + +* Implement Request #10373: if pref_state=stable and installed package=beta, allow up to latest beta version [dufuz] +* Implement Request #10581: login / logout should map to channel-login / channel-logout [dufuz] +* Implement Request #10825: Only display the "invalid or missing package file"-error if it makes sense [dufuz] +* Implement Request #11170: script to generate Command/[command].xml [dufuz] +* Implement Request #11176: improve channel ... has updated its protocols message [dufuz] +* Implement Request #12706: pear list -a hard to read [dufuz] +* Implement Request #11353: upgrade-all and upgrade commands to upgrade within the same stability level [dufuz] +* Implement Request #13015: Add https discovery for channel.xml [dufuz / initial patch by Martin Roos] +* Implement Request #13927: install-pear.php should have option to set www_dir [timj] +* Implement Request #14324: Make the pear install command behave similar to apt-get [dufuz] +* Implement Request #14325: make pear upgrade with no params behave like pear upgrade-all [dufuz] + - upgrade-all can be considered deprecated in favor of calling upgrade with no parameters to replicate + better what other package managers are doing. upgrade-all will still work as intended. +* Implement Request #14504: add a channel parameter support to the upgrade function [dufuz] + - Options -c ezc and --channel=ezc got added to upgrade and upgrade-all to allow for + channel specific upgrades +* Implement Request #14556: install-pear-nozlib.phar should get download_dir config and other options [cweiske] +* Implement Request #15566: Add doc.php.net as a default channel [dufuz / saltybeagle] + +* Fix PHP Bug #43857: --program-suffix not always reflected everywhere [cellog] +* Fix PHP Bug #47323: strotime warnings in make install [dufuz] + +* Fix Bug #13908: pear info command and maintainers inactive not mentioned [dufuz] +* Fix Bug #13926: install-pear.php does not set cfg_dir if -d option set with no -c option [timj] +* Fix Bug #13943: tests fail when php.exe path contains spaces [dufuz / jorrit] +* Fix Bug #13953: config-set/config-show with channel alias fail [cellog] +* Fix Bug #13958: When a phpt tests exit() or die() xdebug coverage is not generated, patch by izi (David Jean Louis) [izi / dufuz] +* Fix Bug #14041: Unpredictable unit test processing sequence [dufuz] +* Fix Bug #14140: Strict warning not suppressed in the shutdown function [dufuz] +* Fix Bug #14210: pear list -ia brings warnings [dufuz] +* Fix Bug #14274: PEAR packager mangles package.xml encoding, then complains about it [dufuz] +* Fix Bug #14287: cannot upgrade from stable to beta via -beta when config is set to stable [dufuz] +* Fix Bug #14300: Package files themselves can not be served over https [dufuz / initial patch by Martin Roos] +* Fix Bug #14437: openbasedir warning when loading config [dufuz] +* Fix Bug #14558: PackageFile.php creates tmp directory outside configured temp_dir [cweiske] +* Fix Bug #14947: downloadHttp() is missing Host part of the HTTP Request when using Proxy [ifeghali] +* Fix Bug #14977: PEAR/Frontend.php doesn't require_once PEAR.php [dufuz] +* Fix Bug #15750: Unreachable code in PEAR_Downloader [dufuz] +* Fix Bug #15979: Package files incorrectly removed when splitting a package into multiple pkgs [dufuz] +* Fix Bug #15914: pear upgrade installs different version if desired version not found [dufuz] + +NOTE! +Functions that have been deprecated for 3+ years in PEAR_Common, please take a moment +to migrate over to one of the alternatives that have ben provided: +* PEAR_Common->downloadHttp (use PEAR_Downloader->downloadHttp instead) +* PEAR_Common->infoFromTgzFile (use PEAR_PackageFile->fromTgzFile instead) +* PEAR_Common->infoFromDescriptionFile (use PEAR_PackageFile->fromPackageFile instead) +* PEAR_Common->infoFromString (use PEAR_PackageFile->fromXmlstring instead) +* PEAR_Common->infoFromArray (use PEAR_PackageFile->fromAnyFile instead) +* PEAR_Common->xmlFromInfo (use a PEAR_PackageFile_v* object's generator instead) +* PEAR_Common->validatePackageInfo (use the validation of PEAR_PackageFile objects) +* PEAR_Common->analyzeSourceCode (use a PEAR_PackageFile_v* object instead) +* PEAR_Common->detectDependencies (use PEAR_Downloader_Package->detectDependencies instead) +* PEAR_Common->buildProvidesArray (use PEAR_PackageFile_v1->_buildProvidesArray or + PEAR_PackageFile_v2_Validator->_buildProvidesArray) + +PHP 4.4 and 5.1.6 are now the minimum PHP requirements, for brave souls +pear upgrade -f PEAR will allow people with lower versions +to upgrade to this release but no guarantees will be made that it will work properly. + +Support for XML RPC channels has been dropped - The only ones that used it +(pear.php.net and pecl.php.net) have used the REST interface for years now. +SOAP support also removed as it was only proof of concept. + +Move codebase from the PHP License to New BSD 2 clause license + + + + 2009-03-27 + + 1.8.0RC1 + 1.8.0 + + + beta + stable + + New BSD License + +* Fix Bug #14331: pear cvstag only works from inside the package directory [dufuz] +* Fix Bug #16045: E_Notice: Undefined index: channel in PEAR/DependencyDB.php [dufuz] + +* Implemented Request #11230: better error message when mirror not in channel.xml file [dufuz] +* Implemented Request #13150: Add support for following HTTP 302 redirects [dufuz] + + + + 2009-04-10 + + 1.8.0 + 1.8.0 + + + stable + stable + + New BSD License + +Changes since RC1: + * Fix Bug #14792: Bad md5sum for files with replaced content [dufuz] + * Fix Bug #16057:-r is limited to 4 directories in depth [dufuz] + * Fix Bug #16077: PEAR5::getStaticProperty does not return a reference to the property [dufuz] + + Remove custom XML_Util class in favor of using upstream XML_Util package as dependency + +RC1 Release Notes: + * Fix Bug #14331: pear cvstag only works from inside the package directory [dufuz] + * Fix Bug #16045: E_Notice: Undefined index: channel in PEAR/DependencyDB.php [dufuz] + + * Implemented Request #11230: better error message when mirror not in channel.xml file [dufuz] + * Implemented Request #13150: Add support for following HTTP 302 redirects [dufuz] + +Alpha1 Release Notes: + * Implement Request #10373: if pref_state=stable and installed package=beta, allow up to latest beta version [dufuz] + * Implement Request #10581: login / logout should map to channel-login / channel-logout [dufuz] + * Implement Request #10825: Only display the "invalid or missing package file"-error if it makes sense [dufuz] + * Implement Request #11170: script to generate Command/[command].xml [dufuz] + * Implement Request #11176: improve channel ... has updated its protocols message [dufuz] + * Implement Request #12706: pear list -a hard to read [dufuz] + * Implement Request #11353: upgrade-all and upgrade commands to upgrade within the same stability level [dufuz] + * Implement Request #13015: Add https discovery for channel.xml [dufuz / initial patch by Martin Roos] + * Implement Request #13927: install-pear.php should have option to set www_dir [timj] + * Implement Request #14324: Make the pear install command behave similar to apt-get [dufuz] + * Implement Request #14325: make pear upgrade with no params behave like pear upgrade-all [dufuz] + - upgrade-all can be considered deprecated in favor of calling upgrade with no parameters to replicate + better what other package managers are doing. upgrade-all will still work as intended. + * Implement Request #14504: add a channel parameter support to the upgrade function [dufuz] + - Options -c ezc and --channel=ezc got added to upgrade and upgrade-all to allow for + channel specific upgrades + * Implement Request #14556: install-pear-nozlib.phar should get download_dir config and other options [cweiske] + * Implement Request #15566: Add doc.php.net as a default channel [dufuz / saltybeagle] + + * Fix PHP Bug #43857: --program-suffix not always reflected everywhere [cellog] + * Fix PHP Bug #47323: strotime warnings in make install [dufuz] + + * Fix Bug #13908: pear info command and maintainers inactive not mentioned [dufuz] + * Fix Bug #13926: install-pear.php does not set cfg_dir if -d option set with no -c option [timj] + * Fix Bug #13943: tests fail when php.exe path contains spaces [dufuz / jorrit] + * Fix Bug #13953: config-set/config-show with channel alias fail [cellog] + * Fix Bug #13958: When a phpt tests exit() or die() xdebug coverage is not generated, patch by izi (David Jean Louis) [izi / dufuz] + * Fix Bug #14041: Unpredictable unit test processing sequence [dufuz] + * Fix Bug #14140: Strict warning not suppressed in the shutdown function [dufuz] + * Fix Bug #14210: pear list -ia brings warnings [dufuz] + * Fix Bug #14274: PEAR packager mangles package.xml encoding, then complains about it [dufuz] + * Fix Bug #14287: cannot upgrade from stable to beta via -beta when config is set to stable [dufuz] + * Fix Bug #14300: Package files themselves can not be served over https [dufuz / initial patch by Martin Roos] + * Fix Bug #14437: openbasedir warning when loading config [dufuz] + * Fix Bug #14558: PackageFile.php creates tmp directory outside configured temp_dir [cweiske] + * Fix Bug #14947: downloadHttp() is missing Host part of the HTTP Request when using Proxy [ifeghali] + * Fix Bug #14977: PEAR/Frontend.php doesn't require_once PEAR.php [dufuz] + * Fix Bug #15750: Unreachable code in PEAR_Downloader [dufuz] + * Fix Bug #15979: Package files incorrectly removed when splitting a package into multiple pkgs [dufuz] + * Fix Bug #15914: pear upgrade installs different version if desired version not found [dufuz] + + NOTE! + Functions that have been deprecated for 3+ years in PEAR_Common, please take a moment + to migrate over to one of the alternatives that have ben provided: + * PEAR_Common->downloadHttp (use PEAR_Downloader->downloadHttp instead) + * PEAR_Common->infoFromTgzFile (use PEAR_PackageFile->fromTgzFile instead) + * PEAR_Common->infoFromDescriptionFile (use PEAR_PackageFile->fromPackageFile instead) + * PEAR_Common->infoFromString (use PEAR_PackageFile->fromXmlstring instead) + * PEAR_Common->infoFromArray (use PEAR_PackageFile->fromAnyFile instead) + * PEAR_Common->xmlFromInfo (use a PEAR_PackageFile_v* object's generator instead) + * PEAR_Common->validatePackageInfo (use the validation of PEAR_PackageFile objects) + * PEAR_Common->analyzeSourceCode (use a PEAR_PackageFile_v* object instead) + * PEAR_Common->detectDependencies (use PEAR_Downloader_Package->detectDependencies instead) + * PEAR_Common->buildProvidesArray (use PEAR_PackageFile_v1->_buildProvidesArray or + PEAR_PackageFile_v2_Validator->_buildProvidesArray) + + PHP 4.4 and 5.1.6 are now the minimum PHP requirements, for brave souls + pear upgrade -f PEAR will allow people with lower versions + to upgrade to this release but no guarantees will be made that it will work properly. + + Support for XML RPC channels has been dropped - The only ones that used it + (pear.php.net and pecl.php.net) have used the REST interface for years now. + SOAP support also removed as it was only proof of concept. + + Move codebase from the PHP License to New BSD 2 clause license + + + + 2009-04-15 + + 1.8.1 + 1.8.1 + + + stable + stable + + New BSD License + +* Fix Bug #16099 PEAR crash on PHP4 (parse error) [dufuz] + + + + 2009-08-18 + + 1.9.0RC1 + 1.9.0RC1 + + + beta + stable + + New BSD License + +* Implement Request #16213: add alias to list-channels output [dufuz] +* Implement Request #16378: pear svntag [dufuz] +* Implement Request #16386: PEAR_Config::remove() does not support specifying a channel [timj] +* Implement Request #16396: package-dependencies should allow package names [dufuz] + +* Fix Bug #11181: pear requests channel.xml from main server instead from mirror [dufuz] +* Fix Bug #14493: pear install --offline doesn't print out errors [dufuz] +* Fix Bug #11348: pear package-dependencies isn't well explained [dufuz] +* Fix Bug #16108: PEAR_PackageFile_Generator_v2 PHP4 parse error when running upgrade-all [dufuz] +* Fix Bug #16113: Installing certain packages fails due incorrect encoding handling [dufuz] +* Fix Bug #16122: PEAR RunTest failed to run as expected [dufuz] +* Fix Bug #16366: compiling 5.2.10 leads to non-functioning pear [dufuz] +* Fix Bug #16387: channel-logout does not support logging out from a non-default channel [timj] +* Fix Bug #16444: Setting preferred mirror fails [dufuz] +* Fix the shutdown functions where a index might not exist and thus raise a notice [derick] + + + + 2009-08-20 + + 1.9.0RC2 + 1.9.0RC2 + + + beta + stable + + New BSD License + +* REST 1.4 file was occasionally being included but REST 1.4 is not intended for this release cycle [dufuz] + + + + 2009-08-21 + + 1.9.0RC3 + 1.9.0RC3 + + + beta + stable + + New BSD License + +* Improved svntag support to handle packages like PEAR it self [dufuz] + + + + 2009-08-23 + + 1.9.0RC4 + 1.9.0RC4 + + + beta + stable + + New BSD License + +* Fixed a problem where the original channel could not be set as a preferred_mirror again [dufuz] +* Make sure channel aliases can't be made to start with - [dufuz] +* Output issues with pear search [dufuz] +* Fixed couple of stray notices [dufuz] + + + + 2009-09-03 + + 1.9.0 + 1.9.0 + + + stable + stable + + New BSD License + +* Fix Bug #16547: The phar for PEAR installer uses ereg() which is deprecated [dufuz] + + + + +PEAR-1.9.0/OS/Guess.php100664 764 764 24634 100664 7457 + * @author Gregory Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Guess.php 278521 2009-04-09 22:24:12Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since PEAR 0.1 + */ + +// {{{ uname examples + +// php_uname() without args returns the same as 'uname -a', or a PHP-custom +// string for Windows. +// PHP versions prior to 4.3 return the uname of the host where PHP was built, +// as of 4.3 it returns the uname of the host running the PHP code. +// +// PC RedHat Linux 7.1: +// Linux host.example.com 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown +// +// PC Debian Potato: +// Linux host 2.4.17 #2 SMP Tue Feb 12 15:10:04 CET 2002 i686 unknown +// +// PC FreeBSD 3.3: +// FreeBSD host.example.com 3.3-STABLE FreeBSD 3.3-STABLE #0: Mon Feb 21 00:42:31 CET 2000 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.3: +// FreeBSD host.example.com 4.3-RELEASE FreeBSD 4.3-RELEASE #1: Mon Jun 25 11:19:43 EDT 2001 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.5: +// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb 6 23:59:23 CET 2002 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.5 w/uname from GNU shellutils: +// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb i386 unknown +// +// HP 9000/712 HP-UX 10: +// HP-UX iq B.10.10 A 9000/712 2008429113 two-user license +// +// HP 9000/712 HP-UX 10 w/uname from GNU shellutils: +// HP-UX host B.10.10 A 9000/712 unknown +// +// IBM RS6000/550 AIX 4.3: +// AIX host 3 4 000003531C00 +// +// AIX 4.3 w/uname from GNU shellutils: +// AIX host 3 4 000003531C00 unknown +// +// SGI Onyx IRIX 6.5 w/uname from GNU shellutils: +// IRIX64 host 6.5 01091820 IP19 mips +// +// SGI Onyx IRIX 6.5: +// IRIX64 host 6.5 01091820 IP19 +// +// SparcStation 20 Solaris 8 w/uname from GNU shellutils: +// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc +// +// SparcStation 20 Solaris 8: +// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc SUNW,SPARCstation-20 +// +// Mac OS X (Darwin) +// Darwin home-eden.local 7.5.0 Darwin Kernel Version 7.5.0: Thu Aug 5 19:26:16 PDT 2004; root:xnu/xnu-517.7.21.obj~3/RELEASE_PPC Power Macintosh +// +// Mac OS X early versions +// + +// }}} + +/* TODO: + * - define endianness, to allow matchSignature("bigend") etc. + */ + +/** + * Retrieves information about the current operating system + * + * This class uses php_uname() to grok information about the current OS + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Gregory Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class OS_Guess +{ + var $sysname; + var $nodename; + var $cpu; + var $release; + var $extra; + + function OS_Guess($uname = null) + { + list($this->sysname, + $this->release, + $this->cpu, + $this->extra, + $this->nodename) = $this->parseSignature($uname); + } + + function parseSignature($uname = null) + { + static $sysmap = array( + 'HP-UX' => 'hpux', + 'IRIX64' => 'irix', + ); + static $cpumap = array( + 'i586' => 'i386', + 'i686' => 'i386', + 'ppc' => 'powerpc', + ); + if ($uname === null) { + $uname = php_uname(); + } + $parts = preg_split('/\s+/', trim($uname)); + $n = count($parts); + + $release = $machine = $cpu = ''; + $sysname = $parts[0]; + $nodename = $parts[1]; + $cpu = $parts[$n-1]; + $extra = ''; + if ($cpu == 'unknown') { + $cpu = $parts[$n - 2]; + } + + switch ($sysname) { + case 'AIX' : + $release = "$parts[3].$parts[2]"; + break; + case 'Windows' : + switch ($parts[1]) { + case '95/98': + $release = '9x'; + break; + default: + $release = $parts[1]; + break; + } + $cpu = 'i386'; + break; + case 'Linux' : + $extra = $this->_detectGlibcVersion(); + // use only the first two digits from the kernel version + $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]); + break; + case 'Mac' : + $sysname = 'darwin'; + $nodename = $parts[2]; + $release = $parts[3]; + if ($cpu == 'Macintosh') { + if ($parts[$n - 2] == 'Power') { + $cpu = 'powerpc'; + } + } + break; + case 'Darwin' : + if ($cpu == 'Macintosh') { + if ($parts[$n - 2] == 'Power') { + $cpu = 'powerpc'; + } + } + $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]); + break; + default: + $release = preg_replace('/-.*/', '', $parts[2]); + break; + } + + if (isset($sysmap[$sysname])) { + $sysname = $sysmap[$sysname]; + } else { + $sysname = strtolower($sysname); + } + if (isset($cpumap[$cpu])) { + $cpu = $cpumap[$cpu]; + } + return array($sysname, $release, $cpu, $extra, $nodename); + } + + function _detectGlibcVersion() + { + static $glibc = false; + if ($glibc !== false) { + return $glibc; // no need to run this multiple times + } + $major = $minor = 0; + include_once "System.php"; + // Use glibc's header file to + // get major and minor version number: + if (@file_exists('/usr/include/features.h') && + @is_readable('/usr/include/features.h')) { + if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) { + $features_file = fopen('/usr/include/features.h', 'rb'); + while (!feof($features_file)) { + $line = fgets($features_file, 8192); + if (!$line || (strpos($line, '#define') === false)) { + continue; + } + if (strpos($line, '__GLIBC__')) { + // major version number #define __GLIBC__ version + $line = preg_split('/\s+/', $line); + $glibc_major = trim($line[2]); + if (isset($glibc_minor)) { + break; + } + continue; + } + + if (strpos($line, '__GLIBC_MINOR__')) { + // got the minor version number + // #define __GLIBC_MINOR__ version + $line = preg_split('/\s+/', $line); + $glibc_minor = trim($line[2]); + if (isset($glibc_major)) { + break; + } + continue; + } + } + fclose($features_file); + if (!isset($glibc_major) || !isset($glibc_minor)) { + return $glibc = ''; + } + return $glibc = 'glibc' . trim($glibc_major) . "." . trim($glibc_minor) ; + } // no cpp + + $tmpfile = System::mktemp("glibctest"); + $fp = fopen($tmpfile, "w"); + fwrite($fp, "#include \n__GLIBC__ __GLIBC_MINOR__\n"); + fclose($fp); + $cpp = popen("/usr/bin/cpp $tmpfile", "r"); + while ($line = fgets($cpp, 1024)) { + if ($line{0} == '#' || trim($line) == '') { + continue; + } + + if (list($major, $minor) = explode(' ', trim($line))) { + break; + } + } + pclose($cpp); + unlink($tmpfile); + } // features.h + + if (!($major && $minor) && @is_link('/lib/libc.so.6')) { + // Let's try reading the libc.so.6 symlink + if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib/libc.so.6')), $matches)) { + list($major, $minor) = explode('.', $matches[1]); + } + } + + if (!($major && $minor)) { + return $glibc = ''; + } + + return $glibc = "glibc{$major}.{$minor}"; + } + + function getSignature() + { + if (empty($this->extra)) { + return "{$this->sysname}-{$this->release}-{$this->cpu}"; + } + return "{$this->sysname}-{$this->release}-{$this->cpu}-{$this->extra}"; + } + + function getSysname() + { + return $this->sysname; + } + + function getNodename() + { + return $this->nodename; + } + + function getCpu() + { + return $this->cpu; + } + + function getRelease() + { + return $this->release; + } + + function getExtra() + { + return $this->extra; + } + + function matchSignature($match) + { + $fragments = is_array($match) ? $match : explode('-', $match); + $n = count($fragments); + $matches = 0; + if ($n > 0) { + $matches += $this->_matchFragment($fragments[0], $this->sysname); + } + if ($n > 1) { + $matches += $this->_matchFragment($fragments[1], $this->release); + } + if ($n > 2) { + $matches += $this->_matchFragment($fragments[2], $this->cpu); + } + if ($n > 3) { + $matches += $this->_matchFragment($fragments[3], $this->extra); + } + return ($matches == $n); + } + + function _matchFragment($fragment, $value) + { + if (strcspn($fragment, '*?') < strlen($fragment)) { + $reg = '/^' . str_replace(array('*', '?', '/'), array('.*', '.', '\\/'), $fragment) . '\\z/'; + return preg_match($reg, $value); + } + return ($fragment == '*' || !strcasecmp($fragment, $value)); + } + +} +/* + * Local Variables: + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */PEAR-1.9.0/PEAR/ChannelFile/Parser.php100664 764 764 3362 100664 12156 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Parser.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base xml parser class + */ +require_once 'PEAR/XMLParser.php'; +require_once 'PEAR/ChannelFile.php'; +/** + * Parser for channel.xml + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_ChannelFile_Parser extends PEAR_XMLParser +{ + var $_config; + var $_logger; + var $_registry; + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + function parse($data, $file) + { + if (PEAR::isError($err = parent::parse($data, $file))) { + return $err; + } + + $ret = new PEAR_ChannelFile; + $ret->setConfig($this->_config); + if (isset($this->_logger)) { + $ret->setLogger($this->_logger); + } + + $ret->fromArray($this->_unserializedData); + // make sure the filelist is in the easy to read format needed + $ret->flattenFilelist(); + $ret->setPackagefile($file, $archive); + return $ret; + } +}PEAR-1.9.0/PEAR/Command/Auth.xml100664 764 764 2314 100664 11036 + + Connects and authenticates to remote server [Deprecated in favor of channel-login] + doLogin + li + + <channel name> +WARNING: This function is deprecated in favor of using channel-login + +Log in to a remote channel server. If <channel name> is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server. + + + Logs out from the remote server [Deprecated in favor of channel-logout] + doLogout + lo + + +WARNING: This function is deprecated in favor of using channel-logout + +Logs out from the remote server. This command does not actually +connect to the remote server, it only deletes the stored username and +password from your user configuration. + +PEAR-1.9.0/PEAR/Command/Auth.php100664 764 764 5136 100664 11032 + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Auth.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + * @deprecated since 1.8.0alpha1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Channels.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + * @deprecated since 1.8.0alpha1 + */ +class PEAR_Command_Auth extends PEAR_Command_Channels +{ + var $commands = array( + 'login' => array( + 'summary' => 'Connects and authenticates to remote server [Deprecated in favor of channel-login]', + 'shortcut' => 'li', + 'function' => 'doLogin', + 'options' => array(), + 'doc' => ' +WARNING: This function is deprecated in favor of using channel-login + +Log in to a remote channel server. If is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server.', + ), + 'logout' => array( + 'summary' => 'Logs out from the remote server [Deprecated in favor of channel-logout]', + 'shortcut' => 'lo', + 'function' => 'doLogout', + 'options' => array(), + 'doc' => ' +WARNING: This function is deprecated in favor of using channel-logout + +Logs out from the remote server. This command does not actually +connect to the remote server, it only deletes the stored username and +password from your user configuration.', + ) + + ); + + /** + * PEAR_Command_Auth constructor. + * + * @access public + */ + function PEAR_Command_Auth(&$ui, &$config) + { + parent::PEAR_Command_Channels($ui, $config); + } +}PEAR-1.9.0/PEAR/Command/Build.xml100664 764 764 404 100664 11152 + + Build an Extension From C Source + doBuild + b + + [package.xml] +Builds one or more extensions contained in a package. + +PEAR-1.9.0/PEAR/Command/Build.php100664 764 764 4453 100664 11171 + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Build.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for building extensions. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Build extends PEAR_Command_Common +{ + var $commands = array( + 'build' => array( + 'summary' => 'Build an Extension From C Source', + 'function' => 'doBuild', + 'shortcut' => 'b', + 'options' => array(), + 'doc' => '[package.xml] +Builds one or more extensions contained in a package.' + ), + ); + + /** + * PEAR_Command_Build constructor. + * + * @access public + */ + function PEAR_Command_Build(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function doBuild($command, $options, $params) + { + require_once 'PEAR/Builder.php'; + if (sizeof($params) < 1) { + $params[0] = 'package.xml'; + } + + $builder = &new PEAR_Builder($this->ui); + $this->debug = $this->config->get('verbose'); + $err = $builder->build($params[0], array(&$this, 'buildCallback')); + if (PEAR::isError($err)) { + return $err; + } + + return true; + } + + function buildCallback($what, $data) + { + if (($what == 'cmdoutput' && $this->debug > 1) || + ($what == 'output' && $this->debug > 0)) { + $this->ui->outputData(rtrim($data), 'build'); + } + } +}PEAR-1.9.0/PEAR/Command/Channels.xml100664 764 764 10172 100664 11711 + + List Available Channels + doList + lc + + +List all available channels for installation. + + + + Update the Channel List + doUpdateAll + uc + + +List all installed packages in all channels. + + + + Remove a Channel From the List + doDelete + cde + + <channel name> +Delete a channel from the registry. You may not +remove any channel that has installed packages. + + + + Add a Channel + doAdd + ca + + <channel.xml> +Add a private channel to the channel list. Note that all +public channels should be synced using "update-channels". +Parameter may be either a local file or remote URL to a +channel.xml. + + + + Update an Existing Channel + doUpdate + cu + + + f + will force download of new channel.xml if an existing channel name is used + + + c + will force download of new channel.xml if an existing channel name is used + CHANNEL + + + [<channel.xml>|<channel name>] +Update a channel in the channel list directly. Note that all +public channels can be synced using "update-channels". +Parameter may be a local or remote channel.xml, or the name of +an existing channel. + + + + Retrieve Information on a Channel + doInfo + ci + + <package> +List the files in an installed package. + + + + Specify an alias to a channel name + doAlias + cha + + <channel> <alias> +Specify a specific alias to use for a channel name. +The alias may not be an existing channel name or +alias. + + + + Initialize a Channel from its server + doDiscover + di + + [<channel.xml>|<channel name>] +Initialize a channel from its server and create a local channel.xml. +If <channel name> is in the format "<username>:<password>@<channel>" then +<username> and <password> will be set as the login username/password for +<channel>. Use caution when passing the username/password in this way, as +it may allow other users on your computer to briefly view your username/ +password via the system's process list. + + + + Connects and authenticates to remote channel server + doLogin + cli + + <channel name> +Log in to a remote channel server. If <channel name> is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server. + + + Logs out from the remote channel server + doLogout + clo + + <channel name> +Logs out from a remote channel server. If <channel name> is not supplied, +the default channel is used. This command does not actually connect to the +remote server, it only deletes the stored username and password from your user +configuration. + +PEAR-1.9.0/PEAR/Command/Channels.php100664 764 764 101377 100664 11730 + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Channels.php 287561 2009-08-21 22:42:58Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +define('PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS', -500); + +/** + * PEAR commands for managing channels. + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Command_Channels extends PEAR_Command_Common +{ + var $commands = array( + 'list-channels' => array( + 'summary' => 'List Available Channels', + 'function' => 'doList', + 'shortcut' => 'lc', + 'options' => array(), + 'doc' => ' +List all available channels for installation. +', + ), + 'update-channels' => array( + 'summary' => 'Update the Channel List', + 'function' => 'doUpdateAll', + 'shortcut' => 'uc', + 'options' => array(), + 'doc' => ' +List all installed packages in all channels. +' + ), + 'channel-delete' => array( + 'summary' => 'Remove a Channel From the List', + 'function' => 'doDelete', + 'shortcut' => 'cde', + 'options' => array(), + 'doc' => ' +Delete a channel from the registry. You may not +remove any channel that has installed packages. +' + ), + 'channel-add' => array( + 'summary' => 'Add a Channel', + 'function' => 'doAdd', + 'shortcut' => 'ca', + 'options' => array(), + 'doc' => ' +Add a private channel to the channel list. Note that all +public channels should be synced using "update-channels". +Parameter may be either a local file or remote URL to a +channel.xml. +' + ), + 'channel-update' => array( + 'summary' => 'Update an Existing Channel', + 'function' => 'doUpdate', + 'shortcut' => 'cu', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'will force download of new channel.xml if an existing channel name is used', + ), + 'channel' => array( + 'shortopt' => 'c', + 'arg' => 'CHANNEL', + 'doc' => 'will force download of new channel.xml if an existing channel name is used', + ), +), + 'doc' => '[|] +Update a channel in the channel list directly. Note that all +public channels can be synced using "update-channels". +Parameter may be a local or remote channel.xml, or the name of +an existing channel. +' + ), + 'channel-info' => array( + 'summary' => 'Retrieve Information on a Channel', + 'function' => 'doInfo', + 'shortcut' => 'ci', + 'options' => array(), + 'doc' => ' +List the files in an installed package. +' + ), + 'channel-alias' => array( + 'summary' => 'Specify an alias to a channel name', + 'function' => 'doAlias', + 'shortcut' => 'cha', + 'options' => array(), + 'doc' => ' +Specify a specific alias to use for a channel name. +The alias may not be an existing channel name or +alias. +' + ), + 'channel-discover' => array( + 'summary' => 'Initialize a Channel from its server', + 'function' => 'doDiscover', + 'shortcut' => 'di', + 'options' => array(), + 'doc' => '[|] +Initialize a channel from its server and create a local channel.xml. +If is in the format ":@" then + and will be set as the login username/password for +. Use caution when passing the username/password in this way, as +it may allow other users on your computer to briefly view your username/ +password via the system\'s process list. +' + ), + 'channel-login' => array( + 'summary' => 'Connects and authenticates to remote channel server', + 'shortcut' => 'cli', + 'function' => 'doLogin', + 'options' => array(), + 'doc' => ' +Log in to a remote channel server. If is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server.', + ), + 'channel-logout' => array( + 'summary' => 'Logs out from the remote channel server', + 'shortcut' => 'clo', + 'function' => 'doLogout', + 'options' => array(), + 'doc' => ' +Logs out from a remote channel server. If is not supplied, +the default channel is used. This command does not actually connect to the +remote server, it only deletes the stored username and password from your user +configuration.', + ), + ); + + /** + * PEAR_Command_Registry constructor. + * + * @access public + */ + function PEAR_Command_Channels(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function _sortChannels($a, $b) + { + return strnatcasecmp($a->getName(), $b->getName()); + } + + function doList($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $registered = $reg->getChannels(); + usort($registered, array(&$this, '_sortchannels')); + $i = $j = 0; + $data = array( + 'caption' => 'Registered Channels:', + 'border' => true, + 'headline' => array('Channel', 'Alias', 'Summary') + ); + foreach ($registered as $channel) { + $data['data'][] = array($channel->getName(), + $channel->getAlias(), + $channel->getSummary()); + } + + if (count($registered) === 0) { + $data = '(no registered channels)'; + } + $this->ui->outputData($data, $command); + return true; + } + + function doUpdateAll($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $channels = $reg->getChannels(); + + $success = true; + foreach ($channels as $channel) { + if ($channel->getName() != '__uri') { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->doUpdate('channel-update', + $options, + array($channel->getName())); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + $success = false; + } else { + $success &= $err; + } + } + } + return $success; + } + + function doInfo($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError("No channel specified"); + } + + $reg = &$this->config->getRegistry(); + $channel = strtolower($params[0]); + if ($reg->channelExists($channel)) { + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + } else { + if (strpos($channel, '://')) { + $downloader = &$this->getDownloader(); + $tmpdir = $this->config->get('temp_dir'); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $loc = $downloader->downloadHttp($channel, $this->ui, $tmpdir); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($loc)) { + return $this->raiseError('Cannot open "' . $channel . + '" (' . $loc->getMessage() . ')'); + } else { + $contents = implode('', file($loc)); + } + } else { + if (!file_exists($params[0])) { + return $this->raiseError('Unknown channel "' . $channel . '"'); + } + + $fp = fopen($params[0], 'r'); + if (!$fp) { + return $this->raiseError('Cannot open "' . $params[0] . '"'); + } + + $contents = ''; + while (!feof($fp)) { + $contents .= fread($fp, 1024); + } + fclose($fp); + } + + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $chan = new PEAR_ChannelFile; + $chan->fromXmlString($contents); + $chan->validate(); + if ($errs = $chan->getErrors(true)) { + foreach ($errs as $err) { + $this->ui->outputData($err['level'] . ': ' . $err['message']); + } + return $this->raiseError('Channel file "' . $params[0] . '" is not valid'); + } + } + + if (!$chan) { + return $this->raiseError('Serious error: Channel "' . $params[0] . + '" has a corrupted registry entry'); + } + + $channel = $chan->getName(); + $caption = 'Channel ' . $channel . ' Information:'; + $data1 = array( + 'caption' => $caption, + 'border' => true); + $data1['data']['server'] = array('Name and Server', $chan->getName()); + if ($chan->getAlias() != $chan->getName()) { + $data1['data']['alias'] = array('Alias', $chan->getAlias()); + } + + $data1['data']['summary'] = array('Summary', $chan->getSummary()); + $validate = $chan->getValidationPackage(); + $data1['data']['vpackage'] = array('Validation Package Name', $validate['_content']); + $data1['data']['vpackageversion'] = + array('Validation Package Version', $validate['attribs']['version']); + $d = array(); + $d['main'] = $data1; + + $data['data'] = array(); + $data['caption'] = 'Server Capabilities'; + $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base'); + if ($chan->supportsREST()) { + if ($chan->supportsREST()) { + $funcs = $chan->getFunctions('rest'); + if (!isset($funcs[0])) { + $funcs = array($funcs); + } + foreach ($funcs as $protocol) { + $data['data'][] = array('rest', $protocol['attribs']['type'], + $protocol['_content']); + } + } + } else { + $data['data'][] = array('No supported protocols'); + } + + $d['protocols'] = $data; + $data['data'] = array(); + $mirrors = $chan->getMirrors(); + if ($mirrors) { + $data['caption'] = 'Channel ' . $channel . ' Mirrors:'; + unset($data['headline']); + foreach ($mirrors as $mirror) { + $data['data'][] = array($mirror['attribs']['host']); + $d['mirrors'] = $data; + } + + foreach ($mirrors as $i => $mirror) { + $data['data'] = array(); + $data['caption'] = 'Mirror ' . $mirror['attribs']['host'] . ' Capabilities'; + $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base'); + if ($chan->supportsREST($mirror['attribs']['host'])) { + if ($chan->supportsREST($mirror['attribs']['host'])) { + $funcs = $chan->getFunctions('rest', $mirror['attribs']['host']); + if (!isset($funcs[0])) { + $funcs = array($funcs); + } + + foreach ($funcs as $protocol) { + $data['data'][] = array('rest', $protocol['attribs']['type'], + $protocol['_content']); + } + } + } else { + $data['data'][] = array('No supported protocols'); + } + $d['mirrorprotocols' . $i] = $data; + } + } + $this->ui->outputData($d, 'channel-info'); + } + + // }}} + + function doDelete($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('channel-delete: no channel specified'); + } + + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($params[0])) { + return $this->raiseError('channel-delete: channel "' . $params[0] . '" does not exist'); + } + + $channel = $reg->channelName($params[0]); + if ($channel == 'pear.php.net') { + return $this->raiseError('Cannot delete the pear.php.net channel'); + } + + if ($channel == 'pecl.php.net') { + return $this->raiseError('Cannot delete the pecl.php.net channel'); + } + + if ($channel == 'doc.php.net') { + return $this->raiseError('Cannot delete the doc.php.net channel'); + } + + if ($channel == '__uri') { + return $this->raiseError('Cannot delete the __uri pseudo-channel'); + } + + if (PEAR::isError($err = $reg->listPackages($channel))) { + return $err; + } + + if (count($err)) { + return $this->raiseError('Channel "' . $channel . + '" has installed packages, cannot delete'); + } + + if (!$reg->deleteChannel($channel)) { + return $this->raiseError('Channel "' . $channel . '" deletion failed'); + } else { + $this->config->deleteChannel($channel); + $this->ui->outputData('Channel "' . $channel . '" deleted', $command); + } + } + + function doAdd($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('channel-add: no channel file specified'); + } + + if (strpos($params[0], '://')) { + $downloader = &$this->getDownloader(); + $tmpdir = $this->config->get('temp_dir'); + if (!file_exists($tmpdir)) { + require_once 'System.php'; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = System::mkdir(array('-p', $tmpdir)); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($err)) { + return $this->raiseError('channel-add: temp_dir does not exist: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + } + + if (!is_writable($tmpdir)) { + return $this->raiseError('channel-add: temp_dir is not writable: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $loc = $downloader->downloadHttp($params[0], $this->ui, $tmpdir, null, false); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($loc)) { + return $this->raiseError('channel-add: Cannot open "' . $params[0] . + '" (' . $loc->getMessage() . ')'); + } + + list($loc, $lastmodified) = $loc; + $contents = implode('', file($loc)); + } else { + $lastmodified = $fp = false; + if (file_exists($params[0])) { + $fp = fopen($params[0], 'r'); + } + + if (!$fp) { + return $this->raiseError('channel-add: cannot open "' . $params[0] . '"'); + } + + $contents = ''; + while (!feof($fp)) { + $contents .= fread($fp, 1024); + } + fclose($fp); + } + + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $channel = new PEAR_ChannelFile; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $result = $channel->fromXmlString($contents); + PEAR::staticPopErrorHandling(); + if (!$result) { + $exit = false; + if (count($errors = $channel->getErrors(true))) { + foreach ($errors as $error) { + $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message'])); + if (!$exit) { + $exit = $error['level'] == 'error' ? true : false; + } + } + if ($exit) { + return $this->raiseError('channel-add: invalid channel.xml file'); + } + } + } + + $reg = &$this->config->getRegistry(); + if ($reg->channelExists($channel->getName())) { + return $this->raiseError('channel-add: Channel "' . $channel->getName() . + '" exists, use channel-update to update entry', PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS); + } + + $ret = $reg->addChannel($channel, $lastmodified); + if (PEAR::isError($ret)) { + return $ret; + } + + if (!$ret) { + return $this->raiseError('channel-add: adding Channel "' . $channel->getName() . + '" to registry failed'); + } + + $this->config->setChannels($reg->listChannels()); + $this->config->writeConfigFile(); + $this->ui->outputData('Adding Channel "' . $channel->getName() . '" succeeded', $command); + } + + function doUpdate($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError("No channel file specified"); + } + + $tmpdir = $this->config->get('temp_dir'); + if (!file_exists($tmpdir)) { + require_once 'System.php'; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = System::mkdir(array('-p', $tmpdir)); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($err)) { + return $this->raiseError('channel-add: temp_dir does not exist: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + } + + if (!is_writable($tmpdir)) { + return $this->raiseError('channel-add: temp_dir is not writable: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + + $reg = &$this->config->getRegistry(); + $lastmodified = false; + if ((!file_exists($params[0]) || is_dir($params[0])) + && $reg->channelExists(strtolower($params[0]))) { + $c = $reg->getChannel(strtolower($params[0])); + if (PEAR::isError($c)) { + return $this->raiseError($c); + } + + $this->ui->outputData("Updating channel \"$params[0]\"", $command); + $dl = &$this->getDownloader(array()); + // if force is specified, use a timestamp of "1" to force retrieval + $lastmodified = isset($options['force']) ? false : $c->lastModified(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $contents = $dl->downloadHttp('http://' . $c->getName() . '/channel.xml', + $this->ui, $tmpdir, null, $lastmodified); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($contents)) { + // Attempt to fall back to https + $this->ui->outputData("Channel \"$params[0]\" is not responding over http://, failed with message: " . $contents->getMessage()); + $this->ui->outputData("Trying channel \"$params[0]\" over https:// instead"); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $contents = $dl->downloadHttp('https://' . $c->getName() . '/channel.xml', + $this->ui, $tmpdir, null, $lastmodified); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($contents)) { + return $this->raiseError('Cannot retrieve channel.xml for channel "' . + $c->getName() . '" (' . $contents->getMessage() . ')'); + } + } + + list($contents, $lastmodified) = $contents; + if (!$contents) { + $this->ui->outputData("Channel \"$params[0]\" is up to date"); + return; + } + + $contents = implode('', file($contents)); + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $channel = new PEAR_ChannelFile; + $channel->fromXmlString($contents); + if (!$channel->getErrors()) { + // security check: is the downloaded file for the channel we got it from? + if (strtolower($channel->getName()) != strtolower($c->getName())) { + if (!isset($options['force'])) { + return $this->raiseError('ERROR: downloaded channel definition file' . + ' for channel "' . $channel->getName() . '" from channel "' . + strtolower($c->getName()) . '"'); + } + + $this->ui->log(0, 'WARNING: downloaded channel definition file' . + ' for channel "' . $channel->getName() . '" from channel "' . + strtolower($c->getName()) . '"'); + } + } + } else { + if (strpos($params[0], '://')) { + $dl = &$this->getDownloader(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $loc = $dl->downloadHttp($params[0], + $this->ui, $tmpdir, null, $lastmodified); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($loc)) { + return $this->raiseError("Cannot open " . $params[0] . + ' (' . $loc->getMessage() . ')'); + } + + list($loc, $lastmodified) = $loc; + $contents = implode('', file($loc)); + } else { + $fp = false; + if (file_exists($params[0])) { + $fp = fopen($params[0], 'r'); + } + + if (!$fp) { + return $this->raiseError("Cannot open " . $params[0]); + } + + $contents = ''; + while (!feof($fp)) { + $contents .= fread($fp, 1024); + } + fclose($fp); + } + + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $channel = new PEAR_ChannelFile; + $channel->fromXmlString($contents); + } + + $exit = false; + if (count($errors = $channel->getErrors(true))) { + foreach ($errors as $error) { + $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message'])); + if (!$exit) { + $exit = $error['level'] == 'error' ? true : false; + } + } + if ($exit) { + return $this->raiseError('Invalid channel.xml file'); + } + } + + if (!$reg->channelExists($channel->getName())) { + return $this->raiseError('Error: Channel "' . $channel->getName() . + '" does not exist, use channel-add to add an entry'); + } + + $ret = $reg->updateChannel($channel, $lastmodified); + if (PEAR::isError($ret)) { + return $ret; + } + + if (!$ret) { + return $this->raiseError('Updating Channel "' . $channel->getName() . + '" in registry failed'); + } + + $this->config->setChannels($reg->listChannels()); + $this->config->writeConfigFile(); + $this->ui->outputData('Update of Channel "' . $channel->getName() . '" succeeded'); + } + + function &getDownloader() + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + $a = new PEAR_Downloader($this->ui, array(), $this->config); + return $a; + } + + function doAlias($command, $options, $params) + { + if (count($params) === 1) { + return $this->raiseError('No channel alias specified'); + } + + if (count($params) !== 2 || (!empty($params[1]) && $params[1]{0} == '-')) { + return $this->raiseError( + 'Invalid format, correct is: channel-alias channel alias'); + } + + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($params[0], true)) { + $extra = ''; + if ($reg->isAlias($params[0])) { + $extra = ' (use "channel-alias ' . $reg->channelName($params[0]) . ' ' . + strtolower($params[1]) . '")'; + } + + return $this->raiseError('"' . $params[0] . '" is not a valid channel' . $extra); + } + + if ($reg->isAlias($params[1])) { + return $this->raiseError('Channel "' . $reg->channelName($params[1]) . '" is ' . + 'already aliased to "' . strtolower($params[1]) . '", cannot re-alias'); + } + + $chan = &$reg->getChannel($params[0]); + if (PEAR::isError($chan)) { + return $this->raiseError('Corrupt registry? Error retrieving channel "' . $params[0] . + '" information (' . $chan->getMessage() . ')'); + } + + // make it a local alias + if (!$chan->setAlias(strtolower($params[1]), true)) { + return $this->raiseError('Alias "' . strtolower($params[1]) . + '" is not a valid channel alias'); + } + + $reg->updateChannel($chan); + $this->ui->outputData('Channel "' . $chan->getName() . '" aliased successfully to "' . + strtolower($params[1]) . '"'); + } + + /** + * The channel-discover command + * + * @param string $command command name + * @param array $options option_name => value + * @param array $params list of additional parameters. + * $params[0] should contain a string with either: + * - or + * - :@ + * @return null|PEAR_Error + */ + function doDiscover($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError("No channel server specified"); + } + + // Look for the possible input format ":@" + if (preg_match('/^(.+):(.+)@(.+)\\z/', $params[0], $matches)) { + $username = $matches[1]; + $password = $matches[2]; + $channel = $matches[3]; + } else { + $channel = $params[0]; + } + + $reg = &$this->config->getRegistry(); + if ($reg->channelExists($channel)) { + if (!$reg->isAlias($channel)) { + return $this->raiseError("Channel \"$channel\" is already initialized", PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS); + } + + return $this->raiseError("A channel alias named \"$channel\" " . + 'already exists, aliasing channel "' . $reg->channelName($channel) + . '"'); + } + + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->doAdd($command, $options, array('http://' . $channel . '/channel.xml')); + $this->popErrorHandling(); + if (PEAR::isError($err)) { + if ($err->getCode() === PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS) { + return $this->raiseError("Discovery of channel \"$channel\" failed (" . + $err->getMessage() . ')'); + } + // Attempt fetch via https + $this->ui->outputData("Discovering channel $channel over http:// failed with message: " . $err->getMessage()); + $this->ui->outputData("Trying to discover channel $channel over https:// instead"); + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->doAdd($command, $options, array('https://' . $channel . '/channel.xml')); + $this->popErrorHandling(); + if (PEAR::isError($err)) { + return $this->raiseError("Discovery of channel \"$channel\" failed (" . + $err->getMessage() . ')'); + } + } + + // Store username/password if they were given + // Arguably we should do a logintest on the channel here, but since + // that's awkward on a REST-based channel (even "pear login" doesn't + // do it for those), and XML-RPC is deprecated, it's fairly pointless. + if (isset($username)) { + $this->config->set('username', $username, 'user', $channel); + $this->config->set('password', $password, 'user', $channel); + $this->config->store(); + $this->ui->outputData("Stored login for channel \"$channel\" using username \"$username\"", $command); + } + + $this->ui->outputData("Discovery of channel \"$channel\" succeeded", $command); + } + + /** + * Execute the 'login' command. + * + * @param string $command command name + * @param array $options option_name => value + * @param array $params list of additional parameters + * + * @return bool TRUE on success or + * a PEAR error on failure + * + * @access public + */ + function doLogin($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + + // If a parameter is supplied, use that as the channel to log in to + $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel'); + + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + $server = $this->config->get('preferred_mirror', null, $channel); + $username = $this->config->get('username', null, $channel); + if (empty($username)) { + $username = isset($_ENV['USER']) ? $_ENV['USER'] : null; + } + $this->ui->outputData("Logging in to $server.", $command); + + list($username, $password) = $this->ui->userDialog( + $command, + array('Username', 'Password'), + array('text', 'password'), + array($username, '') + ); + $username = trim($username); + $password = trim($password); + + $ourfile = $this->config->getConfFile('user'); + if (!$ourfile) { + $ourfile = $this->config->getConfFile('system'); + } + + $this->config->set('username', $username, 'user', $channel); + $this->config->set('password', $password, 'user', $channel); + + if ($chan->supportsREST()) { + $ok = true; + } + + if ($ok !== true) { + return $this->raiseError('Login failed!'); + } + + $this->ui->outputData("Logged in.", $command); + // avoid changing any temporary settings changed with -d + $ourconfig = new PEAR_Config($ourfile, $ourfile); + $ourconfig->set('username', $username, 'user', $channel); + $ourconfig->set('password', $password, 'user', $channel); + $ourconfig->store(); + + return true; + } + + /** + * Execute the 'logout' command. + * + * @param string $command command name + * @param array $options option_name => value + * @param array $params list of additional parameters + * + * @return bool TRUE on success or + * a PEAR error on failure + * + * @access public + */ + function doLogout($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + + // If a parameter is supplied, use that as the channel to log in to + $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel'); + + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + $server = $this->config->get('preferred_mirror', null, $channel); + $this->ui->outputData("Logging out from $server.", $command); + $this->config->remove('username', 'user', $channel); + $this->config->remove('password', 'user', $channel); + $this->config->store(); + return true; + } +}PEAR-1.9.0/PEAR/Command/Common.php100664 764 764 20144 100664 11375 + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR.php'; + +/** + * PEAR commands base class + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Common extends PEAR +{ + /** + * PEAR_Config object used to pass user system and configuration + * on when executing commands + * + * @var PEAR_Config + */ + var $config; + /** + * @var PEAR_Registry + * @access protected + */ + var $_registry; + + /** + * User Interface object, for all interaction with the user. + * @var object + */ + var $ui; + + var $_deps_rel_trans = array( + 'lt' => '<', + 'le' => '<=', + 'eq' => '=', + 'ne' => '!=', + 'gt' => '>', + 'ge' => '>=', + 'has' => '==' + ); + + var $_deps_type_trans = array( + 'pkg' => 'package', + 'ext' => 'extension', + 'php' => 'PHP', + 'prog' => 'external program', + 'ldlib' => 'external library for linking', + 'rtlib' => 'external runtime library', + 'os' => 'operating system', + 'websrv' => 'web server', + 'sapi' => 'SAPI backend' + ); + + /** + * PEAR_Command_Common constructor. + * + * @access public + */ + function PEAR_Command_Common(&$ui, &$config) + { + parent::PEAR(); + $this->config = &$config; + $this->ui = &$ui; + } + + /** + * Return a list of all the commands defined by this class. + * @return array list of commands + * @access public + */ + function getCommands() + { + $ret = array(); + foreach (array_keys($this->commands) as $command) { + $ret[$command] = $this->commands[$command]['summary']; + } + + return $ret; + } + + /** + * Return a list of all the command shortcuts defined by this class. + * @return array shortcut => command + * @access public + */ + function getShortcuts() + { + $ret = array(); + foreach (array_keys($this->commands) as $command) { + if (isset($this->commands[$command]['shortcut'])) { + $ret[$this->commands[$command]['shortcut']] = $command; + } + } + + return $ret; + } + + function getOptions($command) + { + $shortcuts = $this->getShortcuts(); + if (isset($shortcuts[$command])) { + $command = $shortcuts[$command]; + } + + if (isset($this->commands[$command]) && + isset($this->commands[$command]['options'])) { + return $this->commands[$command]['options']; + } + + return null; + } + + function getGetoptArgs($command, &$short_args, &$long_args) + { + $short_args = ''; + $long_args = array(); + if (empty($this->commands[$command]) || empty($this->commands[$command]['options'])) { + return; + } + + reset($this->commands[$command]['options']); + while (list($option, $info) = each($this->commands[$command]['options'])) { + $larg = $sarg = ''; + if (isset($info['arg'])) { + if ($info['arg']{0} == '(') { + $larg = '=='; + $sarg = '::'; + $arg = substr($info['arg'], 1, -1); + } else { + $larg = '='; + $sarg = ':'; + $arg = $info['arg']; + } + } + + if (isset($info['shortopt'])) { + $short_args .= $info['shortopt'] . $sarg; + } + + $long_args[] = $option . $larg; + } + } + + /** + * Returns the help message for the given command + * + * @param string $command The command + * @return mixed A fail string if the command does not have help or + * a two elements array containing [0]=>help string, + * [1]=> help string for the accepted cmd args + */ + function getHelp($command) + { + $config = &PEAR_Config::singleton(); + if (!isset($this->commands[$command])) { + return "No such command \"$command\""; + } + + $help = null; + if (isset($this->commands[$command]['doc'])) { + $help = $this->commands[$command]['doc']; + } + + if (empty($help)) { + // XXX (cox) Fallback to summary if there is no doc (show both?) + if (!isset($this->commands[$command]['summary'])) { + return "No help for command \"$command\""; + } + $help = $this->commands[$command]['summary']; + } + + if (preg_match_all('/{config\s+([^\}]+)}/e', $help, $matches)) { + foreach($matches[0] as $k => $v) { + $help = preg_replace("/$v/", $config->get($matches[1][$k]), $help); + } + } + + return array($help, $this->getHelpArgs($command)); + } + + /** + * Returns the help for the accepted arguments of a command + * + * @param string $command + * @return string The help string + */ + function getHelpArgs($command) + { + if (isset($this->commands[$command]['options']) && + count($this->commands[$command]['options'])) + { + $help = "Options:\n"; + foreach ($this->commands[$command]['options'] as $k => $v) { + if (isset($v['arg'])) { + if ($v['arg'][0] == '(') { + $arg = substr($v['arg'], 1, -1); + $sapp = " [$arg]"; + $lapp = "[=$arg]"; + } else { + $sapp = " $v[arg]"; + $lapp = "=$v[arg]"; + } + } else { + $sapp = $lapp = ""; + } + + if (isset($v['shortopt'])) { + $s = $v['shortopt']; + $help .= " -$s$sapp, --$k$lapp\n"; + } else { + $help .= " --$k$lapp\n"; + } + + $p = " "; + $doc = rtrim(str_replace("\n", "\n$p", $v['doc'])); + $help .= " $doc\n"; + } + + return $help; + } + + return null; + } + + function run($command, $options, $params) + { + if (empty($this->commands[$command]['function'])) { + // look for shortcuts + foreach (array_keys($this->commands) as $cmd) { + if (isset($this->commands[$cmd]['shortcut']) && $this->commands[$cmd]['shortcut'] == $command) { + if (empty($this->commands[$cmd]['function'])) { + return $this->raiseError("unknown command `$command'"); + } else { + $func = $this->commands[$cmd]['function']; + } + $command = $cmd; + + //$command = $this->commands[$cmd]['function']; + break; + } + } + } else { + $func = $this->commands[$command]['function']; + } + + return $this->$func($command, $options, $params); + } +}PEAR-1.9.0/PEAR/Command/Config.xml100664 764 764 6466 100664 11356 + + Show All Settings + doConfigShow + csh + + + c + show configuration variables for another channel + CHAN + + + [layer] +Displays all configuration values. An optional argument +may be used to tell which configuration layer to display. Valid +configuration layers are "user", "system" and "default". To display +configurations for different channels, set the default_channel +configuration variable and run config-show again. + + + + Show One Setting + doConfigGet + cg + + + c + show configuration variables for another channel + CHAN + + + <parameter> [layer] +Displays the value of one configuration parameter. The +first argument is the name of the parameter, an optional second argument +may be used to tell which configuration layer to look in. Valid configuration +layers are "user", "system" and "default". If no layer is specified, a value +will be picked from the first layer that defines the parameter, in the order +just specified. The configuration value will be retrieved for the channel +specified by the default_channel configuration variable. + + + + Change Setting + doConfigSet + cs + + + c + show configuration variables for another channel + CHAN + + + <parameter> <value> [layer] +Sets the value of one configuration parameter. The first argument is +the name of the parameter, the second argument is the new value. Some +parameters are subject to validation, and the command will fail with +an error message if the new value does not make sense. An optional +third argument may be used to specify in which layer to set the +configuration parameter. The default layer is "user". The +configuration value will be set for the current channel, which +is controlled by the default_channel configuration variable. + + + + Show Information About Setting + doConfigHelp + ch + + [parameter] +Displays help for a configuration parameter. Without arguments it +displays help for all configuration parameters. + + + + Create a Default configuration file + doConfigCreate + coc + + + w + create a config file for a windows install + + + <root path> <filename> +Create a default configuration file with all directory configuration +variables set to subdirectories of <root path>, and save it as <filename>. +This is useful especially for creating a configuration file for a remote +PEAR installation (using the --remoteconfig option of install, upgrade, +and uninstall). + + +PEAR-1.9.0/PEAR/Command/Config.php100664 764 764 36076 100664 11365 + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Config.php 287554 2009-08-21 21:16:25Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for managing configuration data. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Config extends PEAR_Command_Common +{ + var $commands = array( + 'config-show' => array( + 'summary' => 'Show All Settings', + 'function' => 'doConfigShow', + 'shortcut' => 'csh', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', + ), +), + 'doc' => '[layer] +Displays all configuration values. An optional argument +may be used to tell which configuration layer to display. Valid +configuration layers are "user", "system" and "default". To display +configurations for different channels, set the default_channel +configuration variable and run config-show again. +', + ), + 'config-get' => array( + 'summary' => 'Show One Setting', + 'function' => 'doConfigGet', + 'shortcut' => 'cg', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', + ), +), + 'doc' => ' [layer] +Displays the value of one configuration parameter. The +first argument is the name of the parameter, an optional second argument +may be used to tell which configuration layer to look in. Valid configuration +layers are "user", "system" and "default". If no layer is specified, a value +will be picked from the first layer that defines the parameter, in the order +just specified. The configuration value will be retrieved for the channel +specified by the default_channel configuration variable. +', + ), + 'config-set' => array( + 'summary' => 'Change Setting', + 'function' => 'doConfigSet', + 'shortcut' => 'cs', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', + ), +), + 'doc' => ' [layer] +Sets the value of one configuration parameter. The first argument is +the name of the parameter, the second argument is the new value. Some +parameters are subject to validation, and the command will fail with +an error message if the new value does not make sense. An optional +third argument may be used to specify in which layer to set the +configuration parameter. The default layer is "user". The +configuration value will be set for the current channel, which +is controlled by the default_channel configuration variable. +', + ), + 'config-help' => array( + 'summary' => 'Show Information About Setting', + 'function' => 'doConfigHelp', + 'shortcut' => 'ch', + 'options' => array(), + 'doc' => '[parameter] +Displays help for a configuration parameter. Without arguments it +displays help for all configuration parameters. +', + ), + 'config-create' => array( + 'summary' => 'Create a Default configuration file', + 'function' => 'doConfigCreate', + 'shortcut' => 'coc', + 'options' => array( + 'windows' => array( + 'shortopt' => 'w', + 'doc' => 'create a config file for a windows install', + ), + ), + 'doc' => ' +Create a default configuration file with all directory configuration +variables set to subdirectories of , and save it as . +This is useful especially for creating a configuration file for a remote +PEAR installation (using the --remoteconfig option of install, upgrade, +and uninstall). +', + ), + ); + + /** + * PEAR_Command_Config constructor. + * + * @access public + */ + function PEAR_Command_Config(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function doConfigShow($command, $options, $params) + { + $layer = null; + if (is_array($params)) { + $layer = isset($params[0]) ? $params[0] : null; + } + + // $params[0] -> the layer + if ($error = $this->_checkLayer($layer)) { + return $this->raiseError("config-show:$error"); + } + + $keys = $this->config->getKeys(); + sort($keys); + $channel = isset($options['channel']) ? $options['channel'] : + $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $channel = $reg->channelName($channel); + $data = array('caption' => 'Configuration (channel ' . $channel . '):'); + foreach ($keys as $key) { + $type = $this->config->getType($key); + $value = $this->config->get($key, $layer, $channel); + if ($type == 'password' && $value) { + $value = '********'; + } + + if ($value === false) { + $value = 'false'; + } elseif ($value === true) { + $value = 'true'; + } + + $data['data'][$this->config->getGroup($key)][] = array($this->config->getPrompt($key) , $key, $value); + } + + foreach ($this->config->getLayers() as $layer) { + $data['data']['Config Files'][] = array(ucfirst($layer) . ' Configuration File', 'Filename' , $this->config->getConfFile($layer)); + } + + $this->ui->outputData($data, $command); + return true; + } + + function doConfigGet($command, $options, $params) + { + $args_cnt = is_array($params) ? count($params) : 0; + switch ($args_cnt) { + case 1: + $config_key = $params[0]; + $layer = null; + break; + case 2: + $config_key = $params[0]; + $layer = $params[1]; + if ($error = $this->_checkLayer($layer)) { + return $this->raiseError("config-get:$error"); + } + break; + case 0: + default: + return $this->raiseError("config-get expects 1 or 2 parameters"); + } + + $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $this->ui->outputData($this->config->get($config_key, $layer, $channel), $command); + return true; + } + + function doConfigSet($command, $options, $params) + { + // $param[0] -> a parameter to set + // $param[1] -> the value for the parameter + // $param[2] -> the layer + $failmsg = ''; + if (count($params) < 2 || count($params) > 3) { + $failmsg .= "config-set expects 2 or 3 parameters"; + return PEAR::raiseError($failmsg); + } + + if (isset($params[2]) && ($error = $this->_checkLayer($params[2]))) { + $failmsg .= $error; + return PEAR::raiseError("config-set:$failmsg"); + } + + $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $channel = $reg->channelName($channel); + if ($params[0] == 'default_channel' && !$reg->channelExists($params[1])) { + return $this->raiseError('Channel "' . $params[1] . '" does not exist'); + } + + if ($params[0] == 'preferred_mirror' + && ( + !$reg->mirrorExists($channel, $params[1]) && + (!$reg->channelExists($params[1]) || $channel != $params[1]) + ) + ) { + $msg = 'Channel Mirror "' . $params[1] . '" does not exist'; + $msg .= ' in your registry for channel "' . $channel . '".'; + $msg .= "\n" . 'Attempt to run "pear channel-update ' . $channel .'"'; + $msg .= ' if you believe this mirror should exist as you may'; + $msg .= ' have outdated channel information.'; + return $this->raiseError($msg); + } + + if (count($params) == 2) { + array_push($params, 'user'); + $layer = 'user'; + } else { + $layer = $params[2]; + } + + array_push($params, $channel); + if (!call_user_func_array(array(&$this->config, 'set'), $params)) { + array_pop($params); + $failmsg = "config-set (" . implode(", ", $params) . ") failed, channel $channel"; + } else { + $this->config->store($layer); + } + + if ($failmsg) { + return $this->raiseError($failmsg); + } + + $this->ui->outputData('config-set succeeded', $command); + return true; + } + + function doConfigHelp($command, $options, $params) + { + if (empty($params)) { + $params = $this->config->getKeys(); + } + + $data['caption'] = "Config help" . ((count($params) == 1) ? " for $params[0]" : ''); + $data['headline'] = array('Name', 'Type', 'Description'); + $data['border'] = true; + foreach ($params as $name) { + $type = $this->config->getType($name); + $docs = $this->config->getDocs($name); + if ($type == 'set') { + $docs = rtrim($docs) . "\nValid set: " . + implode(' ', $this->config->getSetValues($name)); + } + + $data['data'][] = array($name, $type, $docs); + } + + $this->ui->outputData($data, $command); + } + + function doConfigCreate($command, $options, $params) + { + if (count($params) != 2) { + return PEAR::raiseError('config-create: must have 2 parameters, root path and ' . + 'filename to save as'); + } + + $root = $params[0]; + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + $root = preg_replace(array('!\\\\+!', '!/+!', "!$ds2+!"), + array('/', '/', '/'), + $root); + if ($root{0} != '/') { + if (!isset($options['windows'])) { + return PEAR::raiseError('Root directory must be an absolute path beginning ' . + 'with "/", was: "' . $root . '"'); + } + + if (!preg_match('/^[A-Za-z]:/', $root)) { + return PEAR::raiseError('Root directory must be an absolute path beginning ' . + 'with "\\" or "C:\\", was: "' . $root . '"'); + } + } + + $windows = isset($options['windows']); + if ($windows) { + $root = str_replace('/', '\\', $root); + } + + if (!file_exists($params[1]) && !@touch($params[1])) { + return PEAR::raiseError('Could not create "' . $params[1] . '"'); + } + + $params[1] = realpath($params[1]); + $config = &new PEAR_Config($params[1], '#no#system#config#', false, false); + if ($root{strlen($root) - 1} == '/') { + $root = substr($root, 0, strlen($root) - 1); + } + + $config->noRegistry(); + $config->set('php_dir', $windows ? "$root\\pear\\php" : "$root/pear/php", 'user'); + $config->set('data_dir', $windows ? "$root\\pear\\data" : "$root/pear/data"); + $config->set('www_dir', $windows ? "$root\\pear\\www" : "$root/pear/www"); + $config->set('cfg_dir', $windows ? "$root\\pear\\cfg" : "$root/pear/cfg"); + $config->set('ext_dir', $windows ? "$root\\pear\\ext" : "$root/pear/ext"); + $config->set('doc_dir', $windows ? "$root\\pear\\docs" : "$root/pear/docs"); + $config->set('test_dir', $windows ? "$root\\pear\\tests" : "$root/pear/tests"); + $config->set('cache_dir', $windows ? "$root\\pear\\cache" : "$root/pear/cache"); + $config->set('download_dir', $windows ? "$root\\pear\\download" : "$root/pear/download"); + $config->set('temp_dir', $windows ? "$root\\pear\\temp" : "$root/pear/temp"); + $config->set('bin_dir', $windows ? "$root\\pear" : "$root/pear"); + $config->writeConfigFile(); + $this->_showConfig($config); + $this->ui->outputData('Successfully created default configuration file "' . $params[1] . '"', + $command); + } + + function _showConfig(&$config) + { + $params = array('user'); + $keys = $config->getKeys(); + sort($keys); + $channel = 'pear.php.net'; + $data = array('caption' => 'Configuration (channel ' . $channel . '):'); + foreach ($keys as $key) { + $type = $config->getType($key); + $value = $config->get($key, 'user', $channel); + if ($type == 'password' && $value) { + $value = '********'; + } + + if ($value === false) { + $value = 'false'; + } elseif ($value === true) { + $value = 'true'; + } + $data['data'][$config->getGroup($key)][] = + array($config->getPrompt($key) , $key, $value); + } + + foreach ($config->getLayers() as $layer) { + $data['data']['Config Files'][] = + array(ucfirst($layer) . ' Configuration File', 'Filename' , + $config->getConfFile($layer)); + } + + $this->ui->outputData($data, 'config-show'); + return true; + } + + /** + * Checks if a layer is defined or not + * + * @param string $layer The layer to search for + * @return mixed False on no error or the error message + */ + function _checkLayer($layer = null) + { + if (!empty($layer) && $layer != 'default') { + $layers = $this->config->getLayers(); + if (!in_array($layer, $layers)) { + return " only the layers: \"" . implode('" or "', $layers) . "\" are supported"; + } + } + + return false; + } +}PEAR-1.9.0/PEAR/Command/Install.xml100664 764 764 20576 100664 11575 + + Install Package + doInstall + i + + + f + will overwrite newer installed packages + + + l + do not check for recommended dependency version + + + n + ignore dependencies, install anyway + + + r + do not install files, only register the package as installed + + + s + soft install, fail silently, or upgrade if already installed + + + B + don't build C extensions + + + Z + request uncompressed files when downloading + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT), use packagingroot for RPM + DIR + + + P + root directory used when packaging files, like RPM packaging + DIR + + + + force install even if there were errors + + + a + install all required and optional dependencies + + + o + install all required dependencies + + + O + do not attempt to download any urls or contact channels + + + p + Only list the packages that would be downloaded + + + [channel/]<package> ... +Installs one or more PEAR packages. You can specify a package to +install in four ways: + +"Package-1.0.tgz" : installs from a local file + +"http://example.com/Package-1.0.tgz" : installs from +anywhere on the net. + +"package.xml" : installs the package described in +package.xml. Useful for testing, or for wrapping a PEAR package in +another package manager such as RPM. + +"Package[-version/state][.tar]" : queries your default channel's server +({config master_server}) and downloads the newest package with +the preferred quality/state ({config preferred_state}). + +To retrieve Package version 1.1, use "Package-1.1," to retrieve +Package state beta, use "Package-beta." To retrieve an uncompressed +file, append .tar (make sure there is no file by the same name first) + +To download a package from another channel, prefix with the channel name like +"channel/Package" + +More than one package may be specified at once. It is ok to mix these +four ways of specifying packages. + + + + Upgrade Package + doInstall + up + + + c + upgrade packages from a specific channel + CHAN + + + f + overwrite newer installed packages + + + l + do not check for recommended dependency version + + + n + ignore dependencies, upgrade anyway + + + r + do not install files, only register the package as upgraded + + + B + don't build C extensions + + + Z + request uncompressed files when downloading + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT) + DIR + + + + force install even if there were errors + + + a + install all required and optional dependencies + + + o + install all required dependencies + + + O + do not attempt to download any urls or contact channels + + + p + Only list the packages that would be downloaded + + + <package> ... +Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. + + + + Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters] + doUpgradeAll + ua + + + c + upgrade packages from a specific channel + CHAN + + + n + ignore dependencies, upgrade anyway + + + r + do not install files, only register the package as upgraded + + + B + don't build C extensions + + + Z + request uncompressed files when downloading + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT), use packagingroot for RPM + DIR + + + + force install even if there were errors + + + + do not check for recommended dependency version + + + +WARNING: This function is deprecated in favor of using the upgrade command with no params + +Upgrades all packages that have a newer release available. Upgrades are +done only if there is a release available of the state specified in +"preferred_state" (currently {config preferred_state}), or a state considered +more stable. + + + + Un-install Package + doUninstall + un + + + n + ignore dependencies, uninstall anyway + + + r + do not remove files, only register the packages as not installed + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT) + DIR + + + + force install even if there were errors + + + O + do not attempt to uninstall remotely + + + [channel/]<package> ... +Uninstalls one or more PEAR packages. More than one package may be +specified at once. Prefix with channel name to uninstall from a +channel not in your default channel ({config default_channel}) + + + + Unpacks a Pecl Package + doBundle + bun + + + d + Optional destination directory for unpacking (defaults to current path or "ext" if exists) + DIR + + + f + Force the unpacking even if there were errors in the package + + + <package> +Unpacks a Pecl Package into the selected location. It will download the +package if needed. + + + + Run Post-Install Scripts bundled with a package + doRunScripts + rs + + <package> +Run post-installation scripts in package <package>, if any exist. + + +PEAR-1.9.0/PEAR/Command/Install.php100664 764 764 143164 100664 11603 + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Install.php 287477 2009-08-19 14:19:43Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for installation or deinstallation/upgrading of + * packages. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Install extends PEAR_Command_Common +{ + // {{{ properties + + var $commands = array( + 'install' => array( + 'summary' => 'Install Package', + 'function' => 'doInstall', + 'shortcut' => 'i', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'will overwrite newer installed packages', + ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, install anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as installed', + ), + 'soft' => array( + 'shortopt' => 's', + 'doc' => 'soft install, fail silently, or upgrade if already installed', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM', + ), + 'packagingroot' => array( + 'shortopt' => 'P', + 'arg' => 'DIR', + 'doc' => 'root directory used when packaging files, like RPM packaging', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'onlyreqdeps' => array( + 'shortopt' => 'o', + 'doc' => 'install all required dependencies', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to download any urls or contact channels', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), + ), + 'doc' => '[channel/] ... +Installs one or more PEAR packages. You can specify a package to +install in four ways: + +"Package-1.0.tgz" : installs from a local file + +"http://example.com/Package-1.0.tgz" : installs from +anywhere on the net. + +"package.xml" : installs the package described in +package.xml. Useful for testing, or for wrapping a PEAR package in +another package manager such as RPM. + +"Package[-version/state][.tar]" : queries your default channel\'s server +({config master_server}) and downloads the newest package with +the preferred quality/state ({config preferred_state}). + +To retrieve Package version 1.1, use "Package-1.1," to retrieve +Package state beta, use "Package-beta." To retrieve an uncompressed +file, append .tar (make sure there is no file by the same name first) + +To download a package from another channel, prefix with the channel name like +"channel/Package" + +More than one package may be specified at once. It is ok to mix these +four ways of specifying packages. +'), + 'upgrade' => array( + 'summary' => 'Upgrade Package', + 'function' => 'doInstall', + 'shortcut' => 'up', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'overwrite newer installed packages', + ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'onlyreqdeps' => array( + 'shortopt' => 'o', + 'doc' => 'install all required dependencies', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to download any urls or contact channels', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), + ), + 'doc' => ' ... +Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. +'), + 'upgrade-all' => array( + 'summary' => 'Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters]', + 'function' => 'doUpgradeAll', + 'shortcut' => 'ua', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'loose' => array( + 'doc' => 'do not check for recommended dependency version', + ), + ), + 'doc' => ' +WARNING: This function is deprecated in favor of using the upgrade command with no params + +Upgrades all packages that have a newer release available. Upgrades are +done only if there is a release available of the state specified in +"preferred_state" (currently {config preferred_state}), or a state considered +more stable. +'), + 'uninstall' => array( + 'summary' => 'Un-install Package', + 'function' => 'doUninstall', + 'shortcut' => 'un', + 'options' => array( + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, uninstall anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not remove files, only register the packages as not installed', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to uninstall remotely', + ), + ), + 'doc' => '[channel/] ... +Uninstalls one or more PEAR packages. More than one package may be +specified at once. Prefix with channel name to uninstall from a +channel not in your default channel ({config default_channel}) +'), + 'bundle' => array( + 'summary' => 'Unpacks a Pecl Package', + 'function' => 'doBundle', + 'shortcut' => 'bun', + 'options' => array( + 'destination' => array( + 'shortopt' => 'd', + 'arg' => 'DIR', + 'doc' => 'Optional destination directory for unpacking (defaults to current path or "ext" if exists)', + ), + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'Force the unpacking even if there were errors in the package', + ), + ), + 'doc' => ' +Unpacks a Pecl Package into the selected location. It will download the +package if needed. +'), + 'run-scripts' => array( + 'summary' => 'Run Post-Install Scripts bundled with a package', + 'function' => 'doRunScripts', + 'shortcut' => 'rs', + 'options' => array( + ), + 'doc' => ' +Run post-installation scripts in package , if any exist. +'), + ); + + // }}} + // {{{ constructor + + /** + * PEAR_Command_Install constructor. + * + * @access public + */ + function PEAR_Command_Install(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + // }}} + + /** + * For unit testing purposes + */ + function &getDownloader(&$ui, $options, &$config) + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + $a = &new PEAR_Downloader($ui, $options, $config); + return $a; + } + + /** + * For unit testing purposes + */ + function &getInstaller(&$ui) + { + if (!class_exists('PEAR_Installer')) { + require_once 'PEAR/Installer.php'; + } + $a = &new PEAR_Installer($ui); + return $a; + } + + function enableExtension($binaries, $type) + { + if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) { + return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location'); + } + $ini = $this->_parseIni($phpini); + if (PEAR::isError($ini)) { + return $ini; + } + $line = 0; + if ($type == 'extsrc' || $type == 'extbin') { + $search = 'extensions'; + $enable = 'extension'; + } else { + $search = 'zend_extensions'; + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('Thread Safety.+enabled', $info) ? '_ts' : ''; + $enable = 'zend_extension' . $debug . $ts; + } + foreach ($ini[$search] as $line => $extension) { + if (in_array($extension, $binaries, true) || in_array( + $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) { + // already enabled - assume if one is, all are + return true; + } + } + if ($line) { + $newini = array_slice($ini['all'], 0, $line); + } else { + $newini = array(); + } + foreach ($binaries as $binary) { + if ($ini['extension_dir']) { + $binary = basename($binary); + } + $newini[] = $enable . '="' . $binary . '"' . (OS_UNIX ? "\n" : "\r\n"); + } + $newini = array_merge($newini, array_slice($ini['all'], $line)); + $fp = @fopen($phpini, 'wb'); + if (!$fp) { + return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing'); + } + foreach ($newini as $line) { + fwrite($fp, $line); + } + fclose($fp); + return true; + } + + function disableExtension($binaries, $type) + { + if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) { + return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location'); + } + $ini = $this->_parseIni($phpini); + if (PEAR::isError($ini)) { + return $ini; + } + $line = 0; + if ($type == 'extsrc' || $type == 'extbin') { + $search = 'extensions'; + $enable = 'extension'; + } else { + $search = 'zend_extensions'; + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('Thread Safety.+enabled', $info) ? '_ts' : ''; + $enable = 'zend_extension' . $debug . $ts; + } + $found = false; + foreach ($ini[$search] as $line => $extension) { + if (in_array($extension, $binaries, true) || in_array( + $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) { + $found = true; + break; + } + } + if (!$found) { + // not enabled + return true; + } + $fp = @fopen($phpini, 'wb'); + if (!$fp) { + return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing'); + } + if ($line) { + $newini = array_slice($ini['all'], 0, $line); + // delete the enable line + $newini = array_merge($newini, array_slice($ini['all'], $line + 1)); + } else { + $newini = array_slice($ini['all'], 1); + } + foreach ($newini as $line) { + fwrite($fp, $line); + } + fclose($fp); + return true; + } + + function _parseIni($filename) + { + if (!file_exists($filename)) { + return PEAR::raiseError('php.ini "' . $filename . '" does not exist'); + } + + if (filesize($filename) > 300000) { + return PEAR::raiseError('php.ini "' . $filename . '" is too large, aborting'); + } + + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; + $zend_extension_line = 'zend_extension' . $debug . $ts; + $all = @file($filename); + if (!$all) { + return PEAR::raiseError('php.ini "' . $filename .'" could not be read'); + } + $zend_extensions = $extensions = array(); + // assume this is right, but pull from the php.ini if it is found + $extension_dir = ini_get('extension_dir'); + foreach ($all as $linenum => $line) { + $line = trim($line); + if (!$line) { + continue; + } + if ($line[0] == ';') { + continue; + } + if (strtolower(substr($line, 0, 13)) == 'extension_dir') { + $line = trim(substr($line, 13)); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $extension_dir = str_replace('"', '', array_shift($x)); + continue; + } + } + if (strtolower(substr($line, 0, 9)) == 'extension') { + $line = trim(substr($line, 9)); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $extensions[$linenum] = str_replace('"', '', array_shift($x)); + continue; + } + } + if (strtolower(substr($line, 0, strlen($zend_extension_line))) == + $zend_extension_line) { + $line = trim(substr($line, strlen($zend_extension_line))); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $zend_extensions[$linenum] = str_replace('"', '', array_shift($x)); + continue; + } + } + } + return array( + 'extensions' => $extensions, + 'zend_extensions' => $zend_extensions, + 'extension_dir' => $extension_dir, + 'all' => $all, + ); + } + + // {{{ doInstall() + + function doInstall($command, $options, $params) + { + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + if (isset($options['installroot']) && isset($options['packagingroot'])) { + return $this->raiseError('ERROR: cannot use both --installroot and --packagingroot'); + } + + $reg = &$this->config->getRegistry(); + $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel'); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + if (empty($this->installer)) { + $this->installer = &$this->getInstaller($this->ui); + } + + if ($command == 'upgrade' || $command == 'upgrade-all') { + // If people run the upgrade command but pass nothing, emulate a upgrade-all + if ($command == 'upgrade' && empty($params)) { + return $this->doUpgradeAll($command, $options, $params); + } + $options['upgrade'] = true; + } else { + $packages = $params; + } + + $instreg = &$reg; // instreg used to check if package is installed + if (isset($options['packagingroot']) && !isset($options['upgrade'])) { + $packrootphp_dir = $this->installer->_prependPath( + $this->config->get('php_dir', null, 'pear.php.net'), + $options['packagingroot']); + $instreg = new PEAR_Registry($packrootphp_dir); // other instreg! + + if ($this->config->get('verbose') > 2) { + $this->ui->outputData('using package root: ' . $options['packagingroot']); + } + } + + $abstractpackages = $otherpackages = array(); + // parse params + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + + foreach ($params as $param) { + if (strpos($param, 'http://') === 0) { + $otherpackages[] = $param; + continue; + } + + if (strpos($param, 'channel://') === false && @file_exists($param)) { + if (isset($options['force'])) { + $otherpackages[] = $param; + continue; + } + + $pkg = new PEAR_PackageFile($this->config); + $pf = $pkg->fromAnyFile($param, PEAR_VALIDATE_DOWNLOADING); + if (PEAR::isError($pf)) { + $otherpackages[] = $param; + continue; + } + + $exists = $reg->packageExists($pf->getPackage(), $pf->getChannel()); + $pversion = $reg->packageInfo($pf->getPackage(), 'version', $pf->getChannel()); + $version_compare = version_compare($pf->getVersion(), $pversion, '<='); + if ($exists && $version_compare) { + if ($this->config->get('verbose')) { + $this->ui->outputData('Ignoring installed package ' . + $reg->parsedPackageNameToString( + array('package' => $pf->getPackage(), + 'channel' => $pf->getChannel()), true)); + } + continue; + } + $otherpackages[] = $param; + continue; + } + + $e = $reg->parsePackageName($param, $channel); + if (PEAR::isError($e)) { + $otherpackages[] = $param; + } else { + $abstractpackages[] = $e; + } + } + PEAR::staticPopErrorHandling(); + + // if there are any local package .tgz or remote static url, we can't + // filter. The filter only works for abstract packages + if (count($abstractpackages) && !isset($options['force'])) { + // when not being forced, only do necessary upgrades/installs + if (isset($options['upgrade'])) { + $abstractpackages = $this->_filterUptodatePackages($abstractpackages, $command); + } else { + $count = count($abstractpackages); + foreach ($abstractpackages as $i => $package) { + if (isset($package['group'])) { + // do not filter out install groups + continue; + } + + if ($instreg->packageExists($package['package'], $package['channel'])) { + if ($count > 1) { + if ($this->config->get('verbose')) { + $this->ui->outputData('Ignoring installed package ' . + $reg->parsedPackageNameToString($package, true)); + } + unset($abstractpackages[$i]); + } elseif ($count === 1) { + // Lets try to upgrade it since it's already installed + $options['upgrade'] = true; + } + } + } + } + $abstractpackages = + array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages); + } elseif (count($abstractpackages)) { + $abstractpackages = + array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages); + } + + $packages = array_merge($abstractpackages, $otherpackages); + if (!count($packages)) { + $c = ''; + if (isset($options['channel'])){ + $c .= ' in channel "' . $options['channel'] . '"'; + } + $this->ui->outputData('Nothing to ' . $command . $c); + return true; + } + + $this->downloader = &$this->getDownloader($this->ui, $options, $this->config); + $errors = $downloaded = $binaries = array(); + $downloaded = &$this->downloader->download($packages); + if (PEAR::isError($downloaded)) { + return $this->raiseError($downloaded); + } + + $errors = $this->downloader->getErrorMsgs(); + if (count($errors)) { + $err = array(); + $err['data'] = array(); + foreach ($errors as $error) { + if ($error !== null) { + $err['data'][] = array($error); + } + } + + if (!empty($err['data'])) { + $err['headline'] = 'Install Errors'; + $this->ui->outputData($err); + } + + if (!count($downloaded)) { + return $this->raiseError("$command failed"); + } + } + + $data = array( + 'headline' => 'Packages that would be Installed' + ); + + if (isset($options['pretend'])) { + foreach ($downloaded as $package) { + $data['data'][] = array($reg->parsedPackageNameToString($package->getParsedPackage())); + } + $this->ui->outputData($data, 'pretend'); + return true; + } + + $this->installer->setOptions($options); + $this->installer->sortPackagesForInstall($downloaded); + if (PEAR::isError($err = $this->installer->setDownloadedPackages($downloaded))) { + $this->raiseError($err->getMessage()); + return true; + } + + $binaries = $extrainfo = array(); + foreach ($downloaded as $param) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->install($param, $options); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($info)) { + $oldinfo = $info; + $pkg = &$param->getPackageFile(); + if ($info->getCode() != PEAR_INSTALLER_NOBINARY) { + if (!($info = $pkg->installBinary($this->installer))) { + $this->ui->outputData('ERROR: ' .$oldinfo->getMessage()); + continue; + } + + // we just installed a different package than requested, + // let's change the param and info so that the rest of this works + $param = $info[0]; + $info = $info[1]; + } + } + + if (!is_array($info)) { + return $this->raiseError("$command failed"); + } + + if ($param->getPackageType() == 'extsrc' || + $param->getPackageType() == 'extbin' || + $param->getPackageType() == 'zendextsrc' || + $param->getPackageType() == 'zendextbin') { + $pkg = &$param->getPackageFile(); + if ($instbin = $pkg->getInstalledBinary()) { + $instpkg = &$instreg->getPackage($instbin, $pkg->getChannel()); + } else { + $instpkg = &$instreg->getPackage($pkg->getPackage(), $pkg->getChannel()); + } + + foreach ($instpkg->getFilelist() as $name => $atts) { + $pinfo = pathinfo($atts['installed_as']); + if (!isset($pinfo['extension']) || + in_array($pinfo['extension'], array('c', 'h'))) { + continue; // make sure we don't match php_blah.h + } + + if ((strpos($pinfo['basename'], 'php_') === 0 && + $pinfo['extension'] == 'dll') || + // most unices + $pinfo['extension'] == 'so' || + // hp-ux + $pinfo['extension'] == 'sl') { + $binaries[] = array($atts['installed_as'], $pinfo); + break; + } + } + + if (count($binaries)) { + foreach ($binaries as $pinfo) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->enableExtension(array($pinfo[0]), $param->getPackageType()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($ret)) { + $extrainfo[] = $ret->getMessage(); + if ($param->getPackageType() == 'extsrc' || + $param->getPackageType() == 'extbin') { + $exttype = 'extension'; + } else { + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('Thread Safety.+enabled', $info) ? '_ts' : ''; + $exttype = 'zend_extension' . $debug . $ts; + } + $extrainfo[] = 'You should add "' . $exttype . '=' . + $pinfo[1]['basename'] . '" to php.ini'; + } else { + $extrainfo[] = 'Extension ' . $instpkg->getProvidesExtension() . + ' enabled in php.ini'; + } + } + } + } + + if ($this->config->get('verbose') > 0) { + $chan = $param->getChannel(); + $label = $reg->parsedPackageNameToString( + array( + 'channel' => $chan, + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + )); + $out = array('data' => "$command ok: $label"); + if (isset($info['release_warnings'])) { + $out['release_warnings'] = $info['release_warnings']; + } + $this->ui->outputData($out, $command); + + if (!isset($options['register-only']) && !isset($options['offline'])) { + if ($this->config->isDefinedLayer('ftp')) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->ftpInstall($param); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($info)) { + $this->ui->outputData($info->getMessage()); + $this->ui->outputData("remote install failed: $label"); + } else { + $this->ui->outputData("remote install ok: $label"); + } + } + } + } + + $deps = $param->getDeps(); + if ($deps) { + if (isset($deps['group'])) { + $groups = $deps['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + + foreach ($groups as $group) { + if ($group['attribs']['name'] == 'default') { + // default group is always installed, unless the user + // explicitly chooses to install another group + continue; + } + $extrainfo[] = $param->getPackage() . ': Optional feature ' . + $group['attribs']['name'] . ' available (' . + $group['attribs']['hint'] . ')'; + } + + $extrainfo[] = $param->getPackage() . + ': To install optional features use "pear install ' . + $reg->parsedPackageNameToString( + array('package' => $param->getPackage(), + 'channel' => $param->getChannel()), true) . + '#featurename"'; + } + } + + $pkg = &$instreg->getPackage($param->getPackage(), $param->getChannel()); + // $pkg may be NULL if install is a 'fake' install via --packagingroot + if (is_object($pkg)) { + $pkg->setConfig($this->config); + if ($list = $pkg->listPostinstallScripts()) { + $pn = $reg->parsedPackageNameToString(array('channel' => + $param->getChannel(), 'package' => $param->getPackage()), true); + $extrainfo[] = $pn . ' has post-install scripts:'; + foreach ($list as $file) { + $extrainfo[] = $file; + } + $extrainfo[] = $param->getPackage() . + ': Use "pear run-scripts ' . $pn . '" to finish setup.'; + $extrainfo[] = 'DO NOT RUN SCRIPTS FROM UNTRUSTED SOURCES'; + } + } + } + + if (count($extrainfo)) { + foreach ($extrainfo as $info) { + $this->ui->outputData($info); + } + } + + return true; + } + + // }}} + // {{{ doUpgradeAll() + + function doUpgradeAll($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $upgrade = array(); + + if (isset($options['channel'])) { + $channels = array($options['channel']); + } else { + $channels = $reg->listChannels(); + } + + foreach ($channels as $channel) { + if ($channel == '__uri') { + continue; + } + + // parse name with channel + foreach ($reg->listPackages($channel) as $name) { + $upgrade[] = $reg->parsedPackageNameToString(array( + 'channel' => $channel, + 'package' => $name + )); + } + } + + $err = $this->doInstall($command, $options, $upgrade); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + } + } + + // }}} + // {{{ doUninstall() + + function doUninstall($command, $options, $params) + { + if (count($params) < 1) { + return $this->raiseError("Please supply the package(s) you want to uninstall"); + } + + if (empty($this->installer)) { + $this->installer = &$this->getInstaller($this->ui); + } + + if (isset($options['remoteconfig'])) { + $e = $this->config->readFTPConfigFile($options['remoteconfig']); + if (!PEAR::isError($e)) { + $this->installer->setConfig($this->config); + } + } + + $reg = &$this->config->getRegistry(); + $newparams = array(); + $binaries = array(); + $badparams = array(); + foreach ($params as $pkg) { + $channel = $this->config->get('default_channel'); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($pkg, $channel); + PEAR::staticPopErrorHandling(); + if (!$parsed || PEAR::isError($parsed)) { + $badparams[] = $pkg; + continue; + } + $package = $parsed['package']; + $channel = $parsed['channel']; + $info = &$reg->getPackage($package, $channel); + if ($info === null && + ($channel == 'pear.php.net' || $channel == 'pecl.php.net')) { + // make sure this isn't a package that has flipped from pear to pecl but + // used a package.xml 1.0 + $testc = ($channel == 'pear.php.net') ? 'pecl.php.net' : 'pear.php.net'; + $info = &$reg->getPackage($package, $testc); + if ($info !== null) { + $channel = $testc; + } + } + if ($info === null) { + $badparams[] = $pkg; + } else { + $newparams[] = &$info; + // check for binary packages (this is an alias for those packages if so) + if ($installedbinary = $info->getInstalledBinary()) { + $this->ui->log('adding binary package ' . + $reg->parsedPackageNameToString(array('channel' => $channel, + 'package' => $installedbinary), true)); + $newparams[] = &$reg->getPackage($installedbinary, $channel); + } + // add the contents of a dependency group to the list of installed packages + if (isset($parsed['group'])) { + $group = $info->getDependencyGroup($parsed['group']); + if ($group) { + $installed = $reg->getInstalledGroup($group); + if ($installed) { + foreach ($installed as $i => $p) { + $newparams[] = &$installed[$i]; + } + } + } + } + } + } + $err = $this->installer->sortPackagesForUninstall($newparams); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + return true; + } + $params = $newparams; + // twist this to use it to check on whether dependent packages are also being uninstalled + // for circular dependencies like subpackages + $this->installer->setUninstallPackages($newparams); + $params = array_merge($params, $badparams); + $binaries = array(); + foreach ($params as $pkg) { + $this->installer->pushErrorHandling(PEAR_ERROR_RETURN); + if ($err = $this->installer->uninstall($pkg, $options)) { + $this->installer->popErrorHandling(); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + continue; + } + if ($pkg->getPackageType() == 'extsrc' || + $pkg->getPackageType() == 'extbin' || + $pkg->getPackageType() == 'zendextsrc' || + $pkg->getPackageType() == 'zendextbin') { + if ($instbin = $pkg->getInstalledBinary()) { + continue; // this will be uninstalled later + } + + foreach ($pkg->getFilelist() as $name => $atts) { + $pinfo = pathinfo($atts['installed_as']); + if (!isset($pinfo['extension']) || + in_array($pinfo['extension'], array('c', 'h'))) { + continue; // make sure we don't match php_blah.h + } + if ((strpos($pinfo['basename'], 'php_') === 0 && + $pinfo['extension'] == 'dll') || + // most unices + $pinfo['extension'] == 'so' || + // hp-ux + $pinfo['extension'] == 'sl') { + $binaries[] = array($atts['installed_as'], $pinfo); + break; + } + } + if (count($binaries)) { + foreach ($binaries as $pinfo) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->disableExtension(array($pinfo[0]), $pkg->getPackageType()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($ret)) { + $extrainfo[] = $ret->getMessage(); + if ($pkg->getPackageType() == 'extsrc' || + $pkg->getPackageType() == 'extbin') { + $exttype = 'extension'; + } else { + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('Thread Safety.+enabled', $info) ? '_ts' : ''; + $exttype = 'zend_extension' . $debug . $ts; + } + $this->ui->outputData('Unable to remove "' . $exttype . '=' . + $pinfo[1]['basename'] . '" from php.ini', $command); + } else { + $this->ui->outputData('Extension ' . $pkg->getProvidesExtension() . + ' disabled in php.ini', $command); + } + } + } + } + $savepkg = $pkg; + if ($this->config->get('verbose') > 0) { + if (is_object($pkg)) { + $pkg = $reg->parsedPackageNameToString($pkg); + } + $this->ui->outputData("uninstall ok: $pkg", $command); + } + if (!isset($options['offline']) && is_object($savepkg) && + defined('PEAR_REMOTEINSTALL_OK')) { + if ($this->config->isDefinedLayer('ftp')) { + $this->installer->pushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->ftpUninstall($savepkg); + $this->installer->popErrorHandling(); + if (PEAR::isError($info)) { + $this->ui->outputData($info->getMessage()); + $this->ui->outputData("remote uninstall failed: $pkg"); + } else { + $this->ui->outputData("remote uninstall ok: $pkg"); + } + } + } + } else { + $this->installer->popErrorHandling(); + if (!is_object($pkg)) { + return $this->raiseError("uninstall failed: $pkg"); + } + $pkg = $reg->parsedPackageNameToString($pkg); + } + } + + return true; + } + + // }}} + + + // }}} + // {{{ doBundle() + /* + (cox) It just downloads and untars the package, does not do + any check that the PEAR_Installer::_installFile() does. + */ + + function doBundle($command, $options, $params) + { + $opts = array( + 'force' => true, + 'nodeps' => true, + 'soft' => true, + 'downloadonly' => true + ); + $downloader = &$this->getDownloader($this->ui, $opts, $this->config); + $reg = &$this->config->getRegistry(); + if (count($params) < 1) { + return $this->raiseError("Please supply the package you want to bundle"); + } + + if (isset($options['destination'])) { + if (!is_dir($options['destination'])) { + System::mkdir('-p ' . $options['destination']); + } + $dest = realpath($options['destination']); + } else { + $pwd = getcwd(); + $dir = $pwd . DIRECTORY_SEPARATOR . 'ext'; + $dest = is_dir($dir) ? $dir : $pwd; + } + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $downloader->setDownloadDir($dest); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($err)) { + return PEAR::raiseError('download directory "' . $dest . + '" is not writeable.'); + } + $result = &$downloader->download(array($params[0])); + if (PEAR::isError($result)) { + return $result; + } + if (!isset($result[0])) { + return $this->raiseError('unable to unpack ' . $params[0]); + } + $pkgfile = &$result[0]->getPackageFile(); + $pkgname = $pkgfile->getName(); + $pkgversion = $pkgfile->getVersion(); + + // Unpacking ------------------------------------------------- + $dest .= DIRECTORY_SEPARATOR . $pkgname; + $orig = $pkgname . '-' . $pkgversion; + + $tar = &new Archive_Tar($pkgfile->getArchiveFile()); + if (!$tar->extractModify($dest, $orig)) { + return $this->raiseError('unable to unpack ' . $pkgfile->getArchiveFile()); + } + $this->ui->outputData("Package ready at '$dest'"); + // }}} + } + + // }}} + + function doRunScripts($command, $options, $params) + { + if (!isset($params[0])) { + return $this->raiseError('run-scripts expects 1 parameter: a package name'); + } + + $reg = &$this->config->getRegistry(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($parsed)) { + return $this->raiseError($parsed); + } + + $package = &$reg->getPackage($parsed['package'], $parsed['channel']); + if (!is_object($package)) { + return $this->raiseError('Could not retrieve package "' . $params[0] . '" from registry'); + } + + $package->setConfig($this->config); + $package->runPostinstallScripts(); + $this->ui->outputData('Install scripts complete', $command); + return true; + } + + /** + * Given a list of packages, filter out those ones that are already up to date + * + * @param $packages: packages, in parsed array format ! + * @return list of packages that can be upgraded + */ + function _filterUptodatePackages($packages, $command) + { + $reg = &$this->config->getRegistry(); + $latestReleases = array(); + + $ret = array(); + foreach ($packages as $package) { + if (isset($package['group'])) { + $ret[] = $package; + continue; + } + + $channel = $package['channel']; + $name = $package['package']; + if (!$reg->packageExists($name, $channel)) { + $ret[] = $package; + continue; + } + + if (!isset($latestReleases[$channel])) { + // fill in cache for this channel + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + $base2 = false; + $preferred_mirror = $this->config->get('preferred_mirror', null, $channel); + if ($chan->supportsREST($preferred_mirror) && + ( + //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) || + ($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + ) { + $dorest = true; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!isset($package['state'])) { + $state = $this->config->get('preferred_state', null, $channel); + } else { + $state = $package['state']; + } + + if ($dorest) { + if ($base2) { + $rest = &$this->config->getREST('1.4', array()); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', array()); + } + + $installed = array_flip($reg->listPackages($channel)); + $latest = $rest->listLatestUpgrades($base, $state, $installed, $channel, $reg); + } + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($latest)) { + $this->ui->outputData('Error getting channel info from ' . $channel . + ': ' . $latest->getMessage()); + continue; + } + + $latestReleases[$channel] = array_change_key_case($latest); + } + + // check package for latest release + $name_lower = strtolower($name); + if (isset($latestReleases[$channel][$name_lower])) { + // if not set, up to date + $inst_version = $reg->packageInfo($name, 'version', $channel); + $channel_version = $latestReleases[$channel][$name_lower]['version']; + if (version_compare($channel_version, $inst_version, 'le')) { + // installed version is up-to-date + continue; + } + + // maintain BC + if ($command == 'upgrade-all') { + $this->ui->outputData(array('data' => 'Will upgrade ' . + $reg->parsedPackageNameToString($package)), $command); + } + $ret[] = $package; + } + } + + return $ret; + } +}PEAR-1.9.0/PEAR/Command/Mirror.xml100664 764 764 1151 100664 11405 + + Downloads each available package from the default channel + doDownloadAll + da + + + c + specify a channel other than the default channel + CHAN + + + +Requests a list of available packages from the default channel ({config default_channel}) +and downloads them to current working directory. Note: only +packages within preferred_state ({config preferred_state}) will be downloaded + +PEAR-1.9.0/PEAR/Command/Mirror.php100664 764 764 10762 100664 11424 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Mirror.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.2.0 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for providing file mirrors + * + * @category pear + * @package PEAR + * @author Alexander Merz + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.2.0 + */ +class PEAR_Command_Mirror extends PEAR_Command_Common +{ + var $commands = array( + 'download-all' => array( + 'summary' => 'Downloads each available package from the default channel', + 'function' => 'doDownloadAll', + 'shortcut' => 'da', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + ), + 'doc' => ' +Requests a list of available packages from the default channel ({config default_channel}) +and downloads them to current working directory. Note: only +packages within preferred_state ({config preferred_state}) will be downloaded' + ), + ); + + /** + * PEAR_Command_Mirror constructor. + * + * @access public + * @param object PEAR_Frontend a reference to an frontend + * @param object PEAR_Config a reference to the configuration data + */ + function PEAR_Command_Mirror(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + /** + * For unit-testing + */ + function &factory($a) + { + $a = &PEAR_Command::factory($a, $this->config); + return $a; + } + + /** + * retrieves a list of avaible Packages from master server + * and downloads them + * + * @access public + * @param string $command the command + * @param array $options the command options before the command + * @param array $params the stuff after the command name + * @return bool true if succesful + * @throw PEAR_Error + */ + function doDownloadAll($command, $options, $params) + { + $savechannel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + $channel = isset($options['channel']) ? $options['channel'] : + $this->config->get('default_channel'); + if (!$reg->channelExists($channel)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + $this->config->set('default_channel', $channel); + + $this->ui->outputData('Using Channel ' . $this->config->get('default_channel')); + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $remoteInfo = array_flip($rest->listPackages($base, $channel)); + } + + if (PEAR::isError($remoteInfo)) { + return $remoteInfo; + } + + $cmd = &$this->factory("download"); + if (PEAR::isError($cmd)) { + return $cmd; + } + + $this->ui->outputData('Using Preferred State of ' . + $this->config->get('preferred_state')); + $this->ui->outputData('Gathering release information, please wait...'); + + /** + * Error handling not necessary, because already done by + * the download command + */ + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $cmd->run('download', array('downloadonly' => true), array_keys($remoteInfo)); + PEAR::staticPopErrorHandling(); + $this->config->set('default_channel', $savechannel); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage()); + } + + return true; + } +}PEAR-1.9.0/PEAR/Command/Package.xml100664 764 764 16066 100664 11521 + + Build Package + doPackage + p + + + Z + Do not gzip the package file + + + n + Print the name of the packaged file. + + + [descfile] [descfile2] +Creates a PEAR package from its description file (usually called +package.xml). If a second packagefile is passed in, then +the packager will check to make sure that one is a package.xml +version 1.0, and the other is a package.xml version 2.0. The +package.xml version 1.0 will be saved as "package.xml" in the archive, +and the other as "package2.xml" in the archive" + + + + Validate Package Consistency + doPackageValidate + pv + + + + + + Run a "cvs diff" for all files in a package + doCvsDiff + cd + + + q + Be quiet + + + Q + Be really quiet + + + D + Diff against revision of DATE + DATE + + + R + Diff against tag for package release REL + REL + + + r + Diff against revision REV + REV + + + c + Generate context diff + + + u + Generate unified diff + + + i + Ignore case, consider upper- and lower-case letters equivalent + + + b + Ignore changes in amount of white space + + + B + Ignore changes that insert or delete blank lines + + + + Report only whether the files differ, no details + + + n + Don't do anything, just pretend + + + <package.xml> +Compares all the files in a package. Without any options, this +command will compare the current code with the last checked-in code. +Using the -r or -R option you may compare the current code with that +of a specific release. + + + + Set SVN Release Tag + doSvnTag + sv + + + q + Be quiet + + + F + Move (slide) tag if it exists + + + d + Remove tag + + + n + Don't do anything, just pretend + + + <package.xml> [files...] + Sets a SVN tag on all files in a package. Use this command after you have + packaged a distribution tarball with the "package" command to tag what + revisions of what files were in that release. If need to fix something + after running cvstag once, but before the tarball is released to the public, + use the "slide" option to move the release tag. + + to include files (such as a second package.xml, or tests not included in the + release), pass them as additional parameters. + + + + Set CVS Release Tag + doCvsTag + ct + + + q + Be quiet + + + Q + Be really quiet + + + F + Move (slide) tag if it exists + + + d + Remove tag + + + n + Don't do anything, just pretend + + + <package.xml> [files...] +Sets a CVS tag on all files in a package. Use this command after you have +packaged a distribution tarball with the "package" command to tag what +revisions of what files were in that release. If need to fix something +after running cvstag once, but before the tarball is released to the public, +use the "slide" option to move the release tag. + +to include files (such as a second package.xml, or tests not included in the +release), pass them as additional parameters. + + + + Show package dependencies + doPackageDependencies + pd + + <package-file> or <package.xml> or <install-package-name> +List all dependencies the package has. +Can take a tgz / tar file, package.xml or a package name of an installed package. + + + Sign a package distribution file + doSign + si + + + v + Display GnuPG output + + + <package-file> +Signs a package distribution (.tar or .tgz) file with GnuPG. + + + Builds an RPM spec file from a PEAR package + doMakeRPM + rpm + + + t + Use FILE as RPM spec file template + FILE + + + p + Use FORMAT as format string for RPM package name, %s is replaced +by the PEAR package name, defaults to "PEAR::%s". + FORMAT + + + <package-file> + +Creates an RPM .spec file for wrapping a PEAR package inside an RPM +package. Intended to be used from the SPECS directory, with the PEAR +package tarball in the SOURCES directory: + +$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz +Wrote RPM spec file PEAR::Net_Geo-1.0.spec +$ rpm -bb PEAR::Net_Socket-1.0.spec +... +Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm + + + + Convert a package.xml 1.0 to package.xml 2.0 format + doConvert + c2 + + + f + do not beautify the filelist. + + + [descfile] [descfile2] +Converts a package.xml in 1.0 format into a package.xml +in 2.0 format. The new file will be named package2.xml by default, +and package.xml will be used as the old file by default. +This is not the most intelligent conversion, and should only be +used for automated conversion or learning the format. + + +PEAR-1.9.0/PEAR/Command/Package.php100664 764 764 114657 100664 11535 + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Package.php 287559 2009-08-21 22:33:10Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ + +class PEAR_Command_Package extends PEAR_Command_Common +{ + var $commands = array( + 'package' => array( + 'summary' => 'Build Package', + 'function' => 'doPackage', + 'shortcut' => 'p', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'Do not gzip the package file' + ), + 'showname' => array( + 'shortopt' => 'n', + 'doc' => 'Print the name of the packaged file.', + ), + ), + 'doc' => '[descfile] [descfile2] +Creates a PEAR package from its description file (usually called +package.xml). If a second packagefile is passed in, then +the packager will check to make sure that one is a package.xml +version 1.0, and the other is a package.xml version 2.0. The +package.xml version 1.0 will be saved as "package.xml" in the archive, +and the other as "package2.xml" in the archive" +' + ), + 'package-validate' => array( + 'summary' => 'Validate Package Consistency', + 'function' => 'doPackageValidate', + 'shortcut' => 'pv', + 'options' => array(), + 'doc' => ' +', + ), + 'cvsdiff' => array( + 'summary' => 'Run a "cvs diff" for all files in a package', + 'function' => 'doCvsDiff', + 'shortcut' => 'cd', + 'options' => array( + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Be quiet', + ), + 'reallyquiet' => array( + 'shortopt' => 'Q', + 'doc' => 'Be really quiet', + ), + 'date' => array( + 'shortopt' => 'D', + 'doc' => 'Diff against revision of DATE', + 'arg' => 'DATE', + ), + 'release' => array( + 'shortopt' => 'R', + 'doc' => 'Diff against tag for package release REL', + 'arg' => 'REL', + ), + 'revision' => array( + 'shortopt' => 'r', + 'doc' => 'Diff against revision REV', + 'arg' => 'REV', + ), + 'context' => array( + 'shortopt' => 'c', + 'doc' => 'Generate context diff', + ), + 'unified' => array( + 'shortopt' => 'u', + 'doc' => 'Generate unified diff', + ), + 'ignore-case' => array( + 'shortopt' => 'i', + 'doc' => 'Ignore case, consider upper- and lower-case letters equivalent', + ), + 'ignore-whitespace' => array( + 'shortopt' => 'b', + 'doc' => 'Ignore changes in amount of white space', + ), + 'ignore-blank-lines' => array( + 'shortopt' => 'B', + 'doc' => 'Ignore changes that insert or delete blank lines', + ), + 'brief' => array( + 'doc' => 'Report only whether the files differ, no details', + ), + 'dry-run' => array( + 'shortopt' => 'n', + 'doc' => 'Don\'t do anything, just pretend', + ), + ), + 'doc' => ' +Compares all the files in a package. Without any options, this +command will compare the current code with the last checked-in code. +Using the -r or -R option you may compare the current code with that +of a specific release. +', + ), + 'svntag' => array( + 'summary' => 'Set SVN Release Tag', + 'function' => 'doSvnTag', + 'shortcut' => 'sv', + 'options' => array( + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Be quiet', + ), + 'slide' => array( + 'shortopt' => 'F', + 'doc' => 'Move (slide) tag if it exists', + ), + 'delete' => array( + 'shortopt' => 'd', + 'doc' => 'Remove tag', + ), + 'dry-run' => array( + 'shortopt' => 'n', + 'doc' => 'Don\'t do anything, just pretend', + ), + ), + 'doc' => ' [files...] + Sets a SVN tag on all files in a package. Use this command after you have + packaged a distribution tarball with the "package" command to tag what + revisions of what files were in that release. If need to fix something + after running cvstag once, but before the tarball is released to the public, + use the "slide" option to move the release tag. + + to include files (such as a second package.xml, or tests not included in the + release), pass them as additional parameters. + ', + ), + 'cvstag' => array( + 'summary' => 'Set CVS Release Tag', + 'function' => 'doCvsTag', + 'shortcut' => 'ct', + 'options' => array( + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Be quiet', + ), + 'reallyquiet' => array( + 'shortopt' => 'Q', + 'doc' => 'Be really quiet', + ), + 'slide' => array( + 'shortopt' => 'F', + 'doc' => 'Move (slide) tag if it exists', + ), + 'delete' => array( + 'shortopt' => 'd', + 'doc' => 'Remove tag', + ), + 'dry-run' => array( + 'shortopt' => 'n', + 'doc' => 'Don\'t do anything, just pretend', + ), + ), + 'doc' => ' [files...] +Sets a CVS tag on all files in a package. Use this command after you have +packaged a distribution tarball with the "package" command to tag what +revisions of what files were in that release. If need to fix something +after running cvstag once, but before the tarball is released to the public, +use the "slide" option to move the release tag. + +to include files (such as a second package.xml, or tests not included in the +release), pass them as additional parameters. +', + ), + 'package-dependencies' => array( + 'summary' => 'Show package dependencies', + 'function' => 'doPackageDependencies', + 'shortcut' => 'pd', + 'options' => array(), + 'doc' => ' or or +List all dependencies the package has. +Can take a tgz / tar file, package.xml or a package name of an installed package.' + ), + 'sign' => array( + 'summary' => 'Sign a package distribution file', + 'function' => 'doSign', + 'shortcut' => 'si', + 'options' => array( + 'verbose' => array( + 'shortopt' => 'v', + 'doc' => 'Display GnuPG output', + ), + ), + 'doc' => ' +Signs a package distribution (.tar or .tgz) file with GnuPG.', + ), + 'makerpm' => array( + 'summary' => 'Builds an RPM spec file from a PEAR package', + 'function' => 'doMakeRPM', + 'shortcut' => 'rpm', + 'options' => array( + 'spec-template' => array( + 'shortopt' => 't', + 'arg' => 'FILE', + 'doc' => 'Use FILE as RPM spec file template' + ), + 'rpm-pkgname' => array( + 'shortopt' => 'p', + 'arg' => 'FORMAT', + 'doc' => 'Use FORMAT as format string for RPM package name, %s is replaced +by the PEAR package name, defaults to "PEAR::%s".', + ), + ), + 'doc' => ' + +Creates an RPM .spec file for wrapping a PEAR package inside an RPM +package. Intended to be used from the SPECS directory, with the PEAR +package tarball in the SOURCES directory: + +$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz +Wrote RPM spec file PEAR::Net_Geo-1.0.spec +$ rpm -bb PEAR::Net_Socket-1.0.spec +... +Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm +', + ), + 'convert' => array( + 'summary' => 'Convert a package.xml 1.0 to package.xml 2.0 format', + 'function' => 'doConvert', + 'shortcut' => 'c2', + 'options' => array( + 'flat' => array( + 'shortopt' => 'f', + 'doc' => 'do not beautify the filelist.', + ), + ), + 'doc' => '[descfile] [descfile2] +Converts a package.xml in 1.0 format into a package.xml +in 2.0 format. The new file will be named package2.xml by default, +and package.xml will be used as the old file by default. +This is not the most intelligent conversion, and should only be +used for automated conversion or learning the format. +' + ), + ); + + var $output; + + /** + * PEAR_Command_Package constructor. + * + * @access public + */ + function PEAR_Command_Package(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function _displayValidationResults($err, $warn, $strict = false) + { + foreach ($err as $e) { + $this->output .= "Error: $e\n"; + } + foreach ($warn as $w) { + $this->output .= "Warning: $w\n"; + } + $this->output .= sprintf('Validation: %d error(s), %d warning(s)'."\n", + sizeof($err), sizeof($warn)); + if ($strict && count($err) > 0) { + $this->output .= "Fix these errors and try again."; + return false; + } + return true; + } + + function &getPackager() + { + if (!class_exists('PEAR_Packager')) { + require_once 'PEAR/Packager.php'; + } + $a = &new PEAR_Packager; + return $a; + } + + function &getPackageFile($config, $debug = false, $tmpdir = null) + { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + $a = &new PEAR_PackageFile($config, $debug, $tmpdir); + $common = new PEAR_Common; + $common->ui = $this->ui; + $a->setLogger($common); + return $a; + } + + function doPackage($command, $options, $params) + { + $this->output = ''; + $pkginfofile = isset($params[0]) ? $params[0] : 'package.xml'; + $pkg2 = isset($params[1]) ? $params[1] : null; + if (!$pkg2 && !isset($params[0]) && file_exists('package2.xml')) { + $pkg2 = 'package2.xml'; + } + + $packager = &$this->getPackager(); + $compress = empty($options['nocompress']) ? true : false; + $result = $packager->package($pkginfofile, $compress, $pkg2); + if (PEAR::isError($result)) { + return $this->raiseError($result); + } + + // Don't want output, only the package file name just created + if (isset($options['showname'])) { + $this->output = $result; + } + + if ($this->output) { + $this->ui->outputData($this->output, $command); + } + + return true; + } + + function doPackageValidate($command, $options, $params) + { + $this->output = ''; + if (count($params) < 1) { + $params[0] = 'package.xml'; + } + + $obj = &$this->getPackageFile($this->config, $this->_debug); + $obj->rawReturn(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + $info = $obj->fromPackageFile($params[0], PEAR_VALIDATE_NORMAL); + } else { + $archive = $info->getArchiveFile(); + $tar = &new Archive_Tar($archive); + $tar->extract(dirname($info->getPackageFile())); + $info->setPackageFile(dirname($info->getPackageFile()) . DIRECTORY_SEPARATOR . + $info->getPackage() . '-' . $info->getVersion() . DIRECTORY_SEPARATOR . + basename($info->getPackageFile())); + } + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $valid = false; + if ($info->getPackagexmlVersion() == '2.0') { + if ($valid = $info->validate(PEAR_VALIDATE_NORMAL)) { + $info->flattenFileList(); + $valid = $info->validate(PEAR_VALIDATE_PACKAGING); + } + } else { + $valid = $info->validate(PEAR_VALIDATE_PACKAGING); + } + + $err = $warn = array(); + if ($errors = $info->getValidationWarnings()) { + foreach ($errors as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + $this->_displayValidationResults($err, $warn); + $this->ui->outputData($this->output, $command); + return true; + } + + function doSvnTag($command, $options, $params) + { + $this->output = ''; + $_cmd = $command; + if (count($params) < 1) { + $help = $this->getHelp($command); + return $this->raiseError("$command: missing parameter: $help[0]"); + } + + $packageFile = realpath($params[0]); + $dir = dirname($packageFile); + $dir = substr($dir, strrpos($dir, '/') + 1); + $obj = &$this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $err = $warn = array(); + if (!$info->validate()) { + foreach ($info->getValidationWarnings() as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + if (!$this->_displayValidationResults($err, $warn, true)) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('SVN tag failed'); + } + + $version = $info->getVersion(); + $package = $info->getName(); + $svntag = "$package-$version"; + + if (isset($options['delete'])) { + return $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options); + } + + $path = $this->_svnFindPath($packageFile); + + // Check if there are any modified files + $fp = popen('svn st --xml ' . dirname($packageFile), "r"); + $out = ''; + while ($line = fgets($fp, 1024)) { + $out .= rtrim($line)."\n"; + } + pclose($fp); + + if (!isset($options['quiet']) && strpos($out, 'item="modified"')) { + $params = array(array( + 'name' => 'modified', + 'type' => 'yesno', + 'default' => 'no', + 'prompt' => 'You have files in your SVN checkout (' . $path['from'] . ') that have been modified but not commited, do you still want to tag ' . $version . '?', + )); + $answers = $this->ui->confirmDialog($params); + + if (!in_array($answers['modified'], array('y', 'yes', 'on', '1'))) { + return true; + } + } + + if (isset($options['slide'])) { + $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options); + } + + // Check if tag already exists + $releaseTag = $path['local']['base'] . 'tags/' . $svntag; + $existsCommand = 'svn ls ' . $path['base'] . 'tags/'; + + $fp = popen($existsCommand, "r"); + $out = ''; + while ($line = fgets($fp, 1024)) { + $out .= rtrim($line)."\n"; + } + pclose($fp); + + if (in_array($svntag . '/', explode("\n", $out))) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('SVN tag ' . $svntag . ' for ' . $package . ' already exists.'); + } else { + $makeCommand = 'svn mkdir ' . $releaseTag; + $this->output .= "+ $makeCommand\n"; + if (empty($options['dry-run'])) { + // We need to create the tag dir. + $fp = popen($makeCommand, "r"); + $out = ''; + while ($line = fgets($fp, 1024)) { + $out .= rtrim($line)."\n"; + } + pclose($fp); + $this->output .= "$out\n"; + } + } + + $command = 'svn'; + if (isset($options['quiet'])) { + $command .= ' -q'; + } + + $command .= ' copy --parents '; + + $dir = dirname($packageFile); + $dir = substr($dir, strrpos($dir, '/') + 1); + $files = array_keys($info->getFilelist()); + $commands = array(); + foreach ($files as $file) { + if (!file_exists($file)) { + $file = $dir . DIRECTORY_SEPARATOR . $file; + } + $commands[] = $command . ' ' . escapeshellarg($file) . ' ' . + escapeshellarg($releaseTag . DIRECTORY_SEPARATOR . $file); + } + + $this->output .= implode("\n", $commands) . "\n"; + if (empty($options['dry-run'])) { + foreach ($commands as $command) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + } + + $command = 'svn ci -m "Tagging the ' . $version . ' release" ' . $releaseTag . "\n"; + $this->output .= "+ $command\n"; + if (empty($options['dry-run'])) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $_cmd); + return true; + } + + function _svnFindPath($file) + { + $xml = ''; + $command = "svn info --xml $file"; + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $xml .= rtrim($line)."\n"; + } + pclose($fp); + $url_tag = strpos($xml, ''); + $url = substr($xml, $url_tag + 5, strpos($xml, '', $url_tag + 5) - ($url_tag + 5)); + + $path = array(); + $path['from'] = substr($url, 0, strrpos($url, '/')); + $path['base'] = substr($path['from'], 0, strrpos($path['from'], '/') + 1); + + // Figure out the local paths + $pos = strpos($file, '/trunk/'); + if ($pos === false) { + $pos = strpos($file, '/branches/'); + } + $path['local']['base'] = substr($file, 0, $pos + 1); + + return $path; + } + + function _svnRemoveTag($version, $package, $tag, $packageFile, $options) + { + $command = 'svn'; + + if (isset($options['quiet'])) { + $command .= ' -q'; + } + + $command .= ' remove'; + $command .= ' -m "Removing tag for the ' . $version . ' release."'; + + $path = $this->_svnFindPath($packageFile); + $command .= ' ' . $path['base'] . 'tags/' . $tag; + + + if ($this->config->get('verbose') > 1) { + $this->output .= "+ $command\n"; + } + + $this->output .= "+ $command\n"; + if (empty($options['dry-run'])) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $command); + return true; + } + + function doCvsTag($command, $options, $params) + { + $this->output = ''; + $_cmd = $command; + if (count($params) < 1) { + $help = $this->getHelp($command); + return $this->raiseError("$command: missing parameter: $help[0]"); + } + + $packageFile = realpath($params[0]); + $obj = &$this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $err = $warn = array(); + if (!$info->validate()) { + foreach ($info->getValidationWarnings() as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + if (!$this->_displayValidationResults($err, $warn, true)) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('CVS tag failed'); + } + + $version = $info->getVersion(); + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $version); + $cvstag = "RELEASE_$cvsversion"; + $files = array_keys($info->getFilelist()); + $command = 'cvs'; + if (isset($options['quiet'])) { + $command .= ' -q'; + } + + if (isset($options['reallyquiet'])) { + $command .= ' -Q'; + } + + $command .= ' tag'; + if (isset($options['slide'])) { + $command .= ' -F'; + } + + if (isset($options['delete'])) { + $command .= ' -d'; + } + + $command .= ' ' . $cvstag . ' ' . escapeshellarg($params[0]); + array_shift($params); + if (count($params)) { + // add in additional files to be tagged + $files = array_merge($files, $params); + } + + $dir = dirname($packageFile); + $dir = substr($dir, strrpos($dir, '/') + 1); + foreach ($files as $file) { + if (!file_exists($file)) { + $file = $dir . DIRECTORY_SEPARATOR . $file; + } + $command .= ' ' . escapeshellarg($file); + } + + if ($this->config->get('verbose') > 1) { + $this->output .= "+ $command\n"; + } + + $this->output .= "+ $command\n"; + if (empty($options['dry-run'])) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $_cmd); + return true; + } + + function doCvsDiff($command, $options, $params) + { + $this->output = ''; + if (sizeof($params) < 1) { + $help = $this->getHelp($command); + return $this->raiseError("$command: missing parameter: $help[0]"); + } + + $file = realpath($params[0]); + $obj = &$this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromAnyFile($file, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $err = $warn = array(); + if (!$info->validate()) { + foreach ($info->getValidationWarnings() as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + if (!$this->_displayValidationResults($err, $warn, true)) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('CVS diff failed'); + } + + $info1 = $info->getFilelist(); + $files = $info1; + $cmd = "cvs"; + if (isset($options['quiet'])) { + $cmd .= ' -q'; + unset($options['quiet']); + } + + if (isset($options['reallyquiet'])) { + $cmd .= ' -Q'; + unset($options['reallyquiet']); + } + + if (isset($options['release'])) { + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $options['release']); + $cvstag = "RELEASE_$cvsversion"; + $options['revision'] = $cvstag; + unset($options['release']); + } + + $execute = true; + if (isset($options['dry-run'])) { + $execute = false; + unset($options['dry-run']); + } + + $cmd .= ' diff'; + // the rest of the options are passed right on to "cvs diff" + foreach ($options as $option => $optarg) { + $arg = $short = false; + if (isset($this->commands[$command]['options'][$option])) { + $arg = $this->commands[$command]['options'][$option]['arg']; + $short = $this->commands[$command]['options'][$option]['shortopt']; + } + $cmd .= $short ? " -$short" : " --$option"; + if ($arg && $optarg) { + $cmd .= ($short ? '' : '=') . escapeshellarg($optarg); + } + } + + foreach ($files as $file) { + $cmd .= ' ' . escapeshellarg($file['name']); + } + + if ($this->config->get('verbose') > 1) { + $this->output .= "+ $cmd\n"; + } + + if ($execute) { + $fp = popen($cmd, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $command); + return true; + } + + function doPackageDependencies($command, $options, $params) + { + // $params[0] -> the PEAR package to list its information + if (count($params) !== 1) { + return $this->raiseError("bad parameter(s), try \"help $command\""); + } + + $obj = &$this->getPackageFile($this->config, $this->_debug); + if (is_file($params[0]) || strpos($params[0], '.xml') > 0) { + $info = $obj->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); + } else { + $reg = $this->config->getRegistry(); + $info = $obj->fromArray($reg->packageInfo($params[0])); + } + + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $deps = $info->getDeps(); + if (is_array($deps)) { + if ($info->getPackagexmlVersion() == '1.0') { + $data = array( + 'caption' => 'Dependencies for pear/' . $info->getPackage(), + 'border' => true, + 'headline' => array("Required?", "Type", "Name", "Relation", "Version"), + ); + + foreach ($deps as $d) { + if (isset($d['optional'])) { + if ($d['optional'] == 'yes') { + $req = 'No'; + } else { + $req = 'Yes'; + } + } else { + $req = 'Yes'; + } + + if (isset($this->_deps_rel_trans[$d['rel']])) { + $rel = $this->_deps_rel_trans[$d['rel']]; + } else { + $rel = $d['rel']; + } + + if (isset($this->_deps_type_trans[$d['type']])) { + $type = ucfirst($this->_deps_type_trans[$d['type']]); + } else { + $type = $d['type']; + } + + if (isset($d['name'])) { + $name = $d['name']; + } else { + $name = ''; + } + + if (isset($d['version'])) { + $version = $d['version']; + } else { + $version = ''; + } + + $data['data'][] = array($req, $type, $name, $rel, $version); + } + } else { // package.xml 2.0 dependencies display + require_once 'PEAR/Dependency2.php'; + $deps = $info->getDependencies(); + $reg = &$this->config->getRegistry(); + if (is_array($deps)) { + $d = new PEAR_Dependency2($this->config, array(), ''); + $data = array( + 'caption' => 'Dependencies for ' . $info->getPackage(), + 'border' => true, + 'headline' => array("Required?", "Type", "Name", 'Versioning', 'Group'), + ); + foreach ($deps as $type => $subd) { + $req = ($type == 'required') ? 'Yes' : 'No'; + if ($type == 'group') { + $group = $subd['attribs']['name']; + } else { + $group = ''; + } + + if (!isset($subd[0])) { + $subd = array($subd); + } + + foreach ($subd as $groupa) { + foreach ($groupa as $deptype => $depinfo) { + if ($deptype == 'attribs') { + continue; + } + + if ($deptype == 'pearinstaller') { + $deptype = 'pear Installer'; + } + + if (!isset($depinfo[0])) { + $depinfo = array($depinfo); + } + + foreach ($depinfo as $inf) { + $name = ''; + if (isset($inf['channel'])) { + $alias = $reg->channelAlias($inf['channel']); + if (!$alias) { + $alias = '(channel?) ' .$inf['channel']; + } + $name = $alias . '/'; + + } + if (isset($inf['name'])) { + $name .= $inf['name']; + } elseif (isset($inf['pattern'])) { + $name .= $inf['pattern']; + } else { + $name .= ''; + } + + if (isset($inf['uri'])) { + $name .= ' [' . $inf['uri'] . ']'; + } + + if (isset($inf['conflicts'])) { + $ver = 'conflicts'; + } else { + $ver = $d->_getExtraString($inf); + } + + $data['data'][] = array($req, ucfirst($deptype), $name, + $ver, $group); + } + } + } + } + } + } + + $this->ui->outputData($data, $command); + return true; + } + + // Fallback + $this->ui->outputData("This package does not have any dependencies.", $command); + } + + function doSign($command, $options, $params) + { + // should move most of this code into PEAR_Packager + // so it'll be easy to implement "pear package --sign" + if (count($params) !== 1) { + return $this->raiseError("bad parameter(s), try \"help $command\""); + } + + require_once 'System.php'; + require_once 'Archive/Tar.php'; + + if (!file_exists($params[0])) { + return $this->raiseError("file does not exist: $params[0]"); + } + + $obj = $this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $tar = new Archive_Tar($params[0]); + $tmpdir = System::mktemp('-d pearsign'); + if (!$tar->extractList('package2.xml package.xml package.sig', $tmpdir)) { + return $this->raiseError("failed to extract tar file"); + } + + if (file_exists("$tmpdir/package.sig")) { + return $this->raiseError("package already signed"); + } + + $packagexml = 'package.xml'; + if (file_exists("$tmpdir/package2.xml")) { + $packagexml = 'package2.xml'; + } + + if (file_exists("$tmpdir/package.sig")) { + unlink("$tmpdir/package.sig"); + } + + if (!file_exists("$tmpdir/$packagexml")) { + return $this->raiseError("Extracted file $tmpdir/$packagexml not found."); + } + + $input = $this->ui->userDialog($command, + array('GnuPG Passphrase'), + array('password')); + if (!isset($input[0])) { + //use empty passphrase + $input[0] = ''; + } + + $devnull = (isset($options['verbose'])) ? '' : ' 2>/dev/null'; + $gpg = popen("gpg --batch --passphrase-fd 0 --armor --detach-sign --output $tmpdir/package.sig $tmpdir/$packagexml" . $devnull, "w"); + if (!$gpg) { + return $this->raiseError("gpg command failed"); + } + + fwrite($gpg, "$input[0]\n"); + if (pclose($gpg) || !file_exists("$tmpdir/package.sig")) { + return $this->raiseError("gpg sign failed"); + } + + if (!$tar->addModify("$tmpdir/package.sig", '', $tmpdir)) { + return $this->raiseError('failed adding signature to file'); + } + + $this->ui->outputData("Package signed.", $command); + return true; + } + + /** + * For unit testing purposes + */ + function &getInstaller(&$ui) + { + if (!class_exists('PEAR_Installer')) { + require_once 'PEAR/Installer.php'; + } + $a = &new PEAR_Installer($ui); + return $a; + } + + /** + * For unit testing purposes + */ + function &getCommandPackaging(&$ui, &$config) + { + if (!class_exists('PEAR_Command_Packaging')) { + if ($fp = @fopen('PEAR/Command/Packaging.php', 'r', true)) { + fclose($fp); + include_once 'PEAR/Command/Packaging.php'; + } + } + + if (class_exists('PEAR_Command_Packaging')) { + $a = &new PEAR_Command_Packaging($ui, $config); + } else { + $a = null; + } + + return $a; + } + + function doMakeRPM($command, $options, $params) + { + + // Check to see if PEAR_Command_Packaging is installed, and + // transparently switch to use the "make-rpm-spec" command from it + // instead, if it does. Otherwise, continue to use the old version + // of "makerpm" supplied with this package (PEAR). + $packaging_cmd = $this->getCommandPackaging($this->ui, $this->config); + if ($packaging_cmd !== null) { + $this->ui->outputData('PEAR_Command_Packaging is installed; using '. + 'newer "make-rpm-spec" command instead'); + return $packaging_cmd->run('make-rpm-spec', $options, $params); + } + + $this->ui->outputData('WARNING: "pear makerpm" is no longer available; an '. + 'improved version is available via "pear make-rpm-spec", which '. + 'is available by installing PEAR_Command_Packaging'); + return true; + } + + function doConvert($command, $options, $params) + { + $packagexml = isset($params[0]) ? $params[0] : 'package.xml'; + $newpackagexml = isset($params[1]) ? $params[1] : dirname($packagexml) . + DIRECTORY_SEPARATOR . 'package2.xml'; + $pkg = &$this->getPackageFile($this->config, $this->_debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pf = $pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + if (is_array($pf->getUserInfo())) { + foreach ($pf->getUserInfo() as $warning) { + $this->ui->outputData($warning['message']); + } + } + return $this->raiseError($pf); + } + + if (is_a($pf, 'PEAR_PackageFile_v2')) { + $this->ui->outputData($packagexml . ' is already a package.xml version 2.0'); + return true; + } + + $gen = &$pf->getDefaultGenerator(); + $newpf = &$gen->toV2(); + $newpf->setPackagefile($newpackagexml); + $gen = &$newpf->getDefaultGenerator(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $state = (isset($options['flat']) ? PEAR_VALIDATE_PACKAGING : PEAR_VALIDATE_NORMAL); + $saved = $gen->toPackageFile(dirname($newpackagexml), $state, basename($newpackagexml)); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($saved)) { + if (is_array($saved->getUserInfo())) { + foreach ($saved->getUserInfo() as $warning) { + $this->ui->outputData($warning['message']); + } + } + + $this->ui->outputData($saved->getMessage()); + return true; + } + + $this->ui->outputData('Wrote new version 2.0 package.xml to "' . $saved . '"'); + return true; + } +}PEAR-1.9.0/PEAR/Command/Pickle.xml100664 764 764 2233 100664 11344 + + Build PECL Package + doPackage + pi + + + Z + Do not gzip the package file + + + n + Print the name of the packaged file. + + + [descfile] +Creates a PECL package from its package2.xml file. + +An automatic conversion will be made to a package.xml 1.0 and written out to +disk in the current directory as "package.xml". Note that +only simple package.xml 2.0 will be converted. package.xml 2.0 with: + + - dependency types other than required/optional PECL package/ext/php/pearinstaller + - more than one extsrcrelease or zendextsrcrelease + - zendextbinrelease, extbinrelease, phprelease, or bundle release type + - dependency groups + - ignore tags in release filelist + - tasks other than replace + - custom roles + +will cause pickle to fail, and output an error message. If your package2.xml +uses any of these features, you are best off using PEAR_PackageFileManager to +generate both package.xml. + + +PEAR-1.9.0/PEAR/Command/Pickle.php100664 764 764 37207 100664 11364 + * @copyright 2005-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Pickle.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 2005-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.1 + */ + +class PEAR_Command_Pickle extends PEAR_Command_Common +{ + var $commands = array( + 'pickle' => array( + 'summary' => 'Build PECL Package', + 'function' => 'doPackage', + 'shortcut' => 'pi', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'Do not gzip the package file' + ), + 'showname' => array( + 'shortopt' => 'n', + 'doc' => 'Print the name of the packaged file.', + ), + ), + 'doc' => '[descfile] +Creates a PECL package from its package2.xml file. + +An automatic conversion will be made to a package.xml 1.0 and written out to +disk in the current directory as "package.xml". Note that +only simple package.xml 2.0 will be converted. package.xml 2.0 with: + + - dependency types other than required/optional PECL package/ext/php/pearinstaller + - more than one extsrcrelease or zendextsrcrelease + - zendextbinrelease, extbinrelease, phprelease, or bundle release type + - dependency groups + - ignore tags in release filelist + - tasks other than replace + - custom roles + +will cause pickle to fail, and output an error message. If your package2.xml +uses any of these features, you are best off using PEAR_PackageFileManager to +generate both package.xml. +' + ), + ); + + /** + * PEAR_Command_Package constructor. + * + * @access public + */ + function PEAR_Command_Pickle(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + /** + * For unit-testing ease + * + * @return PEAR_Packager + */ + function &getPackager() + { + if (!class_exists('PEAR_Packager')) { + require_once 'PEAR/Packager.php'; + } + + $a = &new PEAR_Packager; + return $a; + } + + /** + * For unit-testing ease + * + * @param PEAR_Config $config + * @param bool $debug + * @param string|null $tmpdir + * @return PEAR_PackageFile + */ + function &getPackageFile($config, $debug = false, $tmpdir = null) + { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $a = &new PEAR_PackageFile($config, $debug, $tmpdir); + $common = new PEAR_Common; + $common->ui = $this->ui; + $a->setLogger($common); + return $a; + } + + function doPackage($command, $options, $params) + { + $this->output = ''; + $pkginfofile = isset($params[0]) ? $params[0] : 'package2.xml'; + $packager = &$this->getPackager(); + if (PEAR::isError($err = $this->_convertPackage($pkginfofile))) { + return $err; + } + + $compress = empty($options['nocompress']) ? true : false; + $result = $packager->package($pkginfofile, $compress, 'package.xml'); + if (PEAR::isError($result)) { + return $this->raiseError($result); + } + + // Don't want output, only the package file name just created + if (isset($options['showname'])) { + $this->ui->outputData($result, $command); + } + + return true; + } + + function _convertPackage($packagexml) + { + $pkg = &$this->getPackageFile($this->config); + $pf2 = &$pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL); + if (!is_a($pf2, 'PEAR_PackageFile_v2')) { + return $this->raiseError('Cannot process "' . + $packagexml . '", is not a package.xml 2.0'); + } + + require_once 'PEAR/PackageFile/v1.php'; + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->config); + if ($pf2->getPackageType() != 'extsrc' && $pf2->getPackageType() != 'zendextsrc') { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", is not an extension source package. Using a PEAR_PackageFileManager-based ' . + 'script is an option'); + } + + if (is_array($pf2->getUsesRole())) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains custom roles. Using a PEAR_PackageFileManager-based script or ' . + 'the convert command is an option'); + } + + if (is_array($pf2->getUsesTask())) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains custom tasks. Using a PEAR_PackageFileManager-based script or ' . + 'the convert command is an option'); + } + + $deps = $pf2->getDependencies(); + if (isset($deps['group'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains dependency groups. Using a PEAR_PackageFileManager-based script ' . + 'or the convert command is an option'); + } + + if (isset($deps['required']['subpackage']) || + isset($deps['optional']['subpackage'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains subpackage dependencies. Using a PEAR_PackageFileManager-based '. + 'script is an option'); + } + + if (isset($deps['required']['os'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains os dependencies. Using a PEAR_PackageFileManager-based '. + 'script is an option'); + } + + if (isset($deps['required']['arch'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains arch dependencies. Using a PEAR_PackageFileManager-based '. + 'script is an option'); + } + + $pf->setPackage($pf2->getPackage()); + $pf->setSummary($pf2->getSummary()); + $pf->setDescription($pf2->getDescription()); + foreach ($pf2->getMaintainers() as $maintainer) { + $pf->addMaintainer($maintainer['role'], $maintainer['handle'], + $maintainer['name'], $maintainer['email']); + } + + $pf->setVersion($pf2->getVersion()); + $pf->setDate($pf2->getDate()); + $pf->setLicense($pf2->getLicense()); + $pf->setState($pf2->getState()); + $pf->setNotes($pf2->getNotes()); + $pf->addPhpDep($deps['required']['php']['min'], 'ge'); + if (isset($deps['required']['php']['max'])) { + $pf->addPhpDep($deps['required']['php']['max'], 'le'); + } + + if (isset($deps['required']['package'])) { + if (!isset($deps['required']['package'][0])) { + $deps['required']['package'] = array($deps['required']['package']); + } + + foreach ($deps['required']['package'] as $dep) { + if (!isset($dep['channel'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains uri-based dependency on a package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if ($dep['channel'] != 'pear.php.net' + && $dep['channel'] != 'pecl.php.net' + && $dep['channel'] != 'doc.php.net') { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains dependency on a non-standard channel package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['conflicts'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains conflicts dependency. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addPackageDep($dep['name'], $dep['min'], 'ge'); + } + + if (isset($dep['max'])) { + $pf->addPackageDep($dep['name'], $dep['max'], 'le'); + } + } + } + + if (isset($deps['required']['extension'])) { + if (!isset($deps['required']['extension'][0])) { + $deps['required']['extension'] = array($deps['required']['extension']); + } + + foreach ($deps['required']['extension'] as $dep) { + if (isset($dep['conflicts'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains conflicts dependency. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addExtensionDep($dep['name'], $dep['min'], 'ge'); + } + + if (isset($dep['max'])) { + $pf->addExtensionDep($dep['name'], $dep['max'], 'le'); + } + } + } + + if (isset($deps['optional']['package'])) { + if (!isset($deps['optional']['package'][0])) { + $deps['optional']['package'] = array($deps['optional']['package']); + } + + foreach ($deps['optional']['package'] as $dep) { + if (!isset($dep['channel'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains uri-based dependency on a package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if ($dep['channel'] != 'pear.php.net' + && $dep['channel'] != 'pecl.php.net' + && $dep['channel'] != 'doc.php.net') { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains dependency on a non-standard channel package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addPackageDep($dep['name'], $dep['min'], 'ge', 'yes'); + } + + if (isset($dep['max'])) { + $pf->addPackageDep($dep['name'], $dep['max'], 'le', 'yes'); + } + } + } + + if (isset($deps['optional']['extension'])) { + if (!isset($deps['optional']['extension'][0])) { + $deps['optional']['extension'] = array($deps['optional']['extension']); + } + + foreach ($deps['optional']['extension'] as $dep) { + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addExtensionDep($dep['name'], $dep['min'], 'ge', 'yes'); + } + + if (isset($dep['max'])) { + $pf->addExtensionDep($dep['name'], $dep['max'], 'le', 'yes'); + } + } + } + + $contents = $pf2->getContents(); + $release = $pf2->getReleases(); + if (isset($releases[0])) { + return $this->raiseError('Cannot safely process "' . $packagexml . '" contains ' + . 'multiple extsrcrelease/zendextsrcrelease tags. Using a PEAR_PackageFileManager-based script ' . + 'or the convert command is an option'); + } + + if ($configoptions = $pf2->getConfigureOptions()) { + foreach ($configoptions as $option) { + $default = isset($option['default']) ? $option['default'] : false; + $pf->addConfigureOption($option['name'], $option['prompt'], $default); + } + } + + if (isset($release['filelist']['ignore'])) { + return $this->raiseError('Cannot safely process "' . $packagexml . '" contains ' + . 'ignore tags. Using a PEAR_PackageFileManager-based script or the convert' . + ' command is an option'); + } + + if (isset($release['filelist']['install']) && + !isset($release['filelist']['install'][0])) { + $release['filelist']['install'] = array($release['filelist']['install']); + } + + if (isset($contents['dir']['attribs']['baseinstalldir'])) { + $baseinstalldir = $contents['dir']['attribs']['baseinstalldir']; + } else { + $baseinstalldir = false; + } + + if (!isset($contents['dir']['file'][0])) { + $contents['dir']['file'] = array($contents['dir']['file']); + } + + foreach ($contents['dir']['file'] as $file) { + if ($baseinstalldir && !isset($file['attribs']['baseinstalldir'])) { + $file['attribs']['baseinstalldir'] = $baseinstalldir; + } + + $processFile = $file; + unset($processFile['attribs']); + if (count($processFile)) { + foreach ($processFile as $name => $task) { + if ($name != $pf2->getTasksNs() . ':replace') { + return $this->raiseError('Cannot safely process "' . $packagexml . + '" contains tasks other than replace. Using a ' . + 'PEAR_PackageFileManager-based script is an option.'); + } + $file['attribs']['replace'][] = $task; + } + } + + if (!in_array($file['attribs']['role'], PEAR_Common::getFileRoles())) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains custom roles. Using a PEAR_PackageFileManager-based script ' . + 'or the convert command is an option'); + } + + if (isset($release['filelist']['install'])) { + foreach ($release['filelist']['install'] as $installas) { + if ($installas['attribs']['name'] == $file['attribs']['name']) { + $file['attribs']['install-as'] = $installas['attribs']['as']; + } + } + } + + $pf->addFile('/', $file['attribs']['name'], $file['attribs']); + } + + if ($pf2->getChangeLog()) { + $this->ui->outputData('WARNING: changelog is not translated to package.xml ' . + '1.0, use PEAR_PackageFileManager-based script if you need changelog-' . + 'translation for package.xml 1.0'); + } + + $gen = &$pf->getDefaultGenerator(); + $gen->toPackageFile('.'); + } +}PEAR-1.9.0/PEAR/Command/Registry.xml100664 764 764 3376 100664 11756 + + List Installed Packages In The Default Channel + doList + l + + + c + list installed packages from this channel + CHAN + + + a + list installed packages from all channels + + + i + output fully channel-aware data, even on failure + + + <package> +If invoked without parameters, this command lists the PEAR packages +installed in your php_dir ({config php_dir}). With a parameter, it +lists the files in a package. + + + + List Files In Installed Package + doFileList + fl + + <package> +List the files in an installed package. + + + + Shell Script Test + doShellTest + st + + <package> [[relation] version] +Tests if a package is installed in the system. Will exit(1) if it is not. + <relation> The version comparison operator. One of: + <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne + <version> The version to compare with + + + + Display information about a package + doInfo + in + + <package> +Displays information about a package. The package argument may be a +local package file, an URL to a package file, or the name of an +installed package. + +PEAR-1.9.0/PEAR/Command/Registry.php100664 764 764 132371 100664 12003 + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Registry.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for registry manipulation + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Registry extends PEAR_Command_Common +{ + var $commands = array( + 'list' => array( + 'summary' => 'List Installed Packages In The Default Channel', + 'function' => 'doList', + 'shortcut' => 'l', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'list installed packages from this channel', + 'arg' => 'CHAN', + ), + 'allchannels' => array( + 'shortopt' => 'a', + 'doc' => 'list installed packages from all channels', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => ' +If invoked without parameters, this command lists the PEAR packages +installed in your php_dir ({config php_dir}). With a parameter, it +lists the files in a package. +', + ), + 'list-files' => array( + 'summary' => 'List Files In Installed Package', + 'function' => 'doFileList', + 'shortcut' => 'fl', + 'options' => array(), + 'doc' => ' +List the files in an installed package. +' + ), + 'shell-test' => array( + 'summary' => 'Shell Script Test', + 'function' => 'doShellTest', + 'shortcut' => 'st', + 'options' => array(), + 'doc' => ' [[relation] version] +Tests if a package is installed in the system. Will exit(1) if it is not. + The version comparison operator. One of: + <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne + The version to compare with +'), + 'info' => array( + 'summary' => 'Display information about a package', + 'function' => 'doInfo', + 'shortcut' => 'in', + 'options' => array(), + 'doc' => ' +Displays information about a package. The package argument may be a +local package file, an URL to a package file, or the name of an +installed package.' + ) + ); + + /** + * PEAR_Command_Registry constructor. + * + * @access public + */ + function PEAR_Command_Registry(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function _sortinfo($a, $b) + { + $apackage = isset($a['package']) ? $a['package'] : $a['name']; + $bpackage = isset($b['package']) ? $b['package'] : $b['name']; + return strcmp($apackage, $bpackage); + } + + function doList($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $channelinfo = isset($options['channelinfo']); + if (isset($options['allchannels']) && !$channelinfo) { + return $this->doListAll($command, array(), $params); + } + + if (isset($options['allchannels']) && $channelinfo) { + // allchannels with $channelinfo + unset($options['allchannels']); + $channels = $reg->getChannels(); + $errors = array(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + foreach ($channels as $channel) { + $options['channel'] = $channel->getName(); + $ret = $this->doList($command, $options, $params); + + if (PEAR::isError($ret)) { + $errors[] = $ret; + } + } + + PEAR::staticPopErrorHandling(); + if (count($errors)) { + // for now, only give first error + return PEAR::raiseError($errors[0]); + } + + return true; + } + + if (count($params) === 1) { + return $this->doFileList($command, $options, $params); + } + + if (isset($options['channel'])) { + if (!$reg->channelExists($options['channel'])) { + return $this->raiseError('Channel "' . $options['channel'] .'" does not exist'); + } + + $channel = $reg->channelName($options['channel']); + } else { + $channel = $this->config->get('default_channel'); + } + + $installed = $reg->packageInfo(null, null, $channel); + usort($installed, array(&$this, '_sortinfo')); + + $data = array( + 'caption' => 'Installed packages, channel ' . + $channel . ':', + 'border' => true, + 'headline' => array('Package', 'Version', 'State'), + 'channel' => $channel, + ); + if ($channelinfo) { + $data['headline'] = array('Channel', 'Package', 'Version', 'State'); + } + + if (count($installed) && !isset($data['data'])) { + $data['data'] = array(); + } + + foreach ($installed as $package) { + $pobj = $reg->getPackage(isset($package['package']) ? + $package['package'] : $package['name'], $channel); + if ($channelinfo) { + $packageinfo = array($pobj->getChannel(), $pobj->getPackage(), $pobj->getVersion(), + $pobj->getState() ? $pobj->getState() : null); + } else { + $packageinfo = array($pobj->getPackage(), $pobj->getVersion(), + $pobj->getState() ? $pobj->getState() : null); + } + $data['data'][] = $packageinfo; + } + + if (count($installed) === 0) { + if (!$channelinfo) { + $data = '(no packages installed from channel ' . $channel . ')'; + } else { + $data = array( + 'caption' => 'Installed packages, channel ' . + $channel . ':', + 'border' => true, + 'channel' => $channel, + 'data' => array(array('(no packages installed)')), + ); + } + } + + $this->ui->outputData($data, $command); + return true; + } + + function doListAll($command, $options, $params) + { + // This duplicate code is deprecated over + // list --channelinfo, which gives identical + // output for list and list --allchannels. + $reg = &$this->config->getRegistry(); + $installed = $reg->packageInfo(null, null, null); + foreach ($installed as $channel => $packages) { + usort($packages, array($this, '_sortinfo')); + $data = array( + 'caption' => 'Installed packages, channel ' . $channel . ':', + 'border' => true, + 'headline' => array('Package', 'Version', 'State'), + 'channel' => $channel + ); + + foreach ($packages as $package) { + $p = isset($package['package']) ? $package['package'] : $package['name']; + $pobj = $reg->getPackage($p, $channel); + $data['data'][] = array($pobj->getPackage(), $pobj->getVersion(), + $pobj->getState() ? $pobj->getState() : null); + } + + // Adds a blank line after each section + $data['data'][] = array(); + + if (count($packages) === 0) { + $data = array( + 'caption' => 'Installed packages, channel ' . $channel . ':', + 'border' => true, + 'data' => array(array('(no packages installed)'), array()), + 'channel' => $channel + ); + } + $this->ui->outputData($data, $command); + } + return true; + } + + function doFileList($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('list-files expects 1 parameter'); + } + + $reg = &$this->config->getRegistry(); + $fp = false; + if (!is_dir($params[0]) && (file_exists($params[0]) || $fp = @fopen($params[0], 'r'))) { + if ($fp) { + fclose($fp); + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $pkg = &new PEAR_PackageFile($this->config, $this->_debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + $headings = array('Package File', 'Install Path'); + $installed = false; + } else { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($parsed)) { + return $this->raiseError($parsed); + } + + $info = &$reg->getPackage($parsed['package'], $parsed['channel']); + $headings = array('Type', 'Install Path'); + $installed = true; + } + + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + if ($info === null) { + return $this->raiseError("`$params[0]' not installed"); + } + + $list = ($info->getPackagexmlVersion() == '1.0' || $installed) ? + $info->getFilelist() : $info->getContents(); + if ($installed) { + $caption = 'Installed Files For ' . $params[0]; + } else { + $caption = 'Contents of ' . basename($params[0]); + } + + $data = array( + 'caption' => $caption, + 'border' => true, + 'headline' => $headings); + if ($info->getPackagexmlVersion() == '1.0' || $installed) { + foreach ($list as $file => $att) { + if ($installed) { + if (empty($att['installed_as'])) { + continue; + } + $data['data'][] = array($att['role'], $att['installed_as']); + } else { + if (isset($att['baseinstalldir']) && !in_array($att['role'], + array('test', 'data', 'doc'))) { + $dest = $att['baseinstalldir'] . DIRECTORY_SEPARATOR . + $file; + } else { + $dest = $file; + } + switch ($att['role']) { + case 'test': + case 'data': + case 'doc': + $role = $att['role']; + if ($role == 'test') { + $role .= 's'; + } + $dest = $this->config->get($role . '_dir') . DIRECTORY_SEPARATOR . + $info->getPackage() . DIRECTORY_SEPARATOR . $dest; + break; + case 'php': + default: + $dest = $this->config->get('php_dir') . DIRECTORY_SEPARATOR . + $dest; + } + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + $dest = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + array(DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), + $dest); + $file = preg_replace('!/+!', '/', $file); + $data['data'][] = array($file, $dest); + } + } + } else { // package.xml 2.0, not installed + if (!isset($list['dir']['file'][0])) { + $list['dir']['file'] = array($list['dir']['file']); + } + + foreach ($list['dir']['file'] as $att) { + $att = $att['attribs']; + $file = $att['name']; + $role = &PEAR_Installer_Role::factory($info, $att['role'], $this->config); + $role->setup($this, $info, $att, $file); + if (!$role->isInstallable()) { + $dest = '(not installable)'; + } else { + $dest = $role->processInstallation($info, $att, $file, ''); + if (PEAR::isError($dest)) { + $dest = '(Unknown role "' . $att['role'] . ')'; + } else { + list(,, $dest) = $dest; + } + } + $data['data'][] = array($file, $dest); + } + } + + $this->ui->outputData($data, $command); + return true; + } + + function doShellTest($command, $options, $params) + { + if (count($params) < 1) { + return PEAR::raiseError('ERROR, usage: pear shell-test packagename [[relation] version]'); + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $reg = &$this->config->getRegistry(); + $info = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + if (PEAR::isError($info)) { + exit(1); // invalid package name + } + + $package = $info['package']; + $channel = $info['channel']; + // "pear shell-test Foo" + if (!$reg->packageExists($package, $channel)) { + if ($channel == 'pecl.php.net') { + if ($reg->packageExists($package, 'pear.php.net')) { + $channel = 'pear.php.net'; // magically change channels for extensions + } + } + } + + if (count($params) === 1) { + if (!$reg->packageExists($package, $channel)) { + exit(1); + } + // "pear shell-test Foo 1.0" + } elseif (count($params) === 2) { + $v = $reg->packageInfo($package, 'version', $channel); + if (!$v || !version_compare("$v", "{$params[1]}", "ge")) { + exit(1); + } + // "pear shell-test Foo ge 1.0" + } elseif (count($params) === 3) { + $v = $reg->packageInfo($package, 'version', $channel); + if (!$v || !version_compare("$v", "{$params[2]}", $params[1])) { + exit(1); + } + } else { + PEAR::staticPopErrorHandling(); + $this->raiseError("$command: expects 1 to 3 parameters"); + exit(1); + } + } + + function doInfo($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('pear info expects 1 parameter'); + } + + $info = $fp = false; + $reg = &$this->config->getRegistry(); + if ((file_exists($params[0]) && is_file($params[0]) && !is_dir($params[0])) || $fp = @fopen($params[0], 'r')) { + if ($fp) { + fclose($fp); + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $pkg = &new PEAR_PackageFile($this->config, $this->_debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $obj = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($obj)) { + $uinfo = $obj->getUserInfo(); + if (is_array($uinfo)) { + foreach ($uinfo as $message) { + if (is_array($message)) { + $message = $message['message']; + } + $this->ui->outputData($message); + } + } + + return $this->raiseError($obj); + } + + if ($obj->getPackagexmlVersion() != '1.0') { + return $this->_doInfo2($command, $options, $params, $obj, false); + } + + $info = $obj->toArray(); + } else { + $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + if (PEAR::isError($parsed)) { + return $this->raiseError($parsed); + } + + $package = $parsed['package']; + $channel = $parsed['channel']; + $info = $reg->packageInfo($package, null, $channel); + if (isset($info['old'])) { + $obj = $reg->getPackage($package, $channel); + return $this->_doInfo2($command, $options, $params, $obj, true); + } + } + + if (PEAR::isError($info)) { + return $info; + } + + if (empty($info)) { + $this->raiseError("No information found for `$params[0]'"); + return; + } + + unset($info['filelist']); + unset($info['dirtree']); + unset($info['changelog']); + if (isset($info['xsdversion'])) { + $info['package.xml version'] = $info['xsdversion']; + unset($info['xsdversion']); + } + + if (isset($info['packagerversion'])) { + $info['packaged with PEAR version'] = $info['packagerversion']; + unset($info['packagerversion']); + } + + $keys = array_keys($info); + $longtext = array('description', 'summary'); + foreach ($keys as $key) { + if (is_array($info[$key])) { + switch ($key) { + case 'maintainers': { + $i = 0; + $mstr = ''; + foreach ($info[$key] as $m) { + if ($i++ > 0) { + $mstr .= "\n"; + } + $mstr .= $m['name'] . " <"; + if (isset($m['email'])) { + $mstr .= $m['email']; + } else { + $mstr .= $m['handle'] . '@php.net'; + } + $mstr .= "> ($m[role])"; + } + $info[$key] = $mstr; + break; + } + case 'release_deps': { + $i = 0; + $dstr = ''; + foreach ($info[$key] as $d) { + if (isset($this->_deps_rel_trans[$d['rel']])) { + $rel = $this->_deps_rel_trans[$d['rel']]; + } else { + $rel = $d['rel']; + } + if (isset($this->_deps_type_trans[$d['type']])) { + $type = ucfirst($this->_deps_type_trans[$d['type']]); + } else { + $type = $d['type']; + } + if (isset($d['name'])) { + $name = $d['name'] . ' '; + } else { + $name = ''; + } + if (isset($d['version'])) { + $version = $d['version'] . ' '; + } else { + $version = ''; + } + if (isset($d['optional']) && $d['optional'] == 'yes') { + $optional = ' (optional)'; + } else { + $optional = ''; + } + $dstr .= "$type $name$rel $version$optional\n"; + } + $info[$key] = $dstr; + break; + } + case 'provides' : { + $debug = $this->config->get('verbose'); + if ($debug < 2) { + $pstr = 'Classes: '; + } else { + $pstr = ''; + } + $i = 0; + foreach ($info[$key] as $p) { + if ($debug < 2 && $p['type'] != "class") { + continue; + } + // Only print classes when verbosity mode is < 2 + if ($debug < 2) { + if ($i++ > 0) { + $pstr .= ", "; + } + $pstr .= $p['name']; + } else { + if ($i++ > 0) { + $pstr .= "\n"; + } + $pstr .= ucfirst($p['type']) . " " . $p['name']; + if (isset($p['explicit']) && $p['explicit'] == 1) { + $pstr .= " (explicit)"; + } + } + } + $info[$key] = $pstr; + break; + } + case 'configure_options' : { + foreach ($info[$key] as $i => $p) { + $info[$key][$i] = array_map(null, array_keys($p), array_values($p)); + $info[$key][$i] = array_map(create_function('$a', + 'return join(" = ",$a);'), $info[$key][$i]); + $info[$key][$i] = implode(', ', $info[$key][$i]); + } + $info[$key] = implode("\n", $info[$key]); + break; + } + default: { + $info[$key] = implode(", ", $info[$key]); + break; + } + } + } + + if ($key == '_lastmodified') { + $hdate = date('Y-m-d', $info[$key]); + unset($info[$key]); + $info['Last Modified'] = $hdate; + } elseif ($key == '_lastversion') { + $info['Previous Installed Version'] = $info[$key] ? $info[$key] : '- None -'; + unset($info[$key]); + } else { + $info[$key] = trim($info[$key]); + if (in_array($key, $longtext)) { + $info[$key] = preg_replace('/ +/', ' ', $info[$key]); + } + } + } + + $caption = 'About ' . $info['package'] . '-' . $info['version']; + $data = array( + 'caption' => $caption, + 'border' => true); + foreach ($info as $key => $value) { + $key = ucwords(trim(str_replace('_', ' ', $key))); + $data['data'][] = array($key, $value); + } + $data['raw'] = $info; + + $this->ui->outputData($data, 'package-info'); + } + + /** + * @access private + */ + function _doInfo2($command, $options, $params, &$obj, $installed) + { + $reg = &$this->config->getRegistry(); + $caption = 'About ' . $obj->getChannel() . '/' .$obj->getPackage() . '-' . + $obj->getVersion(); + $data = array( + 'caption' => $caption, + 'border' => true); + switch ($obj->getPackageType()) { + case 'php' : + $release = 'PEAR-style PHP-based Package'; + break; + case 'extsrc' : + $release = 'PECL-style PHP extension (source code)'; + break; + case 'zendextsrc' : + $release = 'PECL-style Zend extension (source code)'; + break; + case 'extbin' : + $release = 'PECL-style PHP extension (binary)'; + break; + case 'zendextbin' : + $release = 'PECL-style Zend extension (binary)'; + break; + case 'bundle' : + $release = 'Package bundle (collection of packages)'; + break; + } + $extends = $obj->getExtends(); + $extends = $extends ? + $obj->getPackage() . ' (extends ' . $extends . ')' : $obj->getPackage(); + if ($src = $obj->getSourcePackage()) { + $extends .= ' (source package ' . $src['channel'] . '/' . $src['package'] . ')'; + } + + $info = array( + 'Release Type' => $release, + 'Name' => $extends, + 'Channel' => $obj->getChannel(), + 'Summary' => preg_replace('/ +/', ' ', $obj->getSummary()), + 'Description' => preg_replace('/ +/', ' ', $obj->getDescription()), + ); + $info['Maintainers'] = ''; + foreach (array('lead', 'developer', 'contributor', 'helper') as $role) { + $leads = $obj->{"get{$role}s"}(); + if (!$leads) { + continue; + } + + if (isset($leads['active'])) { + $leads = array($leads); + } + + foreach ($leads as $lead) { + if (!empty($info['Maintainers'])) { + $info['Maintainers'] .= "\n"; + } + + $active = $lead['active'] == 'no' ? ', inactive' : ''; + $info['Maintainers'] .= $lead['name'] . ' <'; + $info['Maintainers'] .= $lead['email'] . "> ($role$active)"; + } + } + + $info['Release Date'] = $obj->getDate(); + if ($time = $obj->getTime()) { + $info['Release Date'] .= ' ' . $time; + } + + $info['Release Version'] = $obj->getVersion() . ' (' . $obj->getState() . ')'; + $info['API Version'] = $obj->getVersion('api') . ' (' . $obj->getState('api') . ')'; + $info['License'] = $obj->getLicense(); + $uri = $obj->getLicenseLocation(); + if ($uri) { + if (isset($uri['uri'])) { + $info['License'] .= ' (' . $uri['uri'] . ')'; + } else { + $extra = $obj->getInstalledLocation($info['filesource']); + if ($extra) { + $info['License'] .= ' (' . $uri['filesource'] . ')'; + } + } + } + + $info['Release Notes'] = $obj->getNotes(); + if ($compat = $obj->getCompatible()) { + if (!isset($compat[0])) { + $compat = array($compat); + } + + $info['Compatible with'] = ''; + foreach ($compat as $package) { + $info['Compatible with'] .= $package['channel'] . '/' . $package['name'] . + "\nVersions >= " . $package['min'] . ', <= ' . $package['max']; + if (isset($package['exclude'])) { + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + $info['Not Compatible with'] .= $package['channel'] . '/' . + $package['name'] . "\nVersions " . $package['exclude']; + } + } + } + + $usesrole = $obj->getUsesrole(); + if ($usesrole) { + if (!isset($usesrole[0])) { + $usesrole = array($usesrole); + } + + foreach ($usesrole as $roledata) { + if (isset($info['Uses Custom Roles'])) { + $info['Uses Custom Roles'] .= "\n"; + } else { + $info['Uses Custom Roles'] = ''; + } + + if (isset($roledata['package'])) { + $rolepackage = $reg->parsedPackageNameToString($roledata, true); + } else { + $rolepackage = $roledata['uri']; + } + $info['Uses Custom Roles'] .= $roledata['role'] . ' (' . $rolepackage . ')'; + } + } + + $usestask = $obj->getUsestask(); + if ($usestask) { + if (!isset($usestask[0])) { + $usestask = array($usestask); + } + + foreach ($usestask as $taskdata) { + if (isset($info['Uses Custom Tasks'])) { + $info['Uses Custom Tasks'] .= "\n"; + } else { + $info['Uses Custom Tasks'] = ''; + } + + if (isset($taskdata['package'])) { + $taskpackage = $reg->parsedPackageNameToString($taskdata, true); + } else { + $taskpackage = $taskdata['uri']; + } + $info['Uses Custom Tasks'] .= $taskdata['task'] . ' (' . $taskpackage . ')'; + } + } + + $deps = $obj->getDependencies(); + $info['Required Dependencies'] = 'PHP version ' . $deps['required']['php']['min']; + if (isset($deps['required']['php']['max'])) { + $info['Required Dependencies'] .= '-' . $deps['required']['php']['max'] . "\n"; + } else { + $info['Required Dependencies'] .= "\n"; + } + + if (isset($deps['required']['php']['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($deps['required']['php']['exclude'])) { + $deps['required']['php']['exclude'] = + implode(', ', $deps['required']['php']['exclude']); + } + $info['Not Compatible with'] .= "PHP versions\n " . + $deps['required']['php']['exclude']; + } + + $info['Required Dependencies'] .= 'PEAR installer version'; + if (isset($deps['required']['pearinstaller']['max'])) { + $info['Required Dependencies'] .= 's ' . + $deps['required']['pearinstaller']['min'] . '-' . + $deps['required']['pearinstaller']['max']; + } else { + $info['Required Dependencies'] .= ' ' . + $deps['required']['pearinstaller']['min'] . ' or newer'; + } + + if (isset($deps['required']['pearinstaller']['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($deps['required']['pearinstaller']['exclude'])) { + $deps['required']['pearinstaller']['exclude'] = + implode(', ', $deps['required']['pearinstaller']['exclude']); + } + $info['Not Compatible with'] .= "PEAR installer\n Versions " . + $deps['required']['pearinstaller']['exclude']; + } + + foreach (array('Package', 'Extension') as $type) { + $index = strtolower($type); + if (isset($deps['required'][$index])) { + if (isset($deps['required'][$index]['name'])) { + $deps['required'][$index] = array($deps['required'][$index]); + } + + foreach ($deps['required'][$index] as $package) { + if (isset($package['conflicts'])) { + $infoindex = 'Not Compatible with'; + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + } else { + $infoindex = 'Required Dependencies'; + $info[$infoindex] .= "\n"; + } + + if ($index == 'extension') { + $name = $package['name']; + } else { + if (isset($package['channel'])) { + $name = $package['channel'] . '/' . $package['name']; + } else { + $name = '__uri/' . $package['name'] . ' (static URI)'; + } + } + + $info[$infoindex] .= "$type $name"; + if (isset($package['uri'])) { + $info[$infoindex] .= "\n Download URI: $package[uri]"; + continue; + } + + if (isset($package['max']) && isset($package['min'])) { + $info[$infoindex] .= " \n Versions " . + $package['min'] . '-' . $package['max']; + } elseif (isset($package['min'])) { + $info[$infoindex] .= " \n Version " . + $package['min'] . ' or newer'; + } elseif (isset($package['max'])) { + $info[$infoindex] .= " \n Version " . + $package['max'] . ' or older'; + } + + if (isset($package['recommended'])) { + $info[$infoindex] .= "\n Recommended version: $package[recommended]"; + } + + if (isset($package['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + + $package['package'] = $package['name']; // for parsedPackageNameToString + if (isset($package['conflicts'])) { + $info['Not Compatible with'] .= '=> except '; + } + $info['Not Compatible with'] .= 'Package ' . + $reg->parsedPackageNameToString($package, true); + $info['Not Compatible with'] .= "\n Versions " . $package['exclude']; + } + } + } + } + + if (isset($deps['required']['os'])) { + if (isset($deps['required']['os']['name'])) { + $dep['required']['os']['name'] = array($dep['required']['os']['name']); + } + + foreach ($dep['required']['os'] as $os) { + if (isset($os['conflicts']) && $os['conflicts'] == 'yes') { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + $info['Not Compatible with'] .= "$os[name] Operating System"; + } else { + $info['Required Dependencies'] .= "\n"; + $info['Required Dependencies'] .= "$os[name] Operating System"; + } + } + } + + if (isset($deps['required']['arch'])) { + if (isset($deps['required']['arch']['pattern'])) { + $dep['required']['arch']['pattern'] = array($dep['required']['os']['pattern']); + } + + foreach ($dep['required']['arch'] as $os) { + if (isset($os['conflicts']) && $os['conflicts'] == 'yes') { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + $info['Not Compatible with'] .= "OS/Arch matching pattern '/$os[pattern]/'"; + } else { + $info['Required Dependencies'] .= "\n"; + $info['Required Dependencies'] .= "OS/Arch matching pattern '/$os[pattern]/'"; + } + } + } + + if (isset($deps['optional'])) { + foreach (array('Package', 'Extension') as $type) { + $index = strtolower($type); + if (isset($deps['optional'][$index])) { + if (isset($deps['optional'][$index]['name'])) { + $deps['optional'][$index] = array($deps['optional'][$index]); + } + + foreach ($deps['optional'][$index] as $package) { + if (isset($package['conflicts']) && $package['conflicts'] == 'yes') { + $infoindex = 'Not Compatible with'; + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + } else { + $infoindex = 'Optional Dependencies'; + if (!isset($info['Optional Dependencies'])) { + $info['Optional Dependencies'] = ''; + } else { + $info['Optional Dependencies'] .= "\n"; + } + } + + if ($index == 'extension') { + $name = $package['name']; + } else { + if (isset($package['channel'])) { + $name = $package['channel'] . '/' . $package['name']; + } else { + $name = '__uri/' . $package['name'] . ' (static URI)'; + } + } + + $info[$infoindex] .= "$type $name"; + if (isset($package['uri'])) { + $info[$infoindex] .= "\n Download URI: $package[uri]"; + continue; + } + + if ($infoindex == 'Not Compatible with') { + // conflicts is only used to say that all versions conflict + continue; + } + + if (isset($package['max']) && isset($package['min'])) { + $info[$infoindex] .= " \n Versions " . + $package['min'] . '-' . $package['max']; + } elseif (isset($package['min'])) { + $info[$infoindex] .= " \n Version " . + $package['min'] . ' or newer'; + } elseif (isset($package['max'])) { + $info[$infoindex] .= " \n Version " . + $package['min'] . ' or older'; + } + + if (isset($package['recommended'])) { + $info[$infoindex] .= "\n Recommended version: $package[recommended]"; + } + + if (isset($package['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + + $info['Not Compatible with'] .= "Package $package\n Versions " . + $package['exclude']; + } + } + } + } + } + + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + foreach ($deps['group'] as $group) { + $info['Dependency Group ' . $group['attribs']['name']] = $group['attribs']['hint']; + $groupindex = $group['attribs']['name'] . ' Contents'; + $info[$groupindex] = ''; + foreach (array('Package', 'Extension') as $type) { + $index = strtolower($type); + if (isset($group[$index])) { + if (isset($group[$index]['name'])) { + $group[$index] = array($group[$index]); + } + + foreach ($group[$index] as $package) { + if (!empty($info[$groupindex])) { + $info[$groupindex] .= "\n"; + } + + if ($index == 'extension') { + $name = $package['name']; + } else { + if (isset($package['channel'])) { + $name = $package['channel'] . '/' . $package['name']; + } else { + $name = '__uri/' . $package['name'] . ' (static URI)'; + } + } + + if (isset($package['uri'])) { + if (isset($package['conflicts']) && $package['conflicts'] == 'yes') { + $info[$groupindex] .= "Not Compatible with $type $name"; + } else { + $info[$groupindex] .= "$type $name"; + } + + $info[$groupindex] .= "\n Download URI: $package[uri]"; + continue; + } + + if (isset($package['conflicts']) && $package['conflicts'] == 'yes') { + $info[$groupindex] .= "Not Compatible with $type $name"; + continue; + } + + $info[$groupindex] .= "$type $name"; + if (isset($package['max']) && isset($package['min'])) { + $info[$groupindex] .= " \n Versions " . + $package['min'] . '-' . $package['max']; + } elseif (isset($package['min'])) { + $info[$groupindex] .= " \n Version " . + $package['min'] . ' or newer'; + } elseif (isset($package['max'])) { + $info[$groupindex] .= " \n Version " . + $package['min'] . ' or older'; + } + + if (isset($package['recommended'])) { + $info[$groupindex] .= "\n Recommended version: $package[recommended]"; + } + + if (isset($package['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info[$groupindex] .= "Not Compatible with\n"; + } + + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + $info[$groupindex] .= " Package $package\n Versions " . + $package['exclude']; + } + } + } + } + } + } + + if ($obj->getPackageType() == 'bundle') { + $info['Bundled Packages'] = ''; + foreach ($obj->getBundledPackages() as $package) { + if (!empty($info['Bundled Packages'])) { + $info['Bundled Packages'] .= "\n"; + } + + if (isset($package['uri'])) { + $info['Bundled Packages'] .= '__uri/' . $package['name']; + $info['Bundled Packages'] .= "\n (URI: $package[uri]"; + } else { + $info['Bundled Packages'] .= $package['channel'] . '/' . $package['name']; + } + } + } + + $info['package.xml version'] = '2.0'; + if ($installed) { + if ($obj->getLastModified()) { + $info['Last Modified'] = date('Y-m-d H:i', $obj->getLastModified()); + } + + $v = $obj->getLastInstalledVersion(); + $info['Previous Installed Version'] = $v ? $v : '- None -'; + } + + foreach ($info as $key => $value) { + $data['data'][] = array($key, $value); + } + + $data['raw'] = $obj->getArray(); // no validation needed + $this->ui->outputData($data, 'package-info'); + } +}PEAR-1.9.0/PEAR/Command/Remote.xml100664 764 764 6357 100664 11403 + + Information About Remote Packages + doRemoteInfo + ri + + <package> +Get details on a package from the server. + + + List Available Upgrades + doListUpgrades + lu + + + i + output fully channel-aware data, even on failure + + + [preferred_state] +List releases on the server of packages you have installed where +a newer version is available with the same release state (stable etc.) +or the state passed as the second parameter. + + + List Remote Packages + doRemoteList + rl + + + c + specify a channel other than the default channel + CHAN + + + +Lists the packages available on the configured server along with the +latest stable release of each package. + + + Search remote package database + doSearch + sp + + + c + specify a channel other than the default channel + CHAN + + + a + search packages from all known channels + + + i + output fully channel-aware data, even on failure + + + [packagename] [packageinfo] +Lists all packages which match the search parameters. The first +parameter is a fragment of a packagename. The default channel +will be used unless explicitly overridden. The second parameter +will be used to match any portion of the summary/description + + + List All Packages + doListAll + la + + + c + specify a channel other than the default channel + CHAN + + + i + output fully channel-aware data, even on failure + + + +Lists the packages available on the configured server along with the +latest stable release of each package. + + + Download Package + doDownload + d + + + Z + download an uncompressed (.tar) file + + + <package>... +Download package tarballs. The files will be named as suggested by the +server, for example if you download the DB package and the latest stable +version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz. + + + Clear Web Services Cache + doClearCache + cc + + +Clear the XML-RPC/REST cache. See also the cache_ttl configuration +parameter. + + +PEAR-1.9.0/PEAR/Command/Remote.php100664 764 764 72576 100664 11420 + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Remote.php 287477 2009-08-19 14:19:43Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; +require_once 'PEAR/REST.php'; + +/** + * PEAR commands for remote server querying + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Remote extends PEAR_Command_Common +{ + var $commands = array( + 'remote-info' => array( + 'summary' => 'Information About Remote Packages', + 'function' => 'doRemoteInfo', + 'shortcut' => 'ri', + 'options' => array(), + 'doc' => ' +Get details on a package from the server.', + ), + 'list-upgrades' => array( + 'summary' => 'List Available Upgrades', + 'function' => 'doListUpgrades', + 'shortcut' => 'lu', + 'options' => array( + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => '[preferred_state] +List releases on the server of packages you have installed where +a newer version is available with the same release state (stable etc.) +or the state passed as the second parameter.' + ), + 'remote-list' => array( + 'summary' => 'List Remote Packages', + 'function' => 'doRemoteList', + 'shortcut' => 'rl', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ) + ), + 'doc' => ' +Lists the packages available on the configured server along with the +latest stable release of each package.', + ), + 'search' => array( + 'summary' => 'Search remote package database', + 'function' => 'doSearch', + 'shortcut' => 'sp', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + 'allchannels' => array( + 'shortopt' => 'a', + 'doc' => 'search packages from all known channels', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => '[packagename] [packageinfo] +Lists all packages which match the search parameters. The first +parameter is a fragment of a packagename. The default channel +will be used unless explicitly overridden. The second parameter +will be used to match any portion of the summary/description', + ), + 'list-all' => array( + 'summary' => 'List All Packages', + 'function' => 'doListAll', + 'shortcut' => 'la', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => ' +Lists the packages available on the configured server along with the +latest stable release of each package.', + ), + 'download' => array( + 'summary' => 'Download Package', + 'function' => 'doDownload', + 'shortcut' => 'd', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'download an uncompressed (.tar) file', + ), + ), + 'doc' => '... +Download package tarballs. The files will be named as suggested by the +server, for example if you download the DB package and the latest stable +version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz.', + ), + 'clear-cache' => array( + 'summary' => 'Clear Web Services Cache', + 'function' => 'doClearCache', + 'shortcut' => 'cc', + 'options' => array(), + 'doc' => ' +Clear the XML-RPC/REST cache. See also the cache_ttl configuration +parameter. +', + ), + ); + + /** + * PEAR_Command_Remote constructor. + * + * @access public + */ + function PEAR_Command_Remote(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function _checkChannelForStatus($channel, $chan) + { + if (PEAR::isError($chan)) { + $this->raiseError($chan); + } + if (!is_a($chan, 'PEAR_ChannelFile')) { + return $this->raiseError('Internal corruption error: invalid channel "' . + $channel . '"'); + } + $rest = new PEAR_REST($this->config); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $mirror = $this->config->get('preferred_mirror', null, + $channel); + $a = $rest->downloadHttp('http://' . $channel . + '/channel.xml', $chan->lastModified()); + PEAR::staticPopErrorHandling(); + if (!PEAR::isError($a) && $a) { + $this->ui->outputData('WARNING: channel "' . $channel . '" has ' . + 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $channel . + '" to update'); + } + } + + function doRemoteInfo($command, $options, $params) + { + if (sizeof($params) != 1) { + return $this->raiseError("$command expects one param: the remote package name"); + } + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + $package = $params[0]; + $parsed = $reg->parsePackageName($package, $channel); + if (PEAR::isError($parsed)) { + return $this->raiseError('Invalid package name "' . $package . '"'); + } + + $channel = $parsed['channel']; + $this->config->set('default_channel', $channel); + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + $mirror = $this->config->get('preferred_mirror'); + if ($chan->supportsREST($mirror) && $base = $chan->getBaseURL('REST1.0', $mirror)) { + $rest = &$this->config->getREST('1.0', array()); + $info = $rest->packageInfo($base, $parsed['package'], $channel); + } + + if (!isset($info)) { + return $this->raiseError('No supported protocol was found'); + } + + if (PEAR::isError($info)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError($info); + } + + if (!isset($info['name'])) { + return $this->raiseError('No remote package "' . $package . '" was found'); + } + + $installed = $reg->packageInfo($info['name'], null, $channel); + $info['installed'] = $installed['version'] ? $installed['version'] : '- no -'; + if (is_array($info['installed'])) { + $info['installed'] = $info['installed']['release']; + } + + $this->ui->outputData($info, $command); + $this->config->set('default_channel', $savechannel); + + return true; + } + + function doRemoteList($command, $options, $params) + { + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (isset($options['channel'])) { + $channel = $options['channel']; + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $this->config->set('default_channel', $channel); + } + + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + $list_options = false; + if ($this->config->get('preferred_state') == 'stable') { + $list_options = true; + } + + $available = array(); + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror')) + ) { + // use faster list-all if available + $rest = &$this->config->getREST('1.1', array()); + $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName()); + } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName()); + } + + if (PEAR::isError($available)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError($available); + } + + $i = $j = 0; + $data = array( + 'caption' => 'Channel ' . $channel . ' Available packages:', + 'border' => true, + 'headline' => array('Package', 'Version'), + 'channel' => $channel + ); + + if (count($available) == 0) { + $data = '(no packages available yet)'; + } else { + foreach ($available as $name => $info) { + $version = (isset($info['stable']) && $info['stable']) ? $info['stable'] : '-n/a-'; + $data['data'][] = array($name, $version); + } + } + $this->ui->outputData($data, $command); + $this->config->set('default_channel', $savechannel); + return true; + } + + function doListAll($command, $options, $params) + { + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (isset($options['channel'])) { + $channel = $options['channel']; + if (!$reg->channelExists($channel)) { + return $this->raiseError("Channel \"$channel\" does not exist"); + } + + $this->config->set('default_channel', $channel); + } + + $list_options = false; + if ($this->config->get('preferred_state') == 'stable') { + $list_options = true; + } + + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror'))) { + // use faster list-all if available + $rest = &$this->config->getREST('1.1', array()); + $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName()); + } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName()); + } + + if (PEAR::isError($available)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError('The package list could not be fetched from the remote server. Please try again. (Debug info: "' . $available->getMessage() . '")'); + } + + $data = array( + 'caption' => 'All packages [Channel ' . $channel . ']:', + 'border' => true, + 'headline' => array('Package', 'Latest', 'Local'), + 'channel' => $channel, + ); + + if (isset($options['channelinfo'])) { + // add full channelinfo + $data['caption'] = 'Channel ' . $channel . ' All packages:'; + $data['headline'] = array('Channel', 'Package', 'Latest', 'Local', + 'Description', 'Dependencies'); + } + $local_pkgs = $reg->listPackages($channel); + + foreach ($available as $name => $info) { + $installed = $reg->packageInfo($name, null, $channel); + if (is_array($installed['version'])) { + $installed['version'] = $installed['version']['release']; + } + $desc = $info['summary']; + if (isset($params[$name])) { + $desc .= "\n\n".$info['description']; + } + if (isset($options['mode'])) + { + if ($options['mode'] == 'installed' && !isset($installed['version'])) { + continue; + } + if ($options['mode'] == 'notinstalled' && isset($installed['version'])) { + continue; + } + if ($options['mode'] == 'upgrades' + && (!isset($installed['version']) || version_compare($installed['version'], + $info['stable'], '>='))) { + continue; + } + } + $pos = array_search(strtolower($name), $local_pkgs); + if ($pos !== false) { + unset($local_pkgs[$pos]); + } + + if (isset($info['stable']) && !$info['stable']) { + $info['stable'] = null; + } + + if (isset($options['channelinfo'])) { + // add full channelinfo + if ($info['stable'] === $info['unstable']) { + $state = $info['state']; + } else { + $state = 'stable'; + } + $latest = $info['stable'].' ('.$state.')'; + $local = ''; + if (isset($installed['version'])) { + $inst_state = $reg->packageInfo($name, 'release_state', $channel); + $local = $installed['version'].' ('.$inst_state.')'; + } + + $packageinfo = array( + $channel, + $name, + $latest, + $local, + isset($desc) ? $desc : null, + isset($info['deps']) ? $info['deps'] : null, + ); + } else { + $packageinfo = array( + $reg->channelAlias($channel) . '/' . $name, + isset($info['stable']) ? $info['stable'] : null, + isset($installed['version']) ? $installed['version'] : null, + isset($desc) ? $desc : null, + isset($info['deps']) ? $info['deps'] : null, + ); + } + $data['data'][$info['category']][] = $packageinfo; + } + + if (isset($options['mode']) && in_array($options['mode'], array('notinstalled', 'upgrades'))) { + $this->config->set('default_channel', $savechannel); + $this->ui->outputData($data, $command); + return true; + } + + foreach ($local_pkgs as $name) { + $info = &$reg->getPackage($name, $channel); + $data['data']['Local'][] = array( + $reg->channelAlias($channel) . '/' . $info->getPackage(), + '', + $info->getVersion(), + $info->getSummary(), + $info->getDeps() + ); + } + + $this->config->set('default_channel', $savechannel); + $this->ui->outputData($data, $command); + return true; + } + + function doSearch($command, $options, $params) + { + if ((!isset($params[0]) || empty($params[0])) + && (!isset($params[1]) || empty($params[1]))) + { + return $this->raiseError('no valid search string supplied'); + } + + $channelinfo = isset($options['channelinfo']); + $reg = &$this->config->getRegistry(); + if (isset($options['allchannels'])) { + // search all channels + unset($options['allchannels']); + $channels = $reg->getChannels(); + $errors = array(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + foreach ($channels as $channel) { + if ($channel->getName() != '__uri') { + $options['channel'] = $channel->getName(); + $ret = $this->doSearch($command, $options, $params); + if (PEAR::isError($ret)) { + $errors[] = $ret; + } + } + } + + PEAR::staticPopErrorHandling(); + if (count($errors) !== 0) { + // for now, only give first error + return PEAR::raiseError($errors[0]); + } + + return true; + } + + $savechannel = $channel = $this->config->get('default_channel'); + $package = strtolower($params[0]); + $summary = isset($params[1]) ? $params[1] : false; + if (isset($options['channel'])) { + $reg = &$this->config->getRegistry(); + $channel = $options['channel']; + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $this->config->set('default_channel', $channel); + } + + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $available = $rest->listAll($base, false, false, $package, $summary, $chan->getName()); + } + + if (PEAR::isError($available)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError($available); + } + + if (!$available && !$channelinfo) { + // clean exit when not found, no error ! + $data = 'no packages found that match pattern "' . $package . '", for channel '.$channel.'.'; + $this->ui->outputData($data); + $this->config->set('default_channel', $channel); + return true; + } + + if ($channelinfo) { + $data = array( + 'caption' => 'Matched packages, channel ' . $channel . ':', + 'border' => true, + 'headline' => array('Channel', 'Package', 'Stable/(Latest)', 'Local'), + 'channel' => $channel + ); + } else { + $data = array( + 'caption' => 'Matched packages, channel ' . $channel . ':', + 'border' => true, + 'headline' => array('Package', 'Stable/(Latest)', 'Local'), + 'channel' => $channel + ); + } + + if (!$available && $channelinfo) { + unset($data['headline']); + $data['data'] = 'No packages found that match pattern "' . $package . '".'; + $available = array(); + } + + foreach ($available as $name => $info) { + $installed = $reg->packageInfo($name, null, $channel); + $desc = $info['summary']; + if (isset($params[$name])) + $desc .= "\n\n".$info['description']; + + if (!isset($info['stable']) || !$info['stable']) { + $version_remote = 'none'; + } else { + if ($info['unstable']) { + $version_remote = $info['unstable']; + } else { + $version_remote = $info['stable']; + } + $version_remote .= ' ('.$info['state'].')'; + } + $version = is_array($installed['version']) ? $installed['version']['release'] : + $installed['version']; + if ($channelinfo) { + $packageinfo = array( + $channel, + $name, + $version_remote, + $version, + $desc, + ); + } else { + $packageinfo = array( + $name, + $version_remote, + $version, + $desc, + ); + } + $data['data'][$info['category']][] = $packageinfo; + } + + $this->ui->outputData($data, $command); + $this->config->set('default_channel', $channel); + return true; + } + + function &getDownloader($options) + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + $a = &new PEAR_Downloader($this->ui, $options, $this->config); + return $a; + } + + function doDownload($command, $options, $params) + { + // make certain that dependencies are ignored + $options['downloadonly'] = 1; + + // eliminate error messages for preferred_state-related errors + /* TODO: Should be an option, but until now download does respect + prefered state */ + /* $options['ignorepreferred_state'] = 1; */ + // eliminate error messages for preferred_state-related errors + + $downloader = &$this->getDownloader($options); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $e = $downloader->setDownloadDir(getcwd()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($e)) { + return $this->raiseError('Current directory is not writeable, cannot download'); + } + + $errors = array(); + $downloaded = array(); + $err = $downloader->download($params); + if (PEAR::isError($err)) { + return $err; + } + + $errors = $downloader->getErrorMsgs(); + if (count($errors)) { + foreach ($errors as $error) { + if ($error !== null) { + $this->ui->outputData($error); + } + } + + return $this->raiseError("$command failed"); + } + + $downloaded = $downloader->getDownloadedPackages(); + foreach ($downloaded as $pkg) { + $this->ui->outputData("File $pkg[file] downloaded", $command); + } + + return true; + } + + function downloadCallback($msg, $params = null) + { + if ($msg == 'done') { + $this->bytes_downloaded = $params; + } + } + + function doListUpgrades($command, $options, $params) + { + require_once 'PEAR/Common.php'; + if (isset($params[0]) && !is_array(PEAR_Common::betterStates($params[0]))) { + return $this->raiseError($params[0] . ' is not a valid state (stable/beta/alpha/devel/etc.) try "pear help list-upgrades"'); + } + + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + foreach ($reg->listChannels() as $channel) { + $inst = array_flip($reg->listPackages($channel)); + if (!count($inst)) { + continue; + } + + if ($channel == '__uri') { + continue; + } + + $this->config->set('default_channel', $channel); + $state = empty($params[0]) ? $this->config->get('preferred_state') : $params[0]; + + $caption = $channel . ' Available Upgrades'; + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + $latest = array(); + $base2 = false; + $preferred_mirror = $this->config->get('preferred_mirror'); + if ($chan->supportsREST($preferred_mirror) && + ( + //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) || + ($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + + ) { + if ($base2) { + $rest = &$this->config->getREST('1.4', array()); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', array()); + } + + if (empty($state) || $state == 'any') { + $state = false; + } else { + $caption .= ' (' . implode(', ', PEAR_Common::betterStates($state, true)) . ')'; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $latest = $rest->listLatestUpgrades($base, $state, $inst, $channel, $reg); + PEAR::staticPopErrorHandling(); + } + + if (PEAR::isError($latest)) { + $this->ui->outputData($latest->getMessage()); + continue; + } + + $caption .= ':'; + if (PEAR::isError($latest)) { + $this->config->set('default_channel', $savechannel); + return $latest; + } + + $data = array( + 'caption' => $caption, + 'border' => 1, + 'headline' => array('Channel', 'Package', 'Local', 'Remote', 'Size'), + 'channel' => $channel + ); + + foreach ((array)$latest as $pkg => $info) { + $package = strtolower($pkg); + if (!isset($inst[$package])) { + // skip packages we don't have installed + continue; + } + + extract($info); + $inst_version = $reg->packageInfo($package, 'version', $channel); + $inst_state = $reg->packageInfo($package, 'release_state', $channel); + if (version_compare("$version", "$inst_version", "le")) { + // installed version is up-to-date + continue; + } + + if ($filesize >= 20480) { + $filesize += 1024 - ($filesize % 1024); + $fs = sprintf("%dkB", $filesize / 1024); + } elseif ($filesize > 0) { + $filesize += 103 - ($filesize % 103); + $fs = sprintf("%.1fkB", $filesize / 1024.0); + } else { + $fs = " -"; // XXX center instead + } + + $data['data'][] = array($channel, $pkg, "$inst_version ($inst_state)", "$version ($state)", $fs); + } + + if (isset($options['channelinfo'])) { + if (empty($data['data'])) { + unset($data['headline']); + if (count($inst) == 0) { + $data['data'] = '(no packages installed)'; + } else { + $data['data'] = '(no upgrades available)'; + } + } + $this->ui->outputData($data, $command); + } else { + if (empty($data['data'])) { + $this->ui->outputData('Channel ' . $channel . ': No upgrades available'); + } else { + $this->ui->outputData($data, $command); + } + } + } + + $this->config->set('default_channel', $savechannel); + return true; + } + + function doClearCache($command, $options, $params) + { + $cache_dir = $this->config->get('cache_dir'); + $verbose = $this->config->get('verbose'); + $output = ''; + if (!file_exists($cache_dir) || !is_dir($cache_dir)) { + return $this->raiseError("$cache_dir does not exist or is not a directory"); + } + + if (!($dp = @opendir($cache_dir))) { + return $this->raiseError("opendir($cache_dir) failed: $php_errormsg"); + } + + if ($verbose >= 1) { + $output .= "reading directory $cache_dir\n"; + } + $num = 0; + while ($ent = readdir($dp)) { + if (preg_match('/rest.cache(file|id)\\z/', $ent)) { + $path = $cache_dir . DIRECTORY_SEPARATOR . $ent; + if (file_exists($path)) { + $ok = @unlink($path); + } else { + $ok = false; + $php_errormsg = ''; + } + + if ($ok) { + if ($verbose >= 2) { + $output .= "deleted $path\n"; + } + $num++; + } elseif ($verbose >= 1) { + $output .= "failed to delete $path $php_errormsg\n"; + } + } + } + + closedir($dp); + if ($verbose >= 1) { + $output .= "$num cache entries cleared\n"; + } + + $this->ui->outputData(rtrim($output), $command); + return $num; + } +}PEAR-1.9.0/PEAR/Command/Test.xml100664 764 764 3151 100664 11054 + + Run Regression Tests + doRunTests + rt + + + r + Run tests in child directories, recursively. 4 dirs deep maximum + + + i + actual string of settings to pass to php in format " -d setting=blah" + SETTINGS + + + l + Log test runs/results as they are run + + + q + Only display detail for failed tests + + + s + Display simple output for all tests + + + p + Treat parameters as installed packages from which to run tests + + + u + Search parameters for AllTests.php, and use that to run phpunit-based tests +If none is found, all .phpt tests will be tried instead. + + + t + Output run-tests.log in TAP-compliant format + + + c + CGI php executable (needed for tests with POST/GET section) + PHPCGI + + + x + Generate a code coverage report (requires Xdebug 2.0.0+) + + + [testfile|dir ...] +Run regression tests with PHP's regression testing script (run-tests.php). + +PEAR-1.9.0/PEAR/Command/Test.php100664 764 764 27267 100664 11101 + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Test.php 279072 2009-04-20 19:57:41Z cellog $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ + +class PEAR_Command_Test extends PEAR_Command_Common +{ + var $commands = array( + 'run-tests' => array( + 'summary' => 'Run Regression Tests', + 'function' => 'doRunTests', + 'shortcut' => 'rt', + 'options' => array( + 'recur' => array( + 'shortopt' => 'r', + 'doc' => 'Run tests in child directories, recursively. 4 dirs deep maximum', + ), + 'ini' => array( + 'shortopt' => 'i', + 'doc' => 'actual string of settings to pass to php in format " -d setting=blah"', + 'arg' => 'SETTINGS' + ), + 'realtimelog' => array( + 'shortopt' => 'l', + 'doc' => 'Log test runs/results as they are run', + ), + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Only display detail for failed tests', + ), + 'simple' => array( + 'shortopt' => 's', + 'doc' => 'Display simple output for all tests', + ), + 'package' => array( + 'shortopt' => 'p', + 'doc' => 'Treat parameters as installed packages from which to run tests', + ), + 'phpunit' => array( + 'shortopt' => 'u', + 'doc' => 'Search parameters for AllTests.php, and use that to run phpunit-based tests +If none is found, all .phpt tests will be tried instead.', + ), + 'tapoutput' => array( + 'shortopt' => 't', + 'doc' => 'Output run-tests.log in TAP-compliant format', + ), + 'cgi' => array( + 'shortopt' => 'c', + 'doc' => 'CGI php executable (needed for tests with POST/GET section)', + 'arg' => 'PHPCGI', + ), + 'coverage' => array( + 'shortopt' => 'x', + 'doc' => 'Generate a code coverage report (requires Xdebug 2.0.0+)', + ), + ), + 'doc' => '[testfile|dir ...] +Run regression tests with PHP\'s regression testing script (run-tests.php).', + ), + ); + + var $output; + + /** + * PEAR_Command_Test constructor. + * + * @access public + */ + function PEAR_Command_Test(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function doRunTests($command, $options, $params) + { + if (isset($options['phpunit']) && isset($options['tapoutput'])) { + return $this->raiseError('ERROR: cannot use both --phpunit and --tapoutput at the same time'); + } + + require_once 'PEAR/Common.php'; + require_once 'System.php'; + $log = new PEAR_Common; + $log->ui = &$this->ui; // slightly hacky, but it will work + $tests = array(); + $depth = isset($options['recur']) ? 14 : 1; + + if (!count($params)) { + $params[] = '.'; + } + + if (isset($options['package'])) { + $oldparams = $params; + $params = array(); + $reg = &$this->config->getRegistry(); + foreach ($oldparams as $param) { + $pname = $reg->parsePackageName($param, $this->config->get('default_channel')); + if (PEAR::isError($pname)) { + return $this->raiseError($pname); + } + + $package = &$reg->getPackage($pname['package'], $pname['channel']); + if (!$package) { + return PEAR::raiseError('Unknown package "' . + $reg->parsedPackageNameToString($pname) . '"'); + } + + $filelist = $package->getFilelist(); + foreach ($filelist as $name => $atts) { + if (isset($atts['role']) && $atts['role'] != 'test') { + continue; + } + + if (isset($options['phpunit']) && preg_match('/AllTests\.php\\z/i', $name)) { + $params[] = $atts['installed_as']; + continue; + } elseif (!preg_match('/\.phpt\\z/', $name)) { + continue; + } + $params[] = $atts['installed_as']; + } + } + } + + foreach ($params as $p) { + if (is_dir($p)) { + if (isset($options['phpunit'])) { + $dir = System::find(array($p, '-type', 'f', + '-maxdepth', $depth, + '-name', 'AllTests.php')); + if (count($dir)) { + foreach ($dir as $p) { + $p = realpath($p); + if (!count($tests) || + (count($tests) && strlen($p) < strlen($tests[0]))) { + // this is in a higher-level directory, use this one instead. + $tests = array($p); + } + } + } + continue; + } + + $args = array($p, '-type', 'f', '-name', '*.phpt'); + } else { + if (isset($options['phpunit'])) { + if (preg_match('/AllTests\.php\\z/i', $p)) { + $p = realpath($p); + if (!count($tests) || + (count($tests) && strlen($p) < strlen($tests[0]))) { + // this is in a higher-level directory, use this one instead. + $tests = array($p); + } + } + continue; + } + + if (file_exists($p) && preg_match('/\.phpt$/', $p)) { + $tests[] = $p; + continue; + } + + if (!preg_match('/\.phpt\\z/', $p)) { + $p .= '.phpt'; + } + + $args = array(dirname($p), '-type', 'f', '-name', $p); + } + + if (!isset($options['recur'])) { + $args[] = '-maxdepth'; + $args[] = 1; + } + + $dir = System::find($args); + $tests = array_merge($tests, $dir); + } + + $ini_settings = ''; + if (isset($options['ini'])) { + $ini_settings .= $options['ini']; + } + + if (isset($_ENV['TEST_PHP_INCLUDE_PATH'])) { + $ini_settings .= " -d include_path={$_ENV['TEST_PHP_INCLUDE_PATH']}"; + } + + if ($ini_settings) { + $this->ui->outputData('Using INI settings: "' . $ini_settings . '"'); + } + + $skipped = $passed = $failed = array(); + $tests_count = count($tests); + $this->ui->outputData('Running ' . $tests_count . ' tests', $command); + $start = time(); + if (isset($options['realtimelog']) && file_exists('run-tests.log')) { + unlink('run-tests.log'); + } + + if (isset($options['tapoutput'])) { + $tap = '1..' . $tests_count . "\n"; + } + + require_once 'PEAR/RunTest.php'; + $run = new PEAR_RunTest($log, $options); + $run->tests_count = $tests_count; + + if (isset($options['coverage']) && extension_loaded('xdebug')){ + $run->xdebug_loaded = true; + } else { + $run->xdebug_loaded = false; + } + + $j = $i = 1; + foreach ($tests as $t) { + if (isset($options['realtimelog'])) { + $fp = @fopen('run-tests.log', 'a'); + if ($fp) { + fwrite($fp, "Running test [$i / $tests_count] $t..."); + fclose($fp); + } + } + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (isset($options['phpunit'])) { + $result = $run->runPHPUnit($t, $ini_settings); + } else { + $result = $run->run($t, $ini_settings, $j); + } + PEAR::staticPopErrorHandling(); + if (PEAR::isError($result)) { + $this->ui->log($result->getMessage()); + continue; + } + + if (isset($options['tapoutput'])) { + $tap .= $result[0] . ' ' . $i . $result[1] . "\n"; + continue; + } + + if (isset($options['realtimelog'])) { + $fp = @fopen('run-tests.log', 'a'); + if ($fp) { + fwrite($fp, "$result\n"); + fclose($fp); + } + } + + if ($result == 'FAILED') { + $failed[] = $t; + } + if ($result == 'PASSED') { + $passed[] = $t; + } + if ($result == 'SKIPPED') { + $skipped[] = $t; + } + + $j++; + } + + $total = date('i:s', time() - $start); + if (isset($options['tapoutput'])) { + $fp = @fopen('run-tests.log', 'w'); + if ($fp) { + fwrite($fp, $tap, strlen($tap)); + fclose($fp); + $this->ui->outputData('wrote TAP-format log to "' .realpath('run-tests.log') . + '"', $command); + } + } else { + if (count($failed)) { + $output = "TOTAL TIME: $total\n"; + $output .= count($passed) . " PASSED TESTS\n"; + $output .= count($skipped) . " SKIPPED TESTS\n"; + $output .= count($failed) . " FAILED TESTS:\n"; + foreach ($failed as $failure) { + $output .= $failure . "\n"; + } + + $mode = isset($options['realtimelog']) ? 'a' : 'w'; + $fp = @fopen('run-tests.log', $mode); + + if ($fp) { + fwrite($fp, $output, strlen($output)); + fclose($fp); + $this->ui->outputData('wrote log to "' . realpath('run-tests.log') . '"', $command); + } + } elseif (file_exists('run-tests.log') && !is_dir('run-tests.log')) { + @unlink('run-tests.log'); + } + } + $this->ui->outputData('TOTAL TIME: ' . $total); + $this->ui->outputData(count($passed) . ' PASSED TESTS', $command); + $this->ui->outputData(count($skipped) . ' SKIPPED TESTS', $command); + if (count($failed)) { + $this->ui->outputData(count($failed) . ' FAILED TESTS:', $command); + foreach ($failed as $failure) { + $this->ui->outputData($failure, $command); + } + } + + return true; + } +}PEAR-1.9.0/PEAR/Downloader/Package.php100664 764 764 226171 100664 12250 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Package.php 287560 2009-08-21 22:36:18Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Error code when parameter initialization fails because no releases + * exist within preferred_state, but releases do exist + */ +define('PEAR_DOWNLOADER_PACKAGE_STATE', -1003); +/** + * Error code when parameter initialization fails because no releases + * exist that will work with the existing PHP version + */ +define('PEAR_DOWNLOADER_PACKAGE_PHPVERSION', -1004); + +/** + * Coordinates download parameters and manages their dependencies + * prior to downloading them. + * + * Input can come from three sources: + * + * - local files (archives or package.xml) + * - remote files (downloadable urls) + * - abstract package names + * + * The first two elements are handled cleanly by PEAR_PackageFile, but the third requires + * accessing pearweb's xml-rpc interface to determine necessary dependencies, and the + * format returned of dependencies is slightly different from that used in package.xml. + * + * This class hides the differences between these elements, and makes automatic + * dependency resolution a piece of cake. It also manages conflicts when + * two classes depend on incompatible dependencies, or differing versions of the same + * package dependency. In addition, download will not be attempted if the php version is + * not supported, PEAR installer version is not supported, or non-PECL extensions are not + * installed. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Downloader_Package +{ + /** + * @var PEAR_Downloader + */ + var $_downloader; + /** + * @var PEAR_Config + */ + var $_config; + /** + * @var PEAR_Registry + */ + var $_registry; + /** + * Used to implement packagingroot properly + * @var PEAR_Registry + */ + var $_installRegistry; + /** + * @var PEAR_PackageFile_v1|PEAR_PackageFile|v2 + */ + var $_packagefile; + /** + * @var array + */ + var $_parsedname; + /** + * @var array + */ + var $_downloadURL; + /** + * @var array + */ + var $_downloadDeps = array(); + /** + * @var boolean + */ + var $_valid = false; + /** + * @var boolean + */ + var $_analyzed = false; + /** + * if this or a parent package was invoked with Package-state, this is set to the + * state variable. + * + * This allows temporary reassignment of preferred_state for a parent package and all of + * its dependencies. + * @var string|false + */ + var $_explicitState = false; + /** + * If this package is invoked with Package#group, this variable will be true + */ + var $_explicitGroup = false; + /** + * Package type local|url + * @var string + */ + var $_type; + /** + * Contents of package.xml, if downloaded from a remote channel + * @var string|false + * @access private + */ + var $_rawpackagefile; + /** + * @var boolean + * @access private + */ + var $_validated = false; + + /** + * @param PEAR_Downloader + */ + function PEAR_Downloader_Package(&$downloader) + { + $this->_downloader = &$downloader; + $this->_config = &$this->_downloader->config; + $this->_registry = &$this->_config->getRegistry(); + $options = $downloader->getOptions(); + if (isset($options['packagingroot'])) { + $this->_config->setInstallRoot($options['packagingroot']); + $this->_installRegistry = &$this->_config->getRegistry(); + $this->_config->setInstallRoot(false); + } else { + $this->_installRegistry = &$this->_registry; + } + $this->_valid = $this->_analyzed = false; + } + + /** + * Parse the input and determine whether this is a local file, a remote uri, or an + * abstract package name. + * + * This is the heart of the PEAR_Downloader_Package(), and is used in + * {@link PEAR_Downloader::download()} + * @param string + * @return bool|PEAR_Error + */ + function initialize($param) + { + $origErr = $this->_fromFile($param); + if ($this->_valid) { + return true; + } + + $options = $this->_downloader->getOptions(); + if (isset($options['offline'])) { + if (PEAR::isError($origErr) && !isset($options['soft'])) { + foreach ($origErr->getUserInfo() as $userInfo) { + if (isset($userInfo['message'])) { + $this->_downloader->log(0, $userInfo['message']); + } + } + + $this->_downloader->log(0, $origErr->getMessage()); + } + + return PEAR::raiseError('Cannot download non-local package "' . $param . '"'); + } + + $err = $this->_fromUrl($param); + if (PEAR::isError($err) || !$this->_valid) { + if ($this->_type == 'url') { + if (PEAR::isError($err) && !isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + return PEAR::raiseError("Invalid or missing remote package file"); + } + + $err = $this->_fromString($param); + if (PEAR::isError($err) || !$this->_valid) { + if (PEAR::isError($err) && $err->getCode() == PEAR_DOWNLOADER_PACKAGE_STATE) { + return false; // instruct the downloader to silently skip + } + + if (isset($this->_type) && $this->_type == 'local' && PEAR::isError($origErr)) { + if (is_array($origErr->getUserInfo())) { + foreach ($origErr->getUserInfo() as $err) { + if (is_array($err)) { + $err = $err['message']; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $err); + } + } + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $origErr->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param, true); + } + + if (!isset($options['soft'])) { + $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file"); + } + + // Passing no message back - already logged above + return PEAR::raiseError(); + } + + if (PEAR::isError($err) && !isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param, true); + } + + if (!isset($options['soft'])) { + $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file"); + } + + // Passing no message back - already logged above + return PEAR::raiseError(); + } + } + + return true; + } + + /** + * Retrieve any non-local packages + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|PEAR_Error + */ + function &download() + { + if (isset($this->_packagefile)) { + return $this->_packagefile; + } + + if (isset($this->_downloadURL['url'])) { + $this->_isvalid = false; + $info = $this->getParsedPackage(); + foreach ($info as $i => $p) { + $info[$i] = strtolower($p); + } + + $err = $this->_fromUrl($this->_downloadURL['url'], + $this->_registry->parsedPackageNameToString($this->_parsedname, true)); + $newinfo = $this->getParsedPackage(); + foreach ($newinfo as $i => $p) { + $newinfo[$i] = strtolower($p); + } + + if ($info != $newinfo) { + do { + if ($info['channel'] == 'pecl.php.net' && $newinfo['channel'] == 'pear.php.net') { + $info['channel'] = 'pear.php.net'; + if ($info == $newinfo) { + // skip the channel check if a pecl package says it's a PEAR package + break; + } + } + if ($info['channel'] == 'pear.php.net' && $newinfo['channel'] == 'pecl.php.net') { + $info['channel'] = 'pecl.php.net'; + if ($info == $newinfo) { + // skip the channel check if a pecl package says it's a PEAR package + break; + } + } + + return PEAR::raiseError('CRITICAL ERROR: We are ' . + $this->_registry->parsedPackageNameToString($info) . ', but the file ' . + 'downloaded claims to be ' . + $this->_registry->parsedPackageNameToString($this->getParsedPackage())); + } while (false); + } + + if (PEAR::isError($err) || !$this->_valid) { + return $err; + } + } + + $this->_type = 'local'; + return $this->_packagefile; + } + + function &getPackageFile() + { + return $this->_packagefile; + } + + function &getDownloader() + { + return $this->_downloader; + } + + function getType() + { + return $this->_type; + } + + /** + * Like {@link initialize()}, but operates on a dependency + */ + function fromDepURL($dep) + { + $this->_downloadURL = $dep; + if (isset($dep['uri'])) { + $options = $this->_downloader->getOptions(); + if (!extension_loaded("zlib") || isset($options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->_fromUrl($dep['uri'] . $ext); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + return PEAR::raiseError('Invalid uri dependency "' . $dep['uri'] . $ext . '", ' . + 'cannot download'); + } + } else { + $this->_parsedname = + array( + 'package' => $dep['info']->getPackage(), + 'channel' => $dep['info']->getChannel(), + 'version' => $dep['version'] + ); + if (!isset($dep['nodefault'])) { + $this->_parsedname['group'] = 'default'; // download the default dependency group + $this->_explicitGroup = false; + } + + $this->_rawpackagefile = $dep['raw']; + } + } + + function detectDependencies($params) + { + $options = $this->_downloader->getOptions(); + if (isset($options['downloadonly'])) { + return; + } + + if (isset($options['offline'])) { + $this->_downloader->log(3, 'Skipping dependency download check, --offline specified'); + return; + } + + $pname = $this->getParsedPackage(); + if (!$pname) { + return; + } + + $deps = $this->getDeps(); + if (!$deps) { + return; + } + + if (isset($deps['required'])) { // package.xml 2.0 + return $this->_detect2($deps, $pname, $options, $params); + } + + return $this->_detect1($deps, $pname, $options, $params); + } + + function setValidated() + { + $this->_validated = true; + } + + function alreadyValidated() + { + return $this->_validated; + } + + /** + * Remove packages to be downloaded that are already installed + * @param array of PEAR_Downloader_Package objects + * @static + */ + function removeInstalled(&$params) + { + if (!isset($params[0])) { + return; + } + + $options = $params[0]->_downloader->getOptions(); + if (!isset($options['downloadonly'])) { + foreach ($params as $i => $param) { + $package = $param->getPackage(); + $channel = $param->getChannel(); + // remove self if already installed with this version + // this does not need any pecl magic - we only remove exact matches + if ($param->_installRegistry->packageExists($package, $channel)) { + $packageVersion = $param->_installRegistry->packageInfo($package, 'version', $channel); + if (version_compare($packageVersion, $param->getVersion(), '==')) { + if (!isset($options['force'])) { + $info = $param->getParsedPackage(); + unset($info['version']); + unset($info['state']); + if (!isset($options['soft'])) { + $param->_downloader->log(1, 'Skipping package "' . + $param->getShortName() . + '", already installed as version ' . $packageVersion); + } + $params[$i] = false; + } + } elseif (!isset($options['force']) && !isset($options['upgrade']) && + !isset($options['soft'])) { + $info = $param->getParsedPackage(); + $param->_downloader->log(1, 'Skipping package "' . + $param->getShortName() . + '", already installed as version ' . $packageVersion); + $params[$i] = false; + } + } + } + } + + PEAR_Downloader_Package::removeDuplicates($params); + } + + function _detect2($deps, $pname, $options, $params) + { + $this->_downloadDeps = array(); + $groupnotfound = false; + foreach (array('package', 'subpackage') as $packagetype) { + // get required dependency group + if (isset($deps['required'][$packagetype])) { + if (isset($deps['required'][$packagetype][0])) { + foreach ($deps['required'][$packagetype] as $dep) { + if (isset($dep['conflicts'])) { + // skip any package that this package conflicts with + continue; + } + $ret = $this->_detect2Dep($dep, $pname, 'required', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } else { + $dep = $deps['required'][$packagetype]; + if (!isset($dep['conflicts'])) { + // skip any package that this package conflicts with + $ret = $this->_detect2Dep($dep, $pname, 'required', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } + } + + // get optional dependency group, if any + if (isset($deps['optional'][$packagetype])) { + $skipnames = array(); + if (!isset($deps['optional'][$packagetype][0])) { + $deps['optional'][$packagetype] = array($deps['optional'][$packagetype]); + } + + foreach ($deps['optional'][$packagetype] as $dep) { + $skip = false; + if (!isset($options['alldeps'])) { + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->_registry->parsedPackageNameToString($this->getParsedPackage(), + true) . '" optional dependency "' . + $this->_registry->parsedPackageNameToString(array('package' => + $dep['name'], 'channel' => 'pear.php.net'), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString($dep, true); + $skip = true; + unset($dep['package']); + } + + $ret = $this->_detect2Dep($dep, $pname, 'optional', $params); + if (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + + if (!$ret) { + $dep['package'] = $dep['name']; + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + } + + if (!$skip && is_array($ret)) { + $this->_downloadDeps[] = $ret; + } + } + + if (count($skipnames)) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'Did not download optional dependencies: ' . + implode(', ', $skipnames) . + ', use --alldeps to download automatically'); + } + } + } + + // get requested dependency group, if any + $groupname = $this->getGroup(); + $explicit = $this->_explicitGroup; + if (!$groupname) { + if (!$this->canDefault()) { + continue; + } + + $groupname = 'default'; // try the default dependency group + } + + if ($groupnotfound) { + continue; + } + + if (isset($deps['group'])) { + if (isset($deps['group']['attribs'])) { + if (strtolower($deps['group']['attribs']['name']) == strtolower($groupname)) { + $group = $deps['group']; + } elseif ($explicit) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Warning: package "' . + $this->_registry->parsedPackageNameToString($pname, true) . + '" has no dependency ' . 'group named "' . $groupname . '"'); + } + + $groupnotfound = true; + continue; + } + } else { + $found = false; + foreach ($deps['group'] as $group) { + if (strtolower($group['attribs']['name']) == strtolower($groupname)) { + $found = true; + break; + } + } + + if (!$found) { + if ($explicit) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Warning: package "' . + $this->_registry->parsedPackageNameToString($pname, true) . + '" has no dependency ' . 'group named "' . $groupname . '"'); + } + } + + $groupnotfound = true; + continue; + } + } + } + + if (isset($group) && isset($group[$packagetype])) { + if (isset($group[$packagetype][0])) { + foreach ($group[$packagetype] as $dep) { + $ret = $this->_detect2Dep($dep, $pname, 'dependency group "' . + $group['attribs']['name'] . '"', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } else { + $ret = $this->_detect2Dep($group[$packagetype], $pname, + 'dependency group "' . + $group['attribs']['name'] . '"', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } + } + } + + function _detect2Dep($dep, $pname, $group, $params) + { + if (isset($dep['conflicts'])) { + return true; + } + + $options = $this->_downloader->getOptions(); + if (isset($dep['uri'])) { + return array('uri' => $dep['uri'], 'dep' => $dep);; + } + + $testdep = $dep; + $testdep['package'] = $dep['name']; + if (PEAR_Downloader_Package::willDownload($testdep, $params)) { + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", will be installed'); + } + return false; + } + + $options = $this->_downloader->getOptions(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if ($this->_explicitState) { + $pname['state'] = $this->_explicitState; + } + + $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname); + if (PEAR::isError($url)) { + PEAR::popErrorHandling(); + return $url; + } + + $dep['package'] = $dep['name']; + $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params, $group == 'optional' && + !isset($options['alldeps']), true); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + + return false; + } + + // check to see if a dep is already installed and is the same or newer + if (!isset($dep['min']) && !isset($dep['max']) && !isset($dep['recommended'])) { + $oper = 'has'; + } else { + $oper = 'gt'; + } + + // do not try to move this before getDepPackageDownloadURL + // we can't determine whether upgrade is necessary until we know what + // version would be downloaded + if (!isset($options['force']) && $this->isInstalled($ret, $oper)) { + $version = $this->_installRegistry->packageInfo($dep['name'], 'version', $dep['channel']); + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '" version ' . $url['version'] . ', already installed as version ' . + $version); + } + + return false; + } + + if (isset($dep['nodefault'])) { + $ret['nodefault'] = true; + } + + return $ret; + } + + function _detect1($deps, $pname, $options, $params) + { + $this->_downloadDeps = array(); + $skipnames = array(); + foreach ($deps as $dep) { + $nodownload = false; + if (isset ($dep['type']) && $dep['type'] === 'pkg') { + $dep['channel'] = 'pear.php.net'; + $dep['package'] = $dep['name']; + switch ($dep['rel']) { + case 'not' : + continue 2; + case 'ge' : + case 'eq' : + case 'gt' : + case 'has' : + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + if (PEAR_Downloader_Package::willDownload($dep, $params)) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group + . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", will be installed'); + continue 2; + } + $fakedp = new PEAR_PackageFile_v1; + $fakedp->setPackage($dep['name']); + // skip internet check if we are not upgrading (bug #5810) + if (!isset($options['upgrade']) && $this->isInstalled( + $fakedp, $dep['rel'])) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group + . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", is already installed'); + continue 2; + } + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if ($this->_explicitState) { + $pname['state'] = $this->_explicitState; + } + + $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname); + $chan = 'pear.php.net'; + if (PEAR::isError($url)) { + // check to see if this is a pecl package that has jumped + // from pear.php.net to pecl.php.net channel + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + $newdep = PEAR_Dependency2::normalizeDep($dep); + $newdep = $newdep[0]; + $newdep['channel'] = 'pecl.php.net'; + $chan = 'pecl.php.net'; + $url = $this->_downloader->_getDepPackageDownloadUrl($newdep, $pname); + $obj = &$this->_installRegistry->getPackage($dep['name']); + if (PEAR::isError($url)) { + PEAR::popErrorHandling(); + if ($obj !== null && $this->isInstalled($obj, $dep['rel'])) { + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . + ': Skipping ' . $group . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", already installed as version ' . $obj->getVersion()); + } + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + continue; + } else { + if (isset($dep['optional']) && $dep['optional'] == 'yes') { + $this->_downloader->log(2, $this->getShortName() . + ': Skipping optional dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", no releases exist'); + continue; + } else { + return $url; + } + } + } + } + + PEAR::popErrorHandling(); + if (!isset($options['alldeps'])) { + if (isset($dep['optional']) && $dep['optional'] == 'yes') { + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->getShortName() . + '" optional dependency "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true); + $nodownload = true; + } + } + + if (!isset($options['alldeps']) && !isset($options['onlyreqdeps'])) { + if (!isset($dep['optional']) || $dep['optional'] == 'no') { + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->getShortName() . + '" required dependency "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true); + $nodownload = true; + } + } + + // check to see if a dep is already installed + // do not try to move this before getDepPackageDownloadURL + // we can't determine whether upgrade is necessary until we know what + // version would be downloaded + if (!isset($options['force']) && $this->isInstalled( + $url, $dep['rel'])) { + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + $dep['package'] = $dep['name']; + if (isset($newdep)) { + $version = $this->_installRegistry->packageInfo($newdep['name'], 'version', $newdep['channel']); + } else { + $version = $this->_installRegistry->packageInfo($dep['name'], 'version'); + } + + $dep['version'] = $url['version']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", already installed as version ' . $version); + } + + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + + continue; + } + + if ($nodownload) { + continue; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (isset($newdep)) { + $dep = $newdep; + } + + $dep['package'] = $dep['name']; + $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params, + isset($dep['optional']) && $dep['optional'] == 'yes' && + !isset($options['alldeps']), true); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + continue; + } + + $this->_downloadDeps[] = $ret; + } + } + + if (count($skipnames)) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'Did not download dependencies: ' . + implode(', ', $skipnames) . + ', use --alldeps or --onlyreqdeps to download automatically'); + } + } + } + + function setDownloadURL($pkg) + { + $this->_downloadURL = $pkg; + } + + /** + * Set the package.xml object for this downloaded package + * + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 $pkg + */ + function setPackageFile(&$pkg) + { + $this->_packagefile = &$pkg; + } + + function getShortName() + { + return $this->_registry->parsedPackageNameToString(array('channel' => $this->getChannel(), + 'package' => $this->getPackage()), true); + } + + function getParsedPackage() + { + if (isset($this->_packagefile) || isset($this->_parsedname)) { + return array('channel' => $this->getChannel(), + 'package' => $this->getPackage(), + 'version' => $this->getVersion()); + } + + return false; + } + + function getDownloadURL() + { + return $this->_downloadURL; + } + + function canDefault() + { + if (isset($this->_downloadURL) && isset($this->_downloadURL['nodefault'])) { + return false; + } + + return true; + } + + function getPackage() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackage(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackage(); + } + + return false; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function isSubpackage(&$pf) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isSubpackage($pf); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->isSubpackage($pf); + } + + return false; + } + + function getPackageType() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackageType(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackageType(); + } + + return false; + } + + function isBundle() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackageType() == 'bundle'; + } + + return false; + } + + function getPackageXmlVersion() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackagexmlVersion(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackagexmlVersion(); + } + + return '1.0'; + } + + function getChannel() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getChannel(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getChannel(); + } + + return false; + } + + function getURI() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getURI(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getURI(); + } + + return false; + } + + function getVersion() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getVersion(); + } elseif (isset($this->_downloadURL['version'])) { + return $this->_downloadURL['version']; + } + + return false; + } + + function isCompatible($pf) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isCompatible($pf); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->isCompatible($pf); + } + + return true; + } + + function setGroup($group) + { + $this->_parsedname['group'] = $group; + } + + function getGroup() + { + if (isset($this->_parsedname['group'])) { + return $this->_parsedname['group']; + } + + return ''; + } + + function isExtension($name) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isExtension($name); + } elseif (isset($this->_downloadURL['info'])) { + if ($this->_downloadURL['info']->getPackagexmlVersion() == '2.0') { + return $this->_downloadURL['info']->getProvidesExtension() == $name; + } + + return false; + } + + return false; + } + + function getDeps() + { + if (isset($this->_packagefile)) { + $ver = $this->_packagefile->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + return $this->_packagefile->getDeps(true); + } + + return $this->_packagefile->getDeps(); + } elseif (isset($this->_downloadURL['info'])) { + $ver = $this->_downloadURL['info']->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + return $this->_downloadURL['info']->getDeps(true); + } + + return $this->_downloadURL['info']->getDeps(); + } + + return array(); + } + + /** + * @param array Parsed array from {@link PEAR_Registry::parsePackageName()} or a dependency + * returned from getDepDownloadURL() + */ + function isEqual($param) + { + if (is_object($param)) { + $channel = $param->getChannel(); + $package = $param->getPackage(); + if ($param->getURI()) { + $param = array( + 'channel' => $param->getChannel(), + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + 'uri' => $param->getURI(), + ); + } else { + $param = array( + 'channel' => $param->getChannel(), + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + ); + } + } else { + if (isset($param['uri'])) { + if ($this->getChannel() != '__uri') { + return false; + } + return $param['uri'] == $this->getURI(); + } + + $package = isset($param['package']) ? $param['package'] : $param['info']->getPackage(); + $channel = isset($param['channel']) ? $param['channel'] : $param['info']->getChannel(); + if (isset($param['rel'])) { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + $newdep = PEAR_Dependency2::normalizeDep($param); + $newdep = $newdep[0]; + } elseif (isset($param['min'])) { + $newdep = $param; + } + } + + if (isset($newdep)) { + if (!isset($newdep['min'])) { + $newdep['min'] = '0'; + } + + if (!isset($newdep['max'])) { + $newdep['max'] = '100000000000000000000'; + } + + // use magic to support pecl packages suddenly jumping to the pecl channel + // we need to support both dependency possibilities + if ($channel == 'pear.php.net' && $this->getChannel() == 'pecl.php.net') { + if ($package == $this->getPackage()) { + $channel = 'pecl.php.net'; + } + } + if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') { + if ($package == $this->getPackage()) { + $channel = 'pear.php.net'; + } + } + + return (strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel() && + version_compare($newdep['min'], $this->getVersion(), '<=') && + version_compare($newdep['max'], $this->getVersion(), '>=')); + } + + // use magic to support pecl packages suddenly jumping to the pecl channel + if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') { + if (strtolower($package) == strtolower($this->getPackage())) { + $channel = 'pear.php.net'; + } + } + + if (isset($param['version'])) { + return (strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel() && + $param['version'] == $this->getVersion()); + } + + return strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel(); + } + + function isInstalled($dep, $oper = '==') + { + if (!$dep) { + return false; + } + + if ($oper != 'ge' && $oper != 'gt' && $oper != 'has' && $oper != '==') { + return false; + } + + if (is_object($dep)) { + $package = $dep->getPackage(); + $channel = $dep->getChannel(); + if ($dep->getURI()) { + $dep = array( + 'uri' => $dep->getURI(), + 'version' => $dep->getVersion(), + ); + } else { + $dep = array( + 'version' => $dep->getVersion(), + ); + } + } else { + if (isset($dep['uri'])) { + $channel = '__uri'; + $package = $dep['dep']['name']; + } else { + $channel = $dep['info']->getChannel(); + $package = $dep['info']->getPackage(); + } + } + + $options = $this->_downloader->getOptions(); + $test = $this->_installRegistry->packageExists($package, $channel); + if (!$test && $channel == 'pecl.php.net') { + // do magic to allow upgrading from old pecl packages to new ones + $test = $this->_installRegistry->packageExists($package, 'pear.php.net'); + $channel = 'pear.php.net'; + } + + if ($test) { + if (isset($dep['uri'])) { + if ($this->_installRegistry->packageInfo($package, 'uri', '__uri') == $dep['uri']) { + return true; + } + } + + if (isset($options['upgrade'])) { + $packageVersion = $this->_installRegistry->packageInfo($package, 'version', $channel); + if (version_compare($packageVersion, $dep['version'], '>=')) { + return true; + } + + return false; + } + + return true; + } + + return false; + } + + /** + * Detect duplicate package names with differing versions + * + * If a user requests to install Date 1.4.6 and Date 1.4.7, + * for instance, this is a logic error. This method + * detects this situation. + * + * @param array $params array of PEAR_Downloader_Package objects + * @param array $errorparams empty array + * @return array array of stupid duplicated packages in PEAR_Downloader_Package obejcts + */ + function detectStupidDuplicates($params, &$errorparams) + { + $existing = array(); + foreach ($params as $i => $param) { + $package = $param->getPackage(); + $channel = $param->getChannel(); + $group = $param->getGroup(); + if (!isset($existing[$channel . '/' . $package])) { + $existing[$channel . '/' . $package] = array(); + } + + if (!isset($existing[$channel . '/' . $package][$group])) { + $existing[$channel . '/' . $package][$group] = array(); + } + + $existing[$channel . '/' . $package][$group][] = $i; + } + + $indices = array(); + foreach ($existing as $package => $groups) { + foreach ($groups as $group => $dupes) { + if (count($dupes) > 1) { + $indices = $indices + $dupes; + } + } + } + + $indices = array_unique($indices); + foreach ($indices as $index) { + $errorparams[] = $params[$index]; + } + + return count($errorparams); + } + + /** + * @param array + * @param bool ignore install groups - for final removal of dupe packages + * @static + */ + function removeDuplicates(&$params, $ignoreGroups = false) + { + $pnames = array(); + foreach ($params as $i => $param) { + if (!$param) { + continue; + } + + if ($param->getPackage()) { + $group = $ignoreGroups ? '' : $param->getGroup(); + $pnames[$i] = $param->getChannel() . '/' . + $param->getPackage() . '-' . $param->getVersion() . '#' . $group; + } + } + + $pnames = array_unique($pnames); + $unset = array_diff(array_keys($params), array_keys($pnames)); + $testp = array_flip($pnames); + foreach ($params as $i => $param) { + if (!$param) { + $unset[] = $i; + continue; + } + + if (!is_a($param, 'PEAR_Downloader_Package')) { + $unset[] = $i; + continue; + } + + $group = $ignoreGroups ? '' : $param->getGroup(); + if (!isset($testp[$param->getChannel() . '/' . $param->getPackage() . '-' . + $param->getVersion() . '#' . $group])) { + $unset[] = $i; + } + } + + foreach ($unset as $i) { + unset($params[$i]); + } + + $ret = array(); + foreach ($params as $i => $param) { + $ret[] = &$params[$i]; + } + + $params = array(); + foreach ($ret as $i => $param) { + $params[] = &$ret[$i]; + } + } + + function explicitState() + { + return $this->_explicitState; + } + + function setExplicitState($s) + { + $this->_explicitState = $s; + } + + /** + * @static + */ + function mergeDependencies(&$params) + { + $bundles = $newparams = array(); + foreach ($params as $i => $param) { + if (!$param->isBundle()) { + continue; + } + + $bundles[] = $i; + $pf = &$param->getPackageFile(); + $newdeps = array(); + $contents = $pf->getBundledPackages(); + if (!is_array($contents)) { + $contents = array($contents); + } + + foreach ($contents as $file) { + $filecontents = $pf->getFileContents($file); + $dl = &$param->getDownloader(); + $options = $dl->getOptions(); + if (PEAR::isError($dir = $dl->getDownloadDir())) { + return $dir; + } + + $fp = @fopen($dir . DIRECTORY_SEPARATOR . $file, 'wb'); + if (!$fp) { + continue; + } + + fwrite($fp, $filecontents, strlen($filecontents)); + fclose($fp); + if ($s = $params[$i]->explicitState()) { + $obj->setExplicitState($s); + } + + $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader()); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($dir = $dl->getDownloadDir())) { + PEAR::popErrorHandling(); + return $dir; + } + + $e = $obj->_fromFile($a = $dir . DIRECTORY_SEPARATOR . $file); + PEAR::popErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $dl->log(0, $e->getMessage()); + } + continue; + } + + $j = &$obj; + if (!PEAR_Downloader_Package::willDownload($j, + array_merge($params, $newparams)) && !$param->isInstalled($j)) { + $newparams[] = &$j; + } + } + } + + foreach ($bundles as $i) { + unset($params[$i]); // remove bundles - only their contents matter for installation + } + + PEAR_Downloader_Package::removeDuplicates($params); // strip any unset indices + if (count($newparams)) { // add in bundled packages for install + foreach ($newparams as $i => $unused) { + $params[] = &$newparams[$i]; + } + $newparams = array(); + } + + foreach ($params as $i => $param) { + $newdeps = array(); + foreach ($param->_downloadDeps as $dep) { + $merge = array_merge($params, $newparams); + if (!PEAR_Downloader_Package::willDownload($dep, $merge) + && !$param->isInstalled($dep) + ) { + $newdeps[] = $dep; + } else { + //var_dump($dep); + // detect versioning conflicts here + } + } + + // convert the dependencies into PEAR_Downloader_Package objects for the next time around + $params[$i]->_downloadDeps = array(); + foreach ($newdeps as $dep) { + $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader()); + if ($s = $params[$i]->explicitState()) { + $obj->setExplicitState($s); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $e = $obj->fromDepURL($dep); + PEAR::popErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $obj->_downloader->log(0, $e->getMessage()); + } + continue; + } + + $e = $obj->detectDependencies($params); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $obj->_downloader->log(0, $e->getMessage()); + } + } + + $j = &$obj; + $newparams[] = &$j; + } + } + + if (count($newparams)) { + foreach ($newparams as $i => $unused) { + $params[] = &$newparams[$i]; + } + return true; + } + + return false; + } + + + /** + * @static + */ + function willDownload($param, $params) + { + if (!is_array($params)) { + return false; + } + + foreach ($params as $obj) { + if ($obj->isEqual($param)) { + return true; + } + } + + return false; + } + + /** + * For simpler unit-testing + * @param PEAR_Config + * @param int + * @param string + */ + function &getPackagefileObject(&$c, $d, $t = false) + { + $a = &new PEAR_PackageFile($c, $d, $t); + return $a; + } + + + /** + * This will retrieve from a local file if possible, and parse out + * a group name as well. The original parameter will be modified to reflect this. + * @param string|array can be a parsed package name as well + * @access private + */ + function _fromFile(&$param) + { + $saveparam = $param; + if (is_string($param)) { + if (!@file_exists($param)) { + $test = explode('#', $param); + $group = array_pop($test); + if (@file_exists(implode('#', $test))) { + $this->setGroup($group); + $param = implode('#', $test); + $this->_explicitGroup = true; + } + } + + if (@is_file($param)) { + $this->_type = 'local'; + $options = $this->_downloader->getOptions(); + if (isset($options['downloadonly'])) { + $pkg = &$this->getPackagefileObject($this->_config, + $this->_downloader->_debug); + } else { + if (PEAR::isError($dir = $this->_downloader->getDownloadDir())) { + return $dir; + } + $pkg = &$this->getPackagefileObject($this->_config, + $this->_downloader->_debug, $dir); + } + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$pkg->fromAnyFile($param, PEAR_VALIDATE_INSTALLING); + PEAR::popErrorHandling(); + if (PEAR::isError($pf)) { + $this->_valid = false; + $param = $saveparam; + return $pf; + } + $this->_packagefile = &$pf; + if (!$this->getGroup()) { + $this->setGroup('default'); // install the default dependency group + } + return $this->_valid = true; + } + } + $param = $saveparam; + return $this->_valid = false; + } + + function _fromUrl($param, $saveparam = '') + { + if (!is_array($param) && (preg_match('#^(http|https|ftp)://#', $param))) { + $options = $this->_downloader->getOptions(); + $this->_type = 'url'; + $callback = $this->_downloader->ui ? + array(&$this->_downloader, '_downloadCallback') : null; + $this->_downloader->pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($dir = $this->_downloader->getDownloadDir())) { + $this->_downloader->popErrorHandling(); + return $dir; + } + + $this->_downloader->log(3, 'Downloading "' . $param . '"'); + $file = $this->_downloader->downloadHttp($param, $this->_downloader->ui, + $dir, $callback, null, false, $this->getChannel()); + $this->_downloader->popErrorHandling(); + if (PEAR::isError($file)) { + if (!empty($saveparam)) { + $saveparam = ", cannot download \"$saveparam\""; + } + $err = PEAR::raiseError('Could not download from "' . $param . + '"' . $saveparam . ' (' . $file->getMessage() . ')'); + return $err; + } + + if ($this->_rawpackagefile) { + require_once 'Archive/Tar.php'; + $tar = &new Archive_Tar($file); + $packagexml = $tar->extractInString('package2.xml'); + if (!$packagexml) { + $packagexml = $tar->extractInString('package.xml'); + } + + if (str_replace(array("\n", "\r"), array('',''), $packagexml) != + str_replace(array("\n", "\r"), array('',''), $this->_rawpackagefile)) { + if ($this->getChannel() != 'pear.php.net') { + return PEAR::raiseError('CRITICAL ERROR: package.xml downloaded does ' . + 'not match value returned from xml-rpc'); + } + + // be more lax for the existing PEAR packages that have not-ok + // characters in their package.xml + $this->_downloader->log(0, 'CRITICAL WARNING: The "' . + $this->getPackage() . '" package has invalid characters in its ' . + 'package.xml. The next version of PEAR may not be able to install ' . + 'this package for security reasons. Please open a bug report at ' . + 'http://pear.php.net/package/' . $this->getPackage() . '/bugs'); + } + } + + // whew, download worked! + if (isset($options['downloadonly'])) { + $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug); + } else { + $dir = $this->_downloader->getDownloadDir(); + if (PEAR::isError($dir)) { + return $dir; + } + $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug, $dir); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$pkg->fromAnyFile($file, PEAR_VALIDATE_INSTALLING); + PEAR::popErrorHandling(); + if (PEAR::isError($pf)) { + if (is_array($pf->getUserInfo())) { + foreach ($pf->getUserInfo() as $err) { + if (is_array($err)) { + $err = $err['message']; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, "Validation Error: $err"); + } + } + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pf->getMessage()); + } + + ///FIXME need to pass back some error code that we can use to match with to cancel all further operations + /// At least stop all deps of this package from being installed + $out = $saveparam ? $saveparam : $param; + $err = PEAR::raiseError('Download of "' . $out . '" succeeded, but it is not a valid package archive'); + $this->_valid = false; + return $err; + } + + $this->_packagefile = &$pf; + $this->setGroup('default'); // install the default dependency group + return $this->_valid = true; + } + + return $this->_valid = false; + } + + /** + * + * @param string|array pass in an array of format + * array( + * 'package' => 'pname', + * ['channel' => 'channame',] + * ['version' => 'version',] + * ['state' => 'state',]) + * or a string of format [channame/]pname[-version|-state] + */ + function _fromString($param) + { + $options = $this->_downloader->getOptions(); + $channel = $this->_config->get('default_channel'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pname = $this->_registry->parsePackageName($param, $channel); + PEAR::popErrorHandling(); + if (PEAR::isError($pname)) { + if ($pname->getCode() == 'invalid') { + $this->_valid = false; + return false; + } + + if ($pname->getCode() == 'channel') { + $parsed = $pname->getUserInfo(); + if ($this->_downloader->discover($parsed['channel'])) { + if ($this->_config->get('auto_discover')) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pname = $this->_registry->parsePackageName($param, $channel); + PEAR::popErrorHandling(); + } else { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Channel "' . $parsed['channel'] . + '" is not initialized, use ' . + '"pear channel-discover ' . $parsed['channel'] . '" to initialize' . + 'or pear config-set auto_discover 1'); + } + } + } + + if (PEAR::isError($pname)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pname->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param); + } + + $err = PEAR::raiseError('invalid package name/package file "' . $param . '"'); + $this->_valid = false; + return $err; + } + } else { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pname->getMessage()); + } + + $err = PEAR::raiseError('invalid package name/package file "' . $param . '"'); + $this->_valid = false; + return $err; + } + } + + if (!isset($this->_type)) { + $this->_type = 'rest'; + } + + $this->_parsedname = $pname; + $this->_explicitState = isset($pname['state']) ? $pname['state'] : false; + $this->_explicitGroup = isset($pname['group']) ? true : false; + + $info = $this->_downloader->_getPackageDownloadUrl($pname); + if (PEAR::isError($info)) { + if ($info->getCode() != -976 && $pname['channel'] == 'pear.php.net') { + // try pecl + $pname['channel'] = 'pecl.php.net'; + if ($test = $this->_downloader->_getPackageDownloadUrl($pname)) { + if (!PEAR::isError($test)) { + $info = PEAR::raiseError($info->getMessage() . ' - package ' . + $this->_registry->parsedPackageNameToString($pname, true) . + ' can be installed with "pecl install ' . $pname['package'] . + '"'); + } else { + $pname['channel'] = 'pear.php.net'; + } + } else { + $pname['channel'] = 'pear.php.net'; + } + } + + return $info; + } + + $this->_rawpackagefile = $info['raw']; + $ret = $this->_analyzeDownloadURL($info, $param, $pname); + if (PEAR::isError($ret)) { + return $ret; + } + + if ($ret) { + $this->_downloadURL = $ret; + return $this->_valid = (bool) $ret; + } + } + + /** + * @param array output of package.getDownloadURL + * @param string|array|object information for detecting packages to be downloaded, and + * for errors + * @param array name information of the package + * @param array|null packages to be downloaded + * @param bool is this an optional dependency? + * @param bool is this any kind of dependency? + * @access private + */ + function _analyzeDownloadURL($info, $param, $pname, $params = null, $optional = false, + $isdependency = false) + { + if (!is_string($param) && PEAR_Downloader_Package::willDownload($param, $params)) { + return false; + } + + if ($info === false) { + $saveparam = !is_string($param) ? ", cannot download \"$param\"" : ''; + + // no releases exist + return PEAR::raiseError('No releases for package "' . + $this->_registry->parsedPackageNameToString($pname, true) . '" exist' . $saveparam); + } + + if (strtolower($info['info']->getChannel()) != strtolower($pname['channel'])) { + $err = false; + if ($pname['channel'] == 'pecl.php.net') { + if ($info['info']->getChannel() != 'pear.php.net') { + $err = true; + } + } elseif ($info['info']->getChannel() == 'pecl.php.net') { + if ($pname['channel'] != 'pear.php.net') { + $err = true; + } + } else { + $err = true; + } + + if ($err) { + return PEAR::raiseError('SECURITY ERROR: package in channel "' . $pname['channel'] . + '" retrieved another channel\'s name for download! ("' . + $info['info']->getChannel() . '")'); + } + } + + $preferred_state = $this->_config->get('preferred_state'); + if (!isset($info['url'])) { + $package_version = $this->_registry->packageInfo($info['info']->getPackage(), + 'version', $info['info']->getChannel()); + if ($this->isInstalled($info)) { + if ($isdependency && version_compare($info['version'], $package_version, '<=')) { + // ignore bogus errors of "failed to download dependency" + // if it is already installed and the one that would be + // downloaded is older or the same version (Bug #7219) + return false; + } + } + + if ($info['version'] === $package_version) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . '-' . $package_version. ', additionally the suggested version' . + ' (' . $package_version . ') is the same as the locally installed one.'); + } + + return false; + } + + if (version_compare($info['version'], $package_version, '<=')) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . '-' . $package_version . ', additionally the suggested version' . + ' (' . $info['version'] . ') is a lower version than the locally installed one (' . $package_version . ').'); + } + + return false; + } + + $instead = ', will instead download version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '"'; + // releases exist, but we failed to get any + if (isset($this->_downloader->_options['force'])) { + if (isset($pname['version'])) { + $vs = ', version "' . $pname['version'] . '"'; + } elseif (isset($pname['state'])) { + $vs = ', stability "' . $pname['state'] . '"'; + } elseif ($param == 'dependency') { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + + if (!in_array($info['info']->getState(), + PEAR_Common::betterStates($preferred_state, true))) { + if ($optional) { + // don't spit out confusing error message + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = ' within preferred state "' . $preferred_state . + '"'; + } else { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + if ($optional) { + // don't spit out confusing error message + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = PEAR_Dependency2::_getExtraString($pname); + $instead = ''; + } + } else { + $vs = ' within preferred state "' . $preferred_state . '"'; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . $vs . $instead); + } + + // download the latest release + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } else { + if (isset($info['php']) && $info['php']) { + $err = PEAR::raiseError('Failed to download ' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], + 'package' => $pname['package']), + true) . + ', latest release is version ' . $info['php']['v'] . + ', but it requires PHP version "' . + $info['php']['m'] . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['php']['v'])) . '" to install', + PEAR_DOWNLOADER_PACKAGE_PHPVERSION); + return $err; + } + + // construct helpful error message + if (isset($pname['version'])) { + $vs = ', version "' . $pname['version'] . '"'; + } elseif (isset($pname['state'])) { + $vs = ', stability "' . $pname['state'] . '"'; + } elseif ($param == 'dependency') { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + + if (!in_array($info['info']->getState(), + PEAR_Common::betterStates($preferred_state, true))) { + if ($optional) { + // don't spit out confusing error message, and don't die on + // optional dep failure! + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = ' within preferred state "' . $preferred_state . '"'; + } else { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + if ($optional) { + // don't spit out confusing error message, and don't die on + // optional dep failure! + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = PEAR_Dependency2::_getExtraString($pname); + } + } else { + $vs = ' within preferred state "' . $this->_downloader->config->get('preferred_state') . '"'; + } + + $options = $this->_downloader->getOptions(); + // this is only set by the "download-all" command + if (isset($options['ignorepreferred_state'])) { + $err = PEAR::raiseError( + 'Failed to download ' . $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package']), + true) + . $vs . + ', latest release is version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['version'])) . '" to install', + PEAR_DOWNLOADER_PACKAGE_STATE); + return $err; + } + + // Checks if the user has a package installed already and checks the release against + // the state against the installed package, this allows upgrades for packages + // with lower stability than the preferred_state + $stability = $this->_registry->packageInfo($pname['package'], 'stability', $pname['channel']); + if (!$this->isInstalled($info) + || !in_array($info['info']->getState(), PEAR_Common::betterStates($stability['release'], true)) + ) { + $err = PEAR::raiseError( + 'Failed to download ' . $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package']), + true) + . $vs . + ', latest release is version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['version'])) . '" to install'); + return $err; + } + } + } + + if (isset($info['deprecated']) && $info['deprecated']) { + $this->_downloader->log(0, + 'WARNING: "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $info['info']->getChannel(), + 'package' => $info['info']->getPackage()), true) . + '" is deprecated in favor of "' . + $this->_registry->parsedPackageNameToString($info['deprecated'], true) . + '"'); + } + + return $info; + } +}PEAR-1.9.0/PEAR/Frontend/CLI.php100664 764 764 60642 100664 10764 + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: CLI.php 278236 2009-04-04 00:09:14Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ +/** + * base class + */ +require_once 'PEAR/Frontend.php'; + +/** + * Command-line Frontend for the PEAR Installer + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Frontend_CLI extends PEAR_Frontend +{ + /** + * What type of user interface this frontend is for. + * @var string + * @access public + */ + var $type = 'CLI'; + var $lp = ''; // line prefix + + var $params = array(); + var $term = array( + 'bold' => '', + 'normal' => '', + ); + + function PEAR_Frontend_CLI() + { + parent::PEAR(); + $term = getenv('TERM'); //(cox) $_ENV is empty for me in 4.1.1 + if (function_exists('posix_isatty') && !posix_isatty(1)) { + // output is being redirected to a file or through a pipe + } elseif ($term) { + if (preg_match('/^(xterm|vt220|linux)/', $term)) { + $this->term['bold'] = sprintf("%c%c%c%c", 27, 91, 49, 109); + $this->term['normal'] = sprintf("%c%c%c", 27, 91, 109); + } elseif (preg_match('/^vt100/', $term)) { + $this->term['bold'] = sprintf("%c%c%c%c%c%c", 27, 91, 49, 109, 0, 0); + $this->term['normal'] = sprintf("%c%c%c%c%c", 27, 91, 109, 0, 0); + } + } elseif (OS_WINDOWS) { + // XXX add ANSI codes here + } + } + + /** + * @param object PEAR_Error object + */ + function displayError($e) + { + return $this->_displayLine($e->getMessage()); + } + + /** + * @param object PEAR_Error object + */ + function displayFatalError($eobj) + { + $this->displayError($eobj); + if (class_exists('PEAR_Config')) { + $config = &PEAR_Config::singleton(); + if ($config->get('verbose') > 5) { + if (function_exists('debug_print_backtrace')) { + debug_print_backtrace(); + exit(1); + } + + $raised = false; + foreach (debug_backtrace() as $i => $frame) { + if (!$raised) { + if (isset($frame['class']) + && strtolower($frame['class']) == 'pear' + && strtolower($frame['function']) == 'raiseerror' + ) { + $raised = true; + } else { + continue; + } + } + + $frame['class'] = !isset($frame['class']) ? '' : $frame['class']; + $frame['type'] = !isset($frame['type']) ? '' : $frame['type']; + $frame['function'] = !isset($frame['function']) ? '' : $frame['function']; + $frame['line'] = !isset($frame['line']) ? '' : $frame['line']; + $this->_displayLine("#$i: $frame[class]$frame[type]$frame[function] $frame[line]"); + } + } + } + + exit(1); + } + + /** + * Instruct the runInstallScript method to skip a paramgroup that matches the + * id value passed in. + * + * This method is useful for dynamically configuring which sections of a post-install script + * will be run based on the user's setup, which is very useful for making flexible + * post-install scripts without losing the cross-Frontend ability to retrieve user input + * @param string + */ + function skipParamgroup($id) + { + $this->_skipSections[$id] = true; + } + + function runPostinstallScripts(&$scripts) + { + foreach ($scripts as $i => $script) { + $this->runInstallScript($scripts[$i]->_params, $scripts[$i]->_obj); + } + } + + /** + * @param array $xml contents of postinstallscript tag + * @param object $script post-installation script + * @param string install|upgrade + */ + function runInstallScript($xml, &$script) + { + $this->_skipSections = array(); + if (!is_array($xml) || !isset($xml['paramgroup'])) { + $script->run(array(), '_default'); + return; + } + + $completedPhases = array(); + if (!isset($xml['paramgroup'][0])) { + $xml['paramgroup'] = array($xml['paramgroup']); + } + + foreach ($xml['paramgroup'] as $group) { + if (isset($this->_skipSections[$group['id']])) { + // the post-install script chose to skip this section dynamically + continue; + } + + if (isset($group['name'])) { + $paramname = explode('::', $group['name']); + if ($lastgroup['id'] != $paramname[0]) { + continue; + } + + $group['name'] = $paramname[1]; + if (!isset($answers)) { + return; + } + + if (isset($answers[$group['name']])) { + switch ($group['conditiontype']) { + case '=' : + if ($answers[$group['name']] != $group['value']) { + continue 2; + } + break; + case '!=' : + if ($answers[$group['name']] == $group['value']) { + continue 2; + } + break; + case 'preg_match' : + if (!@preg_match('/' . $group['value'] . '/', + $answers[$group['name']])) { + continue 2; + } + break; + default : + return; + } + } + } + + $lastgroup = $group; + if (isset($group['instructions'])) { + $this->_display($group['instructions']); + } + + if (!isset($group['param'][0])) { + $group['param'] = array($group['param']); + } + + if (isset($group['param'])) { + if (method_exists($script, 'postProcessPrompts')) { + $prompts = $script->postProcessPrompts($group['param'], $group['id']); + if (!is_array($prompts) || count($prompts) != count($group['param'])) { + $this->outputData('postinstall', 'Error: post-install script did not ' . + 'return proper post-processed prompts'); + $prompts = $group['param']; + } else { + foreach ($prompts as $i => $var) { + if (!is_array($var) || !isset($var['prompt']) || + !isset($var['name']) || + ($var['name'] != $group['param'][$i]['name']) || + ($var['type'] != $group['param'][$i]['type']) + ) { + $this->outputData('postinstall', 'Error: post-install script ' . + 'modified the variables or prompts, severe security risk. ' . + 'Will instead use the defaults from the package.xml'); + $prompts = $group['param']; + } + } + } + + $answers = $this->confirmDialog($prompts); + } else { + $answers = $this->confirmDialog($group['param']); + } + } + + if ((isset($answers) && $answers) || !isset($group['param'])) { + if (!isset($answers)) { + $answers = array(); + } + + array_unshift($completedPhases, $group['id']); + if (!$script->run($answers, $group['id'])) { + $script->run($completedPhases, '_undoOnError'); + return; + } + } else { + $script->run($completedPhases, '_undoOnError'); + return; + } + } + } + + /** + * Ask for user input, confirm the answers and continue until the user is satisfied + * @param array an array of arrays, format array('name' => 'paramname', 'prompt' => + * 'text to display', 'type' => 'string'[, default => 'default value']) + * @return array + */ + function confirmDialog($params) + { + $answers = $prompts = $types = array(); + foreach ($params as $param) { + $prompts[$param['name']] = $param['prompt']; + $types[$param['name']] = $param['type']; + $answers[$param['name']] = isset($param['default']) ? $param['default'] : ''; + } + + $tried = false; + do { + if ($tried) { + $i = 1; + foreach ($answers as $var => $value) { + if (!strlen($value)) { + echo $this->bold("* Enter an answer for #" . $i . ": ({$prompts[$var]})\n"); + } + $i++; + } + } + + $answers = $this->userDialog('', $prompts, $types, $answers); + $tried = true; + } while (is_array($answers) && count(array_filter($answers)) != count($prompts)); + + return $answers; + } + + function userDialog($command, $prompts, $types = array(), $defaults = array(), $screensize = 20) + { + if (!is_array($prompts)) { + return array(); + } + + $testprompts = array_keys($prompts); + $result = $defaults; + + reset($prompts); + if (count($prompts) === 1) { + foreach ($prompts as $key => $prompt) { + $type = $types[$key]; + $default = @$defaults[$key]; + print "$prompt "; + if ($default) { + print "[$default] "; + } + print ": "; + + $line = fgets(STDIN, 2048); + $result[$key] = ($default && trim($line) == '') ? $default : trim($line); + } + + return $result; + } + + $first_run = true; + while (true) { + $descLength = max(array_map('strlen', $prompts)); + $descFormat = "%-{$descLength}s"; + $last = count($prompts); + + $i = 0; + foreach ($prompts as $n => $var) { + $res = isset($result[$n]) ? $result[$n] : null; + printf("%2d. $descFormat : %s\n", ++$i, $prompts[$n], $res); + } + print "\n1-$last, 'all', 'abort', or Enter to continue: "; + + $tmp = trim(fgets(STDIN, 1024)); + if (empty($tmp)) { + break; + } + + if ($tmp == 'abort') { + return false; + } + + if (isset($testprompts[(int)$tmp - 1])) { + $var = $testprompts[(int)$tmp - 1]; + $desc = $prompts[$var]; + $current = @$result[$var]; + print "$desc [$current] : "; + $tmp = trim(fgets(STDIN, 1024)); + if ($tmp !== '') { + $result[$var] = $tmp; + } + } elseif ($tmp == 'all') { + foreach ($prompts as $var => $desc) { + $current = $result[$var]; + print "$desc [$current] : "; + $tmp = trim(fgets(STDIN, 1024)); + if (trim($tmp) !== '') { + $result[$var] = trim($tmp); + } + } + } + + $first_run = false; + } + + return $result; + } + + function userConfirm($prompt, $default = 'yes') + { + trigger_error("PEAR_Frontend_CLI::userConfirm not yet converted", E_USER_ERROR); + static $positives = array('y', 'yes', 'on', '1'); + static $negatives = array('n', 'no', 'off', '0'); + print "$this->lp$prompt [$default] : "; + $fp = fopen("php://stdin", "r"); + $line = fgets($fp, 2048); + fclose($fp); + $answer = strtolower(trim($line)); + if (empty($answer)) { + $answer = $default; + } + if (in_array($answer, $positives)) { + return true; + } + if (in_array($answer, $negatives)) { + return false; + } + if (in_array($default, $positives)) { + return true; + } + return false; + } + + function outputData($data, $command = '_default') + { + switch ($command) { + case 'channel-info': + foreach ($data as $type => $section) { + if ($type == 'main') { + $section['data'] = array_values($section['data']); + } + + $this->outputData($section); + } + break; + case 'install': + case 'upgrade': + case 'upgrade-all': + if (isset($data['release_warnings'])) { + $this->_displayLine(''); + $this->_startTable(array( + 'border' => false, + 'caption' => 'Release Warnings' + )); + $this->_tableRow(array($data['release_warnings']), null, array(1 => array('wrap' => 55))); + $this->_endTable(); + $this->_displayLine(''); + } + + $this->_displayLine($data['data']); + break; + case 'search': + $this->_startTable($data); + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55))); + } + + foreach($data['data'] as $category) { + foreach($category as $pkg) { + $this->_tableRow($pkg, null, array(1 => array('wrap' => 55))); + } + } + + $this->_endTable(); + break; + case 'list-all': + if (!isset($data['data'])) { + $this->_displayLine('No packages in channel'); + break; + } + + $this->_startTable($data); + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55))); + } + + foreach($data['data'] as $category) { + foreach($category as $pkg) { + unset($pkg[4], $pkg[5]); + $this->_tableRow($pkg, null, array(1 => array('wrap' => 55))); + } + } + + $this->_endTable(); + break; + case 'config-show': + $data['border'] = false; + $opts = array( + 0 => array('wrap' => 30), + 1 => array('wrap' => 20), + 2 => array('wrap' => 35) + ); + + $this->_startTable($data); + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], array('bold' => true), $opts); + } + + foreach ($data['data'] as $group) { + foreach ($group as $value) { + if ($value[2] == '') { + $value[2] = ""; + } + + $this->_tableRow($value, null, $opts); + } + } + + $this->_endTable(); + break; + case 'remote-info': + $d = $data; + $data = array( + 'caption' => 'Package details:', + 'border' => false, + 'data' => array( + array("Latest", $data['stable']), + array("Installed", $data['installed']), + array("Package", $data['name']), + array("License", $data['license']), + array("Category", $data['category']), + array("Summary", $data['summary']), + array("Description", $data['description']), + ), + ); + + if (isset($d['deprecated']) && $d['deprecated']) { + $conf = &PEAR_Config::singleton(); + $reg = $conf->getRegistry(); + $name = $reg->parsedPackageNameToString($d['deprecated'], true); + $data['data'][] = array('Deprecated! use', $name); + } + default: { + if (is_array($data)) { + $this->_startTable($data); + $count = count($data['data'][0]); + if ($count == 2) { + $opts = array(0 => array('wrap' => 25), + 1 => array('wrap' => 48) + ); + } elseif ($count == 3) { + $opts = array(0 => array('wrap' => 30), + 1 => array('wrap' => 20), + 2 => array('wrap' => 35) + ); + } else { + $opts = null; + } + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], + array('bold' => true), + $opts); + } + + foreach($data['data'] as $row) { + $this->_tableRow($row, null, $opts); + } + $this->_endTable(); + } else { + $this->_displayLine($data); + } + } + } + } + + function log($text, $append_crlf = true) + { + if ($append_crlf) { + return $this->_displayLine($text); + } + + return $this->_display($text); + } + + function bold($text) + { + if (empty($this->term['bold'])) { + return strtoupper($text); + } + + return $this->term['bold'] . $text . $this->term['normal']; + } + + function _displayHeading($title) + { + print $this->lp.$this->bold($title)."\n"; + print $this->lp.str_repeat("=", strlen($title))."\n"; + } + + function _startTable($params = array()) + { + $params['table_data'] = array(); + $params['widest'] = array(); // indexed by column + $params['highest'] = array(); // indexed by row + $params['ncols'] = 0; + $this->params = $params; + } + + function _tableRow($columns, $rowparams = array(), $colparams = array()) + { + $highest = 1; + for ($i = 0; $i < count($columns); $i++) { + $col = &$columns[$i]; + if (isset($colparams[$i]) && !empty($colparams[$i]['wrap'])) { + $col = wordwrap($col, $colparams[$i]['wrap']); + } + + if (strpos($col, "\n") !== false) { + $multiline = explode("\n", $col); + $w = 0; + foreach ($multiline as $n => $line) { + $len = strlen($line); + if ($len > $w) { + $w = $len; + } + } + $lines = count($multiline); + } else { + $w = strlen($col); + } + + if (isset($this->params['widest'][$i])) { + if ($w > $this->params['widest'][$i]) { + $this->params['widest'][$i] = $w; + } + } else { + $this->params['widest'][$i] = $w; + } + + $tmp = count_chars($columns[$i], 1); + // handle unix, mac and windows formats + $lines = (isset($tmp[10]) ? $tmp[10] : (isset($tmp[13]) ? $tmp[13] : 0)) + 1; + if ($lines > $highest) { + $highest = $lines; + } + } + + if (count($columns) > $this->params['ncols']) { + $this->params['ncols'] = count($columns); + } + + $new_row = array( + 'data' => $columns, + 'height' => $highest, + 'rowparams' => $rowparams, + 'colparams' => $colparams, + ); + $this->params['table_data'][] = $new_row; + } + + function _endTable() + { + extract($this->params); + if (!empty($caption)) { + $this->_displayHeading($caption); + } + + if (count($table_data) === 0) { + return; + } + + if (!isset($width)) { + $width = $widest; + } else { + for ($i = 0; $i < $ncols; $i++) { + if (!isset($width[$i])) { + $width[$i] = $widest[$i]; + } + } + } + + $border = false; + if (empty($border)) { + $cellstart = ''; + $cellend = ' '; + $rowend = ''; + $padrowend = false; + $borderline = ''; + } else { + $cellstart = '| '; + $cellend = ' '; + $rowend = '|'; + $padrowend = true; + $borderline = '+'; + foreach ($width as $w) { + $borderline .= str_repeat('-', $w + strlen($cellstart) + strlen($cellend) - 1); + $borderline .= '+'; + } + } + + if ($borderline) { + $this->_displayLine($borderline); + } + + for ($i = 0; $i < count($table_data); $i++) { + extract($table_data[$i]); + if (!is_array($rowparams)) { + $rowparams = array(); + } + + if (!is_array($colparams)) { + $colparams = array(); + } + + $rowlines = array(); + if ($height > 1) { + for ($c = 0; $c < count($data); $c++) { + $rowlines[$c] = preg_split('/(\r?\n|\r)/', $data[$c]); + if (count($rowlines[$c]) < $height) { + $rowlines[$c] = array_pad($rowlines[$c], $height, ''); + } + } + } else { + for ($c = 0; $c < count($data); $c++) { + $rowlines[$c] = array($data[$c]); + } + } + + for ($r = 0; $r < $height; $r++) { + $rowtext = ''; + for ($c = 0; $c < count($data); $c++) { + if (isset($colparams[$c])) { + $attribs = array_merge($rowparams, $colparams); + } else { + $attribs = $rowparams; + } + + $w = isset($width[$c]) ? $width[$c] : 0; + //$cell = $data[$c]; + $cell = $rowlines[$c][$r]; + $l = strlen($cell); + if ($l > $w) { + $cell = substr($cell, 0, $w); + } + + if (isset($attribs['bold'])) { + $cell = $this->bold($cell); + } + + if ($l < $w) { + // not using str_pad here because we may + // add bold escape characters to $cell + $cell .= str_repeat(' ', $w - $l); + } + + $rowtext .= $cellstart . $cell . $cellend; + } + + if (!$border) { + $rowtext = rtrim($rowtext); + } + + $rowtext .= $rowend; + $this->_displayLine($rowtext); + } + } + + if ($borderline) { + $this->_displayLine($borderline); + } + } + + function _displayLine($text) + { + print "$this->lp$text\n"; + } + + function _display($text) + { + print $text; + } +}PEAR-1.9.0/PEAR/Installer/Role/Common.php100664 764 764 14233 100664 12657 + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class for all installation roles. + * + * This class allows extensibility of file roles. Packages with complex + * customization can now provide custom file roles along with the possibility of + * adding configuration values to match. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Common +{ + /** + * @var PEAR_Config + * @access protected + */ + var $config; + + /** + * @param PEAR_Config + */ + function PEAR_Installer_Role_Common(&$config) + { + $this->config = $config; + } + + /** + * Retrieve configuration information about a file role from its XML info + * + * @param string $role Role Classname, as in "PEAR_Installer_Role_Data" + * @return array + */ + function getInfo($role) + { + if (empty($GLOBALS['_PEAR_INSTALLER_ROLES'][$role])) { + return PEAR::raiseError('Unknown Role class: "' . $role . '"'); + } + return $GLOBALS['_PEAR_INSTALLER_ROLES'][$role]; + } + + /** + * This is called for each file to set up the directories and files + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param array attributes from the tag + * @param string file name + * @return array an array consisting of: + * + * 1 the original, pre-baseinstalldir installation directory + * 2 the final installation directory + * 3 the full path to the final location of the file + * 4 the location of the pre-installation file + */ + function processInstallation($pkg, $atts, $file, $tmp_path, $layer = null) + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + if (!$roleInfo['locationconfig']) { + return false; + } + if ($roleInfo['honorsbaseinstall']) { + $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'], $layer, + $pkg->getChannel()); + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + } elseif ($roleInfo['unusualbaseinstall']) { + $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'], + $layer, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage(); + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + } else { + $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'], + $layer, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage(); + } + if (dirname($file) != '.' && empty($atts['install-as'])) { + $dest_dir .= DIRECTORY_SEPARATOR . dirname($file); + } + if (empty($atts['install-as'])) { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file); + } else { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as']; + } + $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file; + + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + + list($dest_dir, $dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), + array($dest_dir, $dest_file, $orig_file)); + return array($save_destdir, $dest_dir, $dest_file, $orig_file); + } + + /** + * Get the name of the configuration variable that specifies the location of this file + * @return string|false + */ + function getLocationConfig() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['locationconfig']; + } + + /** + * Do any unusual setup here + * @param PEAR_Installer + * @param PEAR_PackageFile_v2 + * @param array file attributes + * @param string file name + */ + function setup(&$installer, $pkg, $atts, $file) + { + } + + function isExecutable() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['executable']; + } + + function isInstallable() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['installable']; + } + + function isExtension() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['phpextension']; + } +} +?>PEAR-1.9.0/PEAR/Installer/Role/Cfg.xml100664 764 764 645 100664 12101 + php + extsrc + extbin + zendextsrc + zendextbin + 1 + cfg_dir + + 1 + + + + +PEAR-1.9.0/PEAR/Installer/Role/Cfg.php100664 764 764 7702 100664 12111 + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Cfg.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.7.0 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.7.0 + */ +class PEAR_Installer_Role_Cfg extends PEAR_Installer_Role_Common +{ + /** + * @var PEAR_Installer + */ + var $installer; + + /** + * the md5 of the original file + * + * @var unknown_type + */ + var $md5 = null; + + /** + * Do any unusual setup here + * @param PEAR_Installer + * @param PEAR_PackageFile_v2 + * @param array file attributes + * @param string file name + */ + function setup(&$installer, $pkg, $atts, $file) + { + $this->installer = &$installer; + $reg = &$this->installer->config->getRegistry(); + $package = $reg->getPackage($pkg->getPackage(), $pkg->getChannel()); + if ($package) { + $filelist = $package->getFilelist(); + if (isset($filelist[$file]) && isset($filelist[$file]['md5sum'])) { + $this->md5 = $filelist[$file]['md5sum']; + } + } + } + + function processInstallation($pkg, $atts, $file, $tmp_path, $layer = null) + { + $test = parent::processInstallation($pkg, $atts, $file, $tmp_path, $layer); + if (@file_exists($test[2]) && @file_exists($test[3])) { + $md5 = md5_file($test[2]); + // configuration has already been installed, check for mods + if ($md5 !== $this->md5 && $md5 !== md5_file($test[3])) { + // configuration has been modified, so save our version as + // configfile-version + $old = $test[2]; + $test[2] .= '.new-' . $pkg->getVersion(); + // backup original and re-install it + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $tmpcfg = $this->config->get('temp_dir'); + $newloc = System::mkdir(array('-p', $tmpcfg)); + if (!$newloc) { + // try temp_dir + $newloc = System::mktemp(array('-d')); + if (!$newloc || PEAR::isError($newloc)) { + PEAR::popErrorHandling(); + return PEAR::raiseError('Could not save existing configuration file '. + $old . ', unable to install. Please set temp_dir ' . + 'configuration variable to a writeable location and try again'); + } + } else { + $newloc = $tmpcfg; + } + + $temp_file = $newloc . DIRECTORY_SEPARATOR . uniqid('savefile'); + if (!@copy($old, $temp_file)) { + PEAR::popErrorHandling(); + return PEAR::raiseError('Could not save existing configuration file '. + $old . ', unable to install. Please set temp_dir ' . + 'configuration variable to a writeable location and try again'); + } + + PEAR::popErrorHandling(); + $this->installer->log(0, "WARNING: configuration file $old is being installed as $test[2], you should manually merge in changes to the existing configuration file"); + $this->installer->addFileOperation('rename', array($temp_file, $old, false)); + $this->installer->addFileOperation('delete', array($temp_file)); + } + } + + return $test; + } +}PEAR-1.9.0/PEAR/Installer/Role/Data.xml100664 764 764 622 100664 12246 + php + extsrc + extbin + zendextsrc + zendextbin + 1 + data_dir + + + + + + +PEAR-1.9.0/PEAR/Installer/Role/Data.php100664 764 764 1523 100664 12256 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Data.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Data extends PEAR_Installer_Role_Common {} +?>PEAR-1.9.0/PEAR/Installer/Role/Doc.xml100664 764 764 621 100664 12101 + php + extsrc + extbin + zendextsrc + zendextbin + 1 + doc_dir + + + + + + +PEAR-1.9.0/PEAR/Installer/Role/Doc.php100664 764 764 1520 100664 12107 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Doc.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Doc extends PEAR_Installer_Role_Common {} +?>PEAR-1.9.0/PEAR/Installer/Role/Ext.xml100664 764 764 502 100664 12132 + extbin + zendextbin + 1 + ext_dir + 1 + + + + 1 + +PEAR-1.9.0/PEAR/Installer/Role/Ext.php100664 764 764 1520 100664 12142 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Ext.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Ext extends PEAR_Installer_Role_Common {} +?>PEAR-1.9.0/PEAR/Installer/Role/Php.xml100664 764 764 655 100664 12132 + php + extsrc + extbin + zendextsrc + zendextbin + 1 + php_dir + 1 + + 1 + + + +PEAR-1.9.0/PEAR/Installer/Role/Php.php100664 764 764 1520 100664 12131 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Php.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Php extends PEAR_Installer_Role_Common {} +?>PEAR-1.9.0/PEAR/Installer/Role/Script.xml100664 764 764 660 100664 12643 + php + extsrc + extbin + zendextsrc + zendextbin + 1 + bin_dir + 1 + + + 1 + + +PEAR-1.9.0/PEAR/Installer/Role/Script.php100664 764 764 1531 100664 12650 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Script.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Script extends PEAR_Installer_Role_Common {} +?>PEAR-1.9.0/PEAR/Installer/Role/Src.xml100664 764 764 442 100664 12124 + extsrc + zendextsrc + 1 + temp_dir + + + + + + +PEAR-1.9.0/PEAR/Installer/Role/Src.php100664 764 764 1665 100664 12143 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Src.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Src extends PEAR_Installer_Role_Common +{ + function setup(&$installer, $pkg, $atts, $file) + { + $installer->source_files++; + } +} +?>PEAR-1.9.0/PEAR/Installer/Role/Test.xml100664 764 764 622 100664 12314 + php + extsrc + extbin + zendextsrc + zendextbin + 1 + test_dir + + + + + + +PEAR-1.9.0/PEAR/Installer/Role/Test.php100664 764 764 1523 100664 12324 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Test.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Test extends PEAR_Installer_Role_Common {} +?>PEAR-1.9.0/PEAR/Installer/Role/Www.xml100664 764 764 644 100664 12165 + php + extsrc + extbin + zendextsrc + zendextbin + 1 + www_dir + 1 + + + + + +PEAR-1.9.0/PEAR/Installer/Role/Www.php100664 764 764 1514 100664 12171 + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Www.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.7.0 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.7.0 + */ +class PEAR_Installer_Role_Www extends PEAR_Installer_Role_Common {} +?>PEAR-1.9.0/PEAR/Installer/Role.php100664 764 764 17464 100664 11440 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Role.php 278552 2009-04-10 19:42:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base class for installer roles + */ +require_once 'PEAR/Installer/Role/Common.php'; +require_once 'PEAR/XMLParser.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role +{ + /** + * Set up any additional configuration variables that file roles require + * + * Never call this directly, it is called by the PEAR_Config constructor + * @param PEAR_Config + * @access private + * @static + */ + function initializeConfig(&$config) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $class => $info) { + if (!$info['config_vars']) { + continue; + } + + $config->_addConfigVars($class, $info['config_vars']); + } + } + + /** + * @param PEAR_PackageFile_v2 + * @param string role name + * @param PEAR_Config + * @return PEAR_Installer_Role_Common + * @static + */ + function &factory($pkg, $role, &$config) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + if (!in_array($role, PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) { + $a = false; + return $a; + } + + $a = 'PEAR_Installer_Role_' . ucfirst($role); + if (!class_exists($a)) { + require_once str_replace('_', '/', $a) . '.php'; + } + + $b = new $a($config); + return $b; + } + + /** + * Get a list of file roles that are valid for the particular release type. + * + * For instance, src files serve no purpose in regular php releases. + * @param string + * @param bool clear cache + * @return array + * @static + */ + function getValidRoles($release, $clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret = array(); + if ($clear) { + $ret = array(); + } + + if (isset($ret[$release])) { + return $ret[$release]; + } + + $ret[$release] = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if (in_array($release, $okreleases['releasetypes'])) { + $ret[$release][] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret[$release]; + } + + /** + * Get a list of roles that require their files to be installed + * + * Most roles must be installed, but src and package roles, for instance + * are pseudo-roles. src files are compiled into a new extension. Package + * roles are actually fully bundled releases of a package + * @param bool clear cache + * @return array + * @static + */ + function getInstallableRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['installable']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Return an array of roles that are affected by the baseinstalldir attribute + * + * Most roles ignore this attribute, and instead install directly into: + * PackageName/filepath + * so a tests file tests/file.phpt is installed into PackageName/tests/filepath.php + * @param bool clear cache + * @return array + * @static + */ + function getBaseinstallRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['honorsbaseinstall']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Return an array of file roles that should be analyzed for PHP content at package time, + * like the "php" role. + * @param bool clear cache + * @return array + * @static + */ + function getPhpRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['phpfile']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Scan through the Command directory looking for classes + * and see what commands they implement. + * @param string which directory to look for classes, defaults to + * the Installer/Roles subdirectory of + * the directory from where this file (__FILE__) is + * included. + * + * @return bool TRUE on success, a PEAR error on failure + * @access public + * @static + */ + function registerRoles($dir = null) + { + $GLOBALS['_PEAR_INSTALLER_ROLES'] = array(); + $parser = new PEAR_XMLParser; + if ($dir === null) { + $dir = dirname(__FILE__) . '/Role'; + } + + if (!file_exists($dir) || !is_dir($dir)) { + return PEAR::raiseError("registerRoles: opendir($dir) failed: does not exist/is not directory"); + } + + $dp = @opendir($dir); + if (empty($dp)) { + return PEAR::raiseError("registerRoles: opendir($dir) failed: $php_errmsg"); + } + + while ($entry = readdir($dp)) { + if ($entry{0} == '.' || substr($entry, -4) != '.xml') { + continue; + } + + $class = "PEAR_Installer_Role_".substr($entry, 0, -4); + // List of roles + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'][$class])) { + $file = "$dir/$entry"; + $parser->parse(file_get_contents($file)); + $data = $parser->getData(); + if (!is_array($data['releasetypes'])) { + $data['releasetypes'] = array($data['releasetypes']); + } + + $GLOBALS['_PEAR_INSTALLER_ROLES'][$class] = $data; + } + } + + closedir($dp); + ksort($GLOBALS['_PEAR_INSTALLER_ROLES']); + PEAR_Installer_Role::getBaseinstallRoles(true); + PEAR_Installer_Role::getInstallableRoles(true); + PEAR_Installer_Role::getPhpRoles(true); + PEAR_Installer_Role::getValidRoles('****', true); + return true; + } +}PEAR-1.9.0/PEAR/PackageFile/Generator/v1.php100664 764 764 142342 100664 13223 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v1.php 286494 2009-07-29 06:57:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * needed for PEAR_VALIDATE_* constants + */ +require_once 'PEAR/Validate.php'; +require_once 'System.php'; +require_once 'PEAR/PackageFile/v2.php'; +/** + * This class converts a PEAR_PackageFile_v1 object into any output format. + * + * Supported output formats include array, XML string, and a PEAR_PackageFile_v2 + * object, for converting package.xml 1.0 into package.xml 2.0 with no sweat. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Generator_v1 +{ + /** + * @var PEAR_PackageFile_v1 + */ + var $_packagefile; + function PEAR_PackageFile_Generator_v1(&$packagefile) + { + $this->_packagefile = &$packagefile; + } + + function getPackagerVersion() + { + return '1.9.0'; + } + + /** + * @param PEAR_Packager + * @param bool if true, a .tgz is written, otherwise a .tar is written + * @param string|null directory in which to save the .tgz + * @return string|PEAR_Error location of package or error object + */ + function toTgz(&$packager, $compress = true, $where = null) + { + require_once 'Archive/Tar.php'; + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: "' . $where . '" could' . + ' not be created'); + } + if (file_exists($where . DIRECTORY_SEPARATOR . 'package.xml') && + !is_file($where . DIRECTORY_SEPARATOR . 'package.xml')) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: unable to save package.xml as' . + ' "' . $where . DIRECTORY_SEPARATOR . 'package.xml"'); + } + if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: invalid package file'); + } + $pkginfo = $this->_packagefile->getArray(); + $ext = $compress ? '.tgz' : '.tar'; + $pkgver = $pkginfo['package'] . '-' . $pkginfo['version']; + $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext; + if (file_exists(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext) && + !is_file(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: cannot create tgz file "' . + getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext . '"'); + } + if ($pkgfile = $this->_packagefile->getPackageFile()) { + $pkgdir = dirname(realpath($pkgfile)); + $pkgfile = basename($pkgfile); + } else { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: package file object must ' . + 'be created from a real file'); + } + // {{{ Create the package file list + $filelist = array(); + $i = 0; + + foreach ($this->_packagefile->getFilelist() as $fname => $atts) { + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return PEAR::raiseError("File does not exist: $fname"); + } else { + $filelist[$i++] = $file; + if (!isset($atts['md5sum'])) { + $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($file)); + } + $packager->log(2, "Adding file $fname"); + } + } + // }}} + $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true); + if ($packagexml) { + $tar =& new Archive_Tar($dest_package, $compress); + $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors + // ----- Creates with the package.xml file + $ok = $tar->createModify(array($packagexml), '', $where); + if (PEAR::isError($ok)) { + return $ok; + } elseif (!$ok) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed'); + } + // ----- Add the content of the package + if (!$tar->addModify($filelist, $pkgver, $pkgdir)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed'); + } + return $dest_package; + } + } + + /** + * @param string|null directory to place the package.xml in, or null for a temporary dir + * @param int one of the PEAR_VALIDATE_* constants + * @param string name of the generated file + * @param bool if true, then no analysis will be performed on role="php" files + * @return string|PEAR_Error path to the created file on success + */ + function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml', + $nofilechecking = false) + { + if (!$this->_packagefile->validate($state, $nofilechecking)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: invalid package.xml', + null, null, null, $this->_packagefile->getValidationWarnings()); + } + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: "' . $where . '" could' . + ' not be created'); + } + $newpkgfile = $where . DIRECTORY_SEPARATOR . $name; + $np = @fopen($newpkgfile, 'wb'); + if (!$np) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: unable to save ' . + "$name as $newpkgfile"); + } + fwrite($np, $this->toXml($state, true)); + fclose($np); + return $newpkgfile; + } + + /** + * fix both XML encoding to be UTF8, and replace standard XML entities < > " & ' + * + * @param string $string + * @return string + * @access private + */ + function _fixXmlEncoding($string) + { + if (version_compare(phpversion(), '5.0.0', 'lt')) { + $string = utf8_encode($string); + } + return strtr($string, array( + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + '\'' => ''' )); + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @return string XML data + */ + function toXml($state = PEAR_VALIDATE_NORMAL, $nofilevalidation = false) + { + $this->_packagefile->setDate(date('Y-m-d')); + if (!$this->_packagefile->validate($state, $nofilevalidation)) { + return false; + } + $pkginfo = $this->_packagefile->getArray(); + static $maint_map = array( + "handle" => "user", + "name" => "name", + "email" => "email", + "role" => "role", + ); + $ret = "\n"; + $ret .= "\n"; + $ret .= "\n" . +" $pkginfo[package]"; + if (isset($pkginfo['extends'])) { + $ret .= "\n$pkginfo[extends]"; + } + $ret .= + "\n ".$this->_fixXmlEncoding($pkginfo['summary'])."\n" . +" ".trim($this->_fixXmlEncoding($pkginfo['description']))."\n \n" . +" \n"; + foreach ($pkginfo['maintainers'] as $maint) { + $ret .= " \n"; + foreach ($maint_map as $idx => $elm) { + $ret .= " <$elm>"; + $ret .= $this->_fixXmlEncoding($maint[$idx]); + $ret .= "\n"; + } + $ret .= " \n"; + } + $ret .= " \n"; + $ret .= $this->_makeReleaseXml($pkginfo, false, $state); + if (isset($pkginfo['changelog']) && count($pkginfo['changelog']) > 0) { + $ret .= " \n"; + foreach ($pkginfo['changelog'] as $oldrelease) { + $ret .= $this->_makeReleaseXml($oldrelease, true); + } + $ret .= " \n"; + } + $ret .= "\n"; + return $ret; + } + + // }}} + // {{{ _makeReleaseXml() + + /** + * Generate part of an XML description with release information. + * + * @param array $pkginfo array with release information + * @param bool $changelog whether the result will be in a changelog element + * + * @return string XML data + * + * @access private + */ + function _makeReleaseXml($pkginfo, $changelog = false, $state = PEAR_VALIDATE_NORMAL) + { + // XXX QUOTE ENTITIES IN PCDATA, OR EMBED IN CDATA BLOCKS!! + $indent = $changelog ? " " : ""; + $ret = "$indent \n"; + if (!empty($pkginfo['version'])) { + $ret .= "$indent $pkginfo[version]\n"; + } + if (!empty($pkginfo['release_date'])) { + $ret .= "$indent $pkginfo[release_date]\n"; + } + if (!empty($pkginfo['release_license'])) { + $ret .= "$indent $pkginfo[release_license]\n"; + } + if (!empty($pkginfo['release_state'])) { + $ret .= "$indent $pkginfo[release_state]\n"; + } + if (!empty($pkginfo['release_notes'])) { + $ret .= "$indent ".trim($this->_fixXmlEncoding($pkginfo['release_notes'])) + ."\n$indent \n"; + } + if (!empty($pkginfo['release_warnings'])) { + $ret .= "$indent ".$this->_fixXmlEncoding($pkginfo['release_warnings'])."\n"; + } + if (isset($pkginfo['release_deps']) && sizeof($pkginfo['release_deps']) > 0) { + $ret .= "$indent \n"; + foreach ($pkginfo['release_deps'] as $dep) { + $ret .= "$indent _fixXmlEncoding($c['name']) . "\""; + if (isset($c['default'])) { + $ret .= " default=\"" . $this->_fixXmlEncoding($c['default']) . "\""; + } + $ret .= " prompt=\"" . $this->_fixXmlEncoding($c['prompt']) . "\""; + $ret .= "/>\n"; + } + $ret .= "$indent \n"; + } + if (isset($pkginfo['provides'])) { + foreach ($pkginfo['provides'] as $key => $what) { + $ret .= "$indent recursiveXmlFilelist($pkginfo['filelist']); + } else { + foreach ($pkginfo['filelist'] as $file => $fa) { + if (!isset($fa['role'])) { + $fa['role'] = ''; + } + $ret .= "$indent _fixXmlEncoding($fa['baseinstalldir']) . '"'; + } + if (isset($fa['md5sum'])) { + $ret .= " md5sum=\"$fa[md5sum]\""; + } + if (isset($fa['platform'])) { + $ret .= " platform=\"$fa[platform]\""; + } + if (!empty($fa['install-as'])) { + $ret .= ' install-as="' . + $this->_fixXmlEncoding($fa['install-as']) . '"'; + } + $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"'; + if (empty($fa['replacements'])) { + $ret .= "/>\n"; + } else { + $ret .= ">\n"; + foreach ($fa['replacements'] as $r) { + $ret .= "$indent $v) { + $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"'; + } + $ret .= "/>\n"; + } + $ret .= "$indent \n"; + } + } + } + $ret .= "$indent \n"; + } + $ret .= "$indent \n"; + return $ret; + } + + /** + * @param array + * @access protected + */ + function recursiveXmlFilelist($list) + { + $this->_dirs = array(); + foreach ($list as $file => $attributes) { + $this->_addDir($this->_dirs, explode('/', dirname($file)), $file, $attributes); + } + return $this->_formatDir($this->_dirs); + } + + /** + * @param array + * @param array + * @param string|null + * @param array|null + * @access private + */ + function _addDir(&$dirs, $dir, $file = null, $attributes = null) + { + if ($dir == array() || $dir == array('.')) { + $dirs['files'][basename($file)] = $attributes; + return; + } + $curdir = array_shift($dir); + if (!isset($dirs['dirs'][$curdir])) { + $dirs['dirs'][$curdir] = array(); + } + $this->_addDir($dirs['dirs'][$curdir], $dir, $file, $attributes); + } + + /** + * @param array + * @param string + * @param string + * @access private + */ + function _formatDir($dirs, $indent = '', $curdir = '') + { + $ret = ''; + if (!count($dirs)) { + return ''; + } + if (isset($dirs['dirs'])) { + uksort($dirs['dirs'], 'strnatcasecmp'); + foreach ($dirs['dirs'] as $dir => $contents) { + $usedir = "$curdir/$dir"; + $ret .= "$indent \n"; + $ret .= $this->_formatDir($contents, "$indent ", $usedir); + $ret .= "$indent \n"; + } + } + if (isset($dirs['files'])) { + uksort($dirs['files'], 'strnatcasecmp'); + foreach ($dirs['files'] as $file => $attribs) { + $ret .= $this->_formatFile($file, $attribs, $indent); + } + } + return $ret; + } + + /** + * @param string + * @param array + * @param string + * @access private + */ + function _formatFile($file, $attributes, $indent) + { + $ret = "$indent _fixXmlEncoding($attributes['baseinstalldir']) . '"'; + } + if (isset($attributes['md5sum'])) { + $ret .= " md5sum=\"$attributes[md5sum]\""; + } + if (isset($attributes['platform'])) { + $ret .= " platform=\"$attributes[platform]\""; + } + if (!empty($attributes['install-as'])) { + $ret .= ' install-as="' . + $this->_fixXmlEncoding($attributes['install-as']) . '"'; + } + $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"'; + if (empty($attributes['replacements'])) { + $ret .= "/>\n"; + } else { + $ret .= ">\n"; + foreach ($attributes['replacements'] as $r) { + $ret .= "$indent $v) { + $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"'; + } + $ret .= "/>\n"; + } + $ret .= "$indent \n"; + } + return $ret; + } + + // {{{ _unIndent() + + /** + * Unindent given string (?) + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } + } + return $data; + } + + /** + * @return array + */ + function dependenciesToV2() + { + $arr = array(); + $this->_convertDependencies2_0($arr); + return $arr['dependencies']; + } + + /** + * Convert a package.xml version 1.0 into version 2.0 + * + * Note that this does a basic conversion, to allow more advanced + * features like bundles and multiple releases + * @param string the classname to instantiate and return. This must be + * PEAR_PackageFile_v2 or a descendant + * @param boolean if true, only valid, deterministic package.xml 1.0 as defined by the + * strictest parameters will be converted + * @return PEAR_PackageFile_v2|PEAR_Error + */ + function &toV2($class = 'PEAR_PackageFile_v2', $strict = false) + { + if ($strict) { + if (!$this->_packagefile->validate()) { + $a = PEAR::raiseError('invalid package.xml version 1.0 cannot be converted' . + ' to version 2.0', null, null, null, + $this->_packagefile->getValidationWarnings(true)); + return $a; + } + } + + $arr = array( + 'attribs' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => "http://pear.php.net/dtd/tasks-1.0\n" . +"http://pear.php.net/dtd/tasks-1.0.xsd\n" . +"http://pear.php.net/dtd/package-2.0\n" . +'http://pear.php.net/dtd/package-2.0.xsd', + ), + 'name' => $this->_packagefile->getPackage(), + 'channel' => 'pear.php.net', + ); + $arr['summary'] = $this->_packagefile->getSummary(); + $arr['description'] = $this->_packagefile->getDescription(); + $maintainers = $this->_packagefile->getMaintainers(); + foreach ($maintainers as $maintainer) { + if ($maintainer['role'] != 'lead') { + continue; + } + $new = array( + 'name' => $maintainer['name'], + 'user' => $maintainer['handle'], + 'email' => $maintainer['email'], + 'active' => 'yes', + ); + $arr['lead'][] = $new; + } + + if (!isset($arr['lead'])) { // some people... you know? + $arr['lead'] = array( + 'name' => 'unknown', + 'user' => 'unknown', + 'email' => 'noleadmaintainer@example.com', + 'active' => 'no', + ); + } + + if (count($arr['lead']) == 1) { + $arr['lead'] = $arr['lead'][0]; + } + + foreach ($maintainers as $maintainer) { + if ($maintainer['role'] == 'lead') { + continue; + } + $new = array( + 'name' => $maintainer['name'], + 'user' => $maintainer['handle'], + 'email' => $maintainer['email'], + 'active' => 'yes', + ); + $arr[$maintainer['role']][] = $new; + } + + if (isset($arr['developer']) && count($arr['developer']) == 1) { + $arr['developer'] = $arr['developer'][0]; + } + + if (isset($arr['contributor']) && count($arr['contributor']) == 1) { + $arr['contributor'] = $arr['contributor'][0]; + } + + if (isset($arr['helper']) && count($arr['helper']) == 1) { + $arr['helper'] = $arr['helper'][0]; + } + + $arr['date'] = $this->_packagefile->getDate(); + $arr['version'] = + array( + 'release' => $this->_packagefile->getVersion(), + 'api' => $this->_packagefile->getVersion(), + ); + $arr['stability'] = + array( + 'release' => $this->_packagefile->getState(), + 'api' => $this->_packagefile->getState(), + ); + $licensemap = + array( + 'php' => 'http://www.php.net/license', + 'php license' => 'http://www.php.net/license', + 'lgpl' => 'http://www.gnu.org/copyleft/lesser.html', + 'bsd' => 'http://www.opensource.org/licenses/bsd-license.php', + 'bsd style' => 'http://www.opensource.org/licenses/bsd-license.php', + 'bsd-style' => 'http://www.opensource.org/licenses/bsd-license.php', + 'mit' => 'http://www.opensource.org/licenses/mit-license.php', + 'gpl' => 'http://www.gnu.org/copyleft/gpl.html', + 'apache' => 'http://www.opensource.org/licenses/apache2.0.php' + ); + + if (isset($licensemap[strtolower($this->_packagefile->getLicense())])) { + $arr['license'] = array( + 'attribs' => array('uri' => + $licensemap[strtolower($this->_packagefile->getLicense())]), + '_content' => $this->_packagefile->getLicense() + ); + } else { + // don't use bogus uri + $arr['license'] = $this->_packagefile->getLicense(); + } + + $arr['notes'] = $this->_packagefile->getNotes(); + $temp = array(); + $arr['contents'] = $this->_convertFilelist2_0($temp); + $this->_convertDependencies2_0($arr); + $release = ($this->_packagefile->getConfigureOptions() || $this->_isExtension) ? + 'extsrcrelease' : 'phprelease'; + if ($release == 'extsrcrelease') { + $arr['channel'] = 'pecl.php.net'; + $arr['providesextension'] = $arr['name']; // assumption + } + + $arr[$release] = array(); + if ($this->_packagefile->getConfigureOptions()) { + $arr[$release]['configureoption'] = $this->_packagefile->getConfigureOptions(); + foreach ($arr[$release]['configureoption'] as $i => $opt) { + $arr[$release]['configureoption'][$i] = array('attribs' => $opt); + } + if (count($arr[$release]['configureoption']) == 1) { + $arr[$release]['configureoption'] = $arr[$release]['configureoption'][0]; + } + } + + $this->_convertRelease2_0($arr[$release], $temp); + if ($release == 'extsrcrelease' && count($arr[$release]) > 1) { + // multiple extsrcrelease tags added in PEAR 1.4.1 + $arr['dependencies']['required']['pearinstaller']['min'] = '1.4.1'; + } + + if ($cl = $this->_packagefile->getChangelog()) { + foreach ($cl as $release) { + $rel = array(); + $rel['version'] = + array( + 'release' => $release['version'], + 'api' => $release['version'], + ); + if (!isset($release['release_state'])) { + $release['release_state'] = 'stable'; + } + + $rel['stability'] = + array( + 'release' => $release['release_state'], + 'api' => $release['release_state'], + ); + if (isset($release['release_date'])) { + $rel['date'] = $release['release_date']; + } else { + $rel['date'] = date('Y-m-d'); + } + + if (isset($release['release_license'])) { + if (isset($licensemap[strtolower($release['release_license'])])) { + $uri = $licensemap[strtolower($release['release_license'])]; + } else { + $uri = 'http://www.example.com'; + } + $rel['license'] = array( + 'attribs' => array('uri' => $uri), + '_content' => $release['release_license'] + ); + } else { + $rel['license'] = $arr['license']; + } + + if (!isset($release['release_notes'])) { + $release['release_notes'] = 'no release notes'; + } + + $rel['notes'] = $release['release_notes']; + $arr['changelog']['release'][] = $rel; + } + } + + $ret = new $class; + $ret->setConfig($this->_packagefile->_config); + if (isset($this->_packagefile->_logger) && is_object($this->_packagefile->_logger)) { + $ret->setLogger($this->_packagefile->_logger); + } + + $ret->fromArray($arr); + return $ret; + } + + /** + * @param array + * @param bool + * @access private + */ + function _convertDependencies2_0(&$release, $internal = false) + { + $peardep = array('pearinstaller' => + array('min' => '1.4.0b1')); // this is a lot safer + $required = $optional = array(); + $release['dependencies'] = array('required' => array()); + if ($this->_packagefile->hasDeps()) { + foreach ($this->_packagefile->getDeps() as $dep) { + if (!isset($dep['optional']) || $dep['optional'] == 'no') { + $required[] = $dep; + } else { + $optional[] = $dep; + } + } + foreach (array('required', 'optional') as $arr) { + $deps = array(); + foreach ($$arr as $dep) { + // organize deps by dependency type and name + if (!isset($deps[$dep['type']])) { + $deps[$dep['type']] = array(); + } + if (isset($dep['name'])) { + $deps[$dep['type']][$dep['name']][] = $dep; + } else { + $deps[$dep['type']][] = $dep; + } + } + do { + if (isset($deps['php'])) { + $php = array(); + if (count($deps['php']) > 1) { + $php = $this->_processPhpDeps($deps['php']); + } else { + if (!isset($deps['php'][0])) { + list($key, $blah) = each ($deps['php']); // stupid buggy versions + $deps['php'] = array($blah[0]); + } + $php = $this->_processDep($deps['php'][0]); + if (!$php) { + break; // poor mans throw + } + } + $release['dependencies'][$arr]['php'] = $php; + } + } while (false); + do { + if (isset($deps['pkg'])) { + $pkg = array(); + $pkg = $this->_processMultipleDepsName($deps['pkg']); + if (!$pkg) { + break; // poor mans throw + } + $release['dependencies'][$arr]['package'] = $pkg; + } + } while (false); + do { + if (isset($deps['ext'])) { + $pkg = array(); + $pkg = $this->_processMultipleDepsName($deps['ext']); + $release['dependencies'][$arr]['extension'] = $pkg; + } + } while (false); + // skip sapi - it's not supported so nobody will have used it + // skip os - it's not supported in 1.0 + } + } + if (isset($release['dependencies']['required'])) { + $release['dependencies']['required'] = + array_merge($peardep, $release['dependencies']['required']); + } else { + $release['dependencies']['required'] = $peardep; + } + if (!isset($release['dependencies']['required']['php'])) { + $release['dependencies']['required']['php'] = + array('min' => '4.0.0'); + } + $order = array(); + $bewm = $release['dependencies']['required']; + $order['php'] = $bewm['php']; + $order['pearinstaller'] = $bewm['pearinstaller']; + isset($bewm['package']) ? $order['package'] = $bewm['package'] :0; + isset($bewm['extension']) ? $order['extension'] = $bewm['extension'] :0; + $release['dependencies']['required'] = $order; + } + + /** + * @param array + * @access private + */ + function _convertFilelist2_0(&$package) + { + $ret = array('dir' => + array( + 'attribs' => array('name' => '/'), + 'file' => array() + ) + ); + $package['platform'] = + $package['install-as'] = array(); + $this->_isExtension = false; + foreach ($this->_packagefile->getFilelist() as $name => $file) { + $file['name'] = $name; + if (isset($file['role']) && $file['role'] == 'src') { + $this->_isExtension = true; + } + if (isset($file['replacements'])) { + $repl = $file['replacements']; + unset($file['replacements']); + } else { + unset($repl); + } + if (isset($file['install-as'])) { + $package['install-as'][$name] = $file['install-as']; + unset($file['install-as']); + } + if (isset($file['platform'])) { + $package['platform'][$name] = $file['platform']; + unset($file['platform']); + } + $file = array('attribs' => $file); + if (isset($repl)) { + foreach ($repl as $replace ) { + $file['tasks:replace'][] = array('attribs' => $replace); + } + if (count($repl) == 1) { + $file['tasks:replace'] = $file['tasks:replace'][0]; + } + } + $ret['dir']['file'][] = $file; + } + return $ret; + } + + /** + * Post-process special files with install-as/platform attributes and + * make the release tag. + * + * This complex method follows this work-flow to create the release tags: + * + *
    +     * - if any install-as/platform exist, create a generic release and fill it with
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     * - create a release for each platform encountered and fill with
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     * 
    + * + * It does this by accessing the $package parameter, which contains an array with + * indices: + * + * - platform: mapping of file => OS the file should be installed on + * - install-as: mapping of file => installed name + * - osmap: mapping of OS => list of files that should be installed + * on that OS + * - notosmap: mapping of OS => list of files that should not be + * installed on that OS + * + * @param array + * @param array + * @access private + */ + function _convertRelease2_0(&$release, $package) + { + //- if any install-as/platform exist, create a generic release and fill it with + if (count($package['platform']) || count($package['install-as'])) { + $generic = array(); + $genericIgnore = array(); + foreach ($package['install-as'] as $file => $as) { + //o tags for + if (!isset($package['platform'][$file])) { + $generic[] = $file; + continue; + } + //o tags for + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} == '!') { + $generic[] = $file; + continue; + } + //o tags for + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} != '!') { + $genericIgnore[] = $file; + continue; + } + } + foreach ($package['platform'] as $file => $platform) { + if (isset($package['install-as'][$file])) { + continue; + } + if ($platform{0} != '!') { + //o tags for + $genericIgnore[] = $file; + } + } + if (count($package['platform'])) { + $oses = $notplatform = $platform = array(); + foreach ($package['platform'] as $file => $os) { + // get a list of oses + if ($os{0} == '!') { + if (isset($oses[substr($os, 1)])) { + continue; + } + $oses[substr($os, 1)] = count($oses); + } else { + if (isset($oses[$os])) { + continue; + } + $oses[$os] = count($oses); + } + } + //- create a release for each platform encountered and fill with + foreach ($oses as $os => $releaseNum) { + $release[$releaseNum]['installconditions']['os']['name'] = $os; + $release[$releaseNum]['filelist'] = array('install' => array(), + 'ignore' => array()); + foreach ($package['install-as'] as $file => $as) { + //o tags for + if (!isset($package['platform'][$file])) { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file] == $os) { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file] != "!$os" && + $package['platform'][$file]{0} == '!') { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file] == "!$os") { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} != '!' && + $package['platform'][$file] != $os) { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + } + foreach ($package['platform'] as $file => $platform) { + if (isset($package['install-as'][$file])) { + continue; + } + //o tags for + if ($platform == "!$os") { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + //o tags for + if ($platform{0} != '!' && $platform != $os) { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + } + } + if (!count($release[$releaseNum]['filelist']['install'])) { + unset($release[$releaseNum]['filelist']['install']); + } + if (!count($release[$releaseNum]['filelist']['ignore'])) { + unset($release[$releaseNum]['filelist']['ignore']); + } + } + if (count($generic) || count($genericIgnore)) { + $release[count($oses)] = array(); + if (count($generic)) { + foreach ($generic as $file) { + if (isset($package['install-as'][$file])) { + $installas = $package['install-as'][$file]; + } else { + $installas = $file; + } + $release[count($oses)]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $installas, + ) + ); + } + } + if (count($genericIgnore)) { + foreach ($genericIgnore as $file) { + $release[count($oses)]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ) + ); + } + } + } + // cleanup + foreach ($release as $i => $rel) { + if (isset($rel['filelist']['install']) && + count($rel['filelist']['install']) == 1) { + $release[$i]['filelist']['install'] = + $release[$i]['filelist']['install'][0]; + } + if (isset($rel['filelist']['ignore']) && + count($rel['filelist']['ignore']) == 1) { + $release[$i]['filelist']['ignore'] = + $release[$i]['filelist']['ignore'][0]; + } + } + if (count($release) == 1) { + $release = $release[0]; + } + } else { + // no platform atts, but some install-as atts + foreach ($package['install-as'] as $file => $value) { + $release['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $value + ) + ); + } + if (count($release['filelist']['install']) == 1) { + $release['filelist']['install'] = $release['filelist']['install'][0]; + } + } + } + } + + /** + * @param array + * @return array + * @access private + */ + function _processDep($dep) + { + if ($dep['type'] == 'php') { + if ($dep['rel'] == 'has') { + // come on - everyone has php! + return false; + } + } + $php = array(); + if ($dep['type'] != 'php') { + $php['name'] = $dep['name']; + if ($dep['type'] == 'pkg') { + $php['channel'] = 'pear.php.net'; + } + } + switch ($dep['rel']) { + case 'gt' : + $php['min'] = $dep['version']; + $php['exclude'] = $dep['version']; + break; + case 'ge' : + if (!isset($dep['version'])) { + if ($dep['type'] == 'php') { + if (isset($dep['name'])) { + $dep['version'] = $dep['name']; + } + } + } + $php['min'] = $dep['version']; + break; + case 'lt' : + $php['max'] = $dep['version']; + $php['exclude'] = $dep['version']; + break; + case 'le' : + $php['max'] = $dep['version']; + break; + case 'eq' : + $php['min'] = $dep['version']; + $php['max'] = $dep['version']; + break; + case 'ne' : + $php['exclude'] = $dep['version']; + break; + case 'not' : + $php['conflicts'] = 'yes'; + break; + } + return $php; + } + + /** + * @param array + * @return array + */ + function _processPhpDeps($deps) + { + $test = array(); + foreach ($deps as $dep) { + $test[] = $this->_processDep($dep); + } + $min = array(); + $max = array(); + foreach ($test as $dep) { + if (!$dep) { + continue; + } + if (isset($dep['min'])) { + $min[$dep['min']] = count($min); + } + if (isset($dep['max'])) { + $max[$dep['max']] = count($max); + } + } + if (count($min) > 0) { + uksort($min, 'version_compare'); + } + if (count($max) > 0) { + uksort($max, 'version_compare'); + } + if (count($min)) { + // get the highest minimum + $min = array_pop($a = array_flip($min)); + } else { + $min = false; + } + if (count($max)) { + // get the lowest maximum + $max = array_shift($a = array_flip($max)); + } else { + $max = false; + } + if ($min) { + $php['min'] = $min; + } + if ($max) { + $php['max'] = $max; + } + $exclude = array(); + foreach ($test as $dep) { + if (!isset($dep['exclude'])) { + continue; + } + $exclude[] = $dep['exclude']; + } + if (count($exclude)) { + $php['exclude'] = $exclude; + } + return $php; + } + + /** + * process multiple dependencies that have a name, like package deps + * @param array + * @return array + * @access private + */ + function _processMultipleDepsName($deps) + { + $ret = $tests = array(); + foreach ($deps as $name => $dep) { + foreach ($dep as $d) { + $tests[$name][] = $this->_processDep($d); + } + } + + foreach ($tests as $name => $test) { + $max = $min = $php = array(); + $php['name'] = $name; + foreach ($test as $dep) { + if (!$dep) { + continue; + } + if (isset($dep['channel'])) { + $php['channel'] = 'pear.php.net'; + } + if (isset($dep['conflicts']) && $dep['conflicts'] == 'yes') { + $php['conflicts'] = 'yes'; + } + if (isset($dep['min'])) { + $min[$dep['min']] = count($min); + } + if (isset($dep['max'])) { + $max[$dep['max']] = count($max); + } + } + if (count($min) > 0) { + uksort($min, 'version_compare'); + } + if (count($max) > 0) { + uksort($max, 'version_compare'); + } + if (count($min)) { + // get the highest minimum + $min = array_pop($a = array_flip($min)); + } else { + $min = false; + } + if (count($max)) { + // get the lowest maximum + $max = array_shift($a = array_flip($max)); + } else { + $max = false; + } + if ($min) { + $php['min'] = $min; + } + if ($max) { + $php['max'] = $max; + } + $exclude = array(); + foreach ($test as $dep) { + if (!isset($dep['exclude'])) { + continue; + } + $exclude[] = $dep['exclude']; + } + if (count($exclude)) { + $php['exclude'] = $exclude; + } + $ret[] = $php; + } + return $ret; + } +} +?>PEAR-1.9.0/PEAR/PackageFile/Generator/v2.php100664 764 764 101360 100664 13217 + * @author Stephan Schmidt (original XML_Serializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v2.php 278907 2009-04-17 21:10:04Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * file/dir manipulation routines + */ +require_once 'System.php'; +require_once 'XML/Util.php'; + +/** + * This class converts a PEAR_PackageFile_v2 object into any output format. + * + * Supported output formats include array, XML string (using S. Schmidt's + * XML_Serializer, slightly customized) + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Stephan Schmidt (original XML_Serializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Generator_v2 +{ + /** + * default options for the serialization + * @access private + * @var array $_defaultOptions + */ + var $_defaultOptions = array( + 'indent' => ' ', // string used for indentation + 'linebreak' => "\n", // string used for newlines + 'typeHints' => false, // automatically add type hin attributes + 'addDecl' => true, // add an XML declaration + 'defaultTagName' => 'XML_Serializer_Tag', // tag used for indexed arrays or invalid names + 'classAsTagName' => false, // use classname for objects in indexed arrays + 'keyAttribute' => '_originalKey', // attribute where original key is stored + 'typeAttribute' => '_type', // attribute for type (only if typeHints => true) + 'classAttribute' => '_class', // attribute for class of objects (only if typeHints => true) + 'scalarAsAttributes' => false, // scalar values (strings, ints,..) will be serialized as attribute + 'prependAttributes' => '', // prepend string for attributes + 'indentAttributes' => false, // indent the attributes, if set to '_auto', it will indent attributes so they all start at the same column + 'mode' => 'simplexml', // use 'simplexml' to use parent name as tagname if transforming an indexed array + 'addDoctype' => false, // add a doctype declaration + 'doctype' => null, // supply a string or an array with id and uri ({@see XML_Util::getDoctypeDeclaration()} + 'rootName' => 'package', // name of the root tag + 'rootAttributes' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0 +http://pear.php.net/dtd/tasks-1.0.xsd +http://pear.php.net/dtd/package-2.0 +http://pear.php.net/dtd/package-2.0.xsd', + ), // attributes of the root tag + 'attributesArray' => 'attribs', // all values in this key will be treated as attributes + 'contentName' => '_content', // this value will be used directly as content, instead of creating a new tag, may only be used in conjuction with attributesArray + 'beautifyFilelist' => false, + 'encoding' => 'UTF-8', + ); + + /** + * options for the serialization + * @access private + * @var array $options + */ + var $options = array(); + + /** + * current tag depth + * @var integer $_tagDepth + */ + var $_tagDepth = 0; + + /** + * serilialized representation of the data + * @var string $_serializedData + */ + var $_serializedData = null; + /** + * @var PEAR_PackageFile_v2 + */ + var $_packagefile; + /** + * @param PEAR_PackageFile_v2 + */ + function PEAR_PackageFile_Generator_v2(&$packagefile) + { + $this->_packagefile = &$packagefile; + if (isset($this->_packagefile->encoding)) { + $this->_defaultOptions['encoding'] = $this->_packagefile->encoding; + } + } + + /** + * @return string + */ + function getPackagerVersion() + { + return '1.9.0'; + } + + /** + * @param PEAR_Packager + * @param bool generate a .tgz or a .tar + * @param string|null temporary directory to package in + */ + function toTgz(&$packager, $compress = true, $where = null) + { + $a = null; + return $this->toTgz2($packager, $a, $compress, $where); + } + + /** + * Package up both a package.xml and package2.xml for the same release + * @param PEAR_Packager + * @param PEAR_PackageFile_v1 + * @param bool generate a .tgz or a .tar + * @param string|null temporary directory to package in + */ + function toTgz2(&$packager, &$pf1, $compress = true, $where = null) + { + require_once 'Archive/Tar.php'; + if (!$this->_packagefile->isEquivalent($pf1)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' . + basename($pf1->getPackageFile()) . + '" is not equivalent to "' . basename($this->_packagefile->getPackageFile()) + . '"'); + } + + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' . $where . '" could' . + ' not be created'); + } + + $file = $where . DIRECTORY_SEPARATOR . 'package.xml'; + if (file_exists($file) && !is_file($file)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: unable to save package.xml as' . + ' "' . $file .'"'); + } + + if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: invalid package.xml'); + } + + $ext = $compress ? '.tgz' : '.tar'; + $pkgver = $this->_packagefile->getPackage() . '-' . $this->_packagefile->getVersion(); + $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext; + if (file_exists($dest_package) && !is_file($dest_package)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: cannot create tgz file "' . + $dest_package . '"'); + } + + $pkgfile = $this->_packagefile->getPackageFile(); + if (!$pkgfile) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: package file object must ' . + 'be created from a real file'); + } + + $pkgdir = dirname(realpath($pkgfile)); + $pkgfile = basename($pkgfile); + + // {{{ Create the package file list + $filelist = array(); + $i = 0; + $this->_packagefile->flattenFilelist(); + $contents = $this->_packagefile->getContents(); + if (isset($contents['bundledpackage'])) { // bundles of packages + $contents = $contents['bundledpackage']; + if (!isset($contents[0])) { + $contents = array($contents); + } + + $packageDir = $where; + foreach ($contents as $i => $package) { + $fname = $package; + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return $packager->raiseError("File does not exist: $fname"); + } + + $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname; + System::mkdir(array('-p', dirname($tfile))); + copy($file, $tfile); + $filelist[$i++] = $tfile; + $packager->log(2, "Adding package $fname"); + } + } else { // normal packages + $contents = $contents['dir']['file']; + if (!isset($contents[0])) { + $contents = array($contents); + } + + $packageDir = $where; + foreach ($contents as $i => $file) { + $fname = $file['attribs']['name']; + $atts = $file['attribs']; + $orig = $file; + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return $packager->raiseError("File does not exist: $fname"); + } + + $origperms = fileperms($file); + $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname; + unset($orig['attribs']); + if (count($orig)) { // file with tasks + // run any package-time tasks + $contents = file_get_contents($file); + foreach ($orig as $tag => $raw) { + $tag = str_replace( + array($this->_packagefile->getTasksNs() . ':', '-'), + array('', '_'), $tag); + $task = "PEAR_Task_$tag"; + $task = &new $task($this->_packagefile->_config, + $this->_packagefile->_logger, + PEAR_TASK_PACKAGE); + $task->init($raw, $atts, null); + $res = $task->startSession($this->_packagefile, $contents, $tfile); + if (!$res) { + continue; // skip this task + } + + if (PEAR::isError($res)) { + return $res; + } + + $contents = $res; // save changes + System::mkdir(array('-p', dirname($tfile))); + $wp = fopen($tfile, "wb"); + fwrite($wp, $contents); + fclose($wp); + } + } + + if (!file_exists($tfile)) { + System::mkdir(array('-p', dirname($tfile))); + copy($file, $tfile); + } + + chmod($tfile, $origperms); + $filelist[$i++] = $tfile; + $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($tfile), $i - 1); + $packager->log(2, "Adding file $fname"); + } + } + // }}} + + $name = $pf1 !== null ? 'package2.xml' : 'package.xml'; + $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, $name); + if ($packagexml) { + $tar =& new Archive_Tar($dest_package, $compress); + $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors + // ----- Creates with the package.xml file + $ok = $tar->createModify(array($packagexml), '', $where); + if (PEAR::isError($ok)) { + return $packager->raiseError($ok); + } elseif (!$ok) { + return $packager->raiseError('PEAR_Packagefile_v2::toTgz(): adding ' . $name . + ' failed'); + } + + // ----- Add the content of the package + if (!$tar->addModify($filelist, $pkgver, $where)) { + return $packager->raiseError( + 'PEAR_Packagefile_v2::toTgz(): tarball creation failed'); + } + + // add the package.xml version 1.0 + if ($pf1 !== null) { + $pfgen = &$pf1->getDefaultGenerator(); + $packagexml1 = $pfgen->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true); + if (!$tar->addModify(array($packagexml1), '', $where)) { + return $packager->raiseError( + 'PEAR_Packagefile_v2::toTgz(): adding package.xml failed'); + } + } + + return $dest_package; + } + } + + function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml') + { + if (!$this->_packagefile->validate($state)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: invalid package.xml', + null, null, null, $this->_packagefile->getValidationWarnings()); + } + + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: "' . $where . '" could' . + ' not be created'); + } + + $newpkgfile = $where . DIRECTORY_SEPARATOR . $name; + $np = @fopen($newpkgfile, 'wb'); + if (!$np) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: unable to save ' . + "$name as $newpkgfile"); + } + fwrite($np, $this->toXml($state)); + fclose($np); + return $newpkgfile; + } + + function &toV2() + { + return $this->_packagefile; + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @return string XML data + */ + function toXml($state = PEAR_VALIDATE_NORMAL, $options = array()) + { + $this->_packagefile->setDate(date('Y-m-d')); + $this->_packagefile->setTime(date('H:i:s')); + if (!$this->_packagefile->validate($state)) { + return false; + } + + if (is_array($options)) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = $this->_defaultOptions; + } + + $arr = $this->_packagefile->getArray(); + if (isset($arr['filelist'])) { + unset($arr['filelist']); + } + + if (isset($arr['_lastversion'])) { + unset($arr['_lastversion']); + } + + // Fix the notes a little bit + if (isset($arr['notes'])) { + // This trims out the indenting, needs fixing + $arr['notes'] = "\n" . trim($arr['notes']) . "\n"; + } + + if (isset($arr['changelog']) && !empty($arr['changelog'])) { + // Fix for inconsistency how the array is filled depending on the changelog release amount + if (!isset($arr['changelog']['release'][0])) { + $release = $arr['changelog']['release']; + unset($arr['changelog']['release']); + + $arr['changelog']['release'] = array(); + $arr['changelog']['release'][0] = $release; + } + + foreach (array_keys($arr['changelog']['release']) as $key) { + $c =& $arr['changelog']['release'][$key]; + if (isset($c['notes'])) { + // This trims out the indenting, needs fixing + $c['notes'] = "\n" . trim($c['notes']) . "\n"; + } + } + } + + if ($state ^ PEAR_VALIDATE_PACKAGING && !isset($arr['bundle'])) { + $use = $this->_recursiveXmlFilelist($arr['contents']['dir']['file']); + unset($arr['contents']['dir']['file']); + if (isset($use['dir'])) { + $arr['contents']['dir']['dir'] = $use['dir']; + } + if (isset($use['file'])) { + $arr['contents']['dir']['file'] = $use['file']; + } + $this->options['beautifyFilelist'] = true; + } + + $arr['attribs']['packagerversion'] = '1.9.0'; + if ($this->serialize($arr, $options)) { + return $this->_serializedData . "\n"; + } + + return false; + } + + + function _recursiveXmlFilelist($list) + { + $dirs = array(); + if (isset($list['attribs'])) { + $file = $list['attribs']['name']; + unset($list['attribs']['name']); + $attributes = $list['attribs']; + $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes); + } else { + foreach ($list as $a) { + $file = $a['attribs']['name']; + $attributes = $a['attribs']; + unset($a['attribs']); + $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes, $a); + } + } + $this->_formatDir($dirs); + $this->_deFormat($dirs); + return $dirs; + } + + function _addDir(&$dirs, $dir, $file = null, $attributes = null, $tasks = null) + { + if (!$tasks) { + $tasks = array(); + } + if ($dir == array() || $dir == array('.')) { + $dirs['file'][basename($file)] = $tasks; + $attributes['name'] = basename($file); + $dirs['file'][basename($file)]['attribs'] = $attributes; + return; + } + $curdir = array_shift($dir); + if (!isset($dirs['dir'][$curdir])) { + $dirs['dir'][$curdir] = array(); + } + $this->_addDir($dirs['dir'][$curdir], $dir, $file, $attributes, $tasks); + } + + function _formatDir(&$dirs) + { + if (!count($dirs)) { + return array(); + } + $newdirs = array(); + if (isset($dirs['dir'])) { + $newdirs['dir'] = $dirs['dir']; + } + if (isset($dirs['file'])) { + $newdirs['file'] = $dirs['file']; + } + $dirs = $newdirs; + if (isset($dirs['dir'])) { + uksort($dirs['dir'], 'strnatcasecmp'); + foreach ($dirs['dir'] as $dir => $contents) { + $this->_formatDir($dirs['dir'][$dir]); + } + } + if (isset($dirs['file'])) { + uksort($dirs['file'], 'strnatcasecmp'); + }; + } + + function _deFormat(&$dirs) + { + if (!count($dirs)) { + return array(); + } + $newdirs = array(); + if (isset($dirs['dir'])) { + foreach ($dirs['dir'] as $dir => $contents) { + $newdir = array(); + $newdir['attribs']['name'] = $dir; + $this->_deFormat($contents); + foreach ($contents as $tag => $val) { + $newdir[$tag] = $val; + } + $newdirs['dir'][] = $newdir; + } + if (count($newdirs['dir']) == 1) { + $newdirs['dir'] = $newdirs['dir'][0]; + } + } + if (isset($dirs['file'])) { + foreach ($dirs['file'] as $name => $file) { + $newdirs['file'][] = $file; + } + if (count($newdirs['file']) == 1) { + $newdirs['file'] = $newdirs['file'][0]; + } + } + $dirs = $newdirs; + } + + /** + * reset all options to default options + * + * @access public + * @see setOption(), XML_Unserializer() + */ + function resetOptions() + { + $this->options = $this->_defaultOptions; + } + + /** + * set an option + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Serializer() + */ + function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * sets several options at once + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Unserializer(), setOption() + */ + function setOptions($options) + { + $this->options = array_merge($this->options, $options); + } + + /** + * serialize data + * + * @access public + * @param mixed $data data to serialize + * @return boolean true on success, pear error on failure + */ + function serialize($data, $options = null) + { + // if options have been specified, use them instead + // of the previously defined ones + if (is_array($options)) { + $optionsBak = $this->options; + if (isset($options['overrideOptions']) && $options['overrideOptions'] == true) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = array_merge($this->options, $options); + } + } else { + $optionsBak = null; + } + + // start depth is zero + $this->_tagDepth = 0; + $this->_serializedData = ''; + // serialize an array + if (is_array($data)) { + $tagName = isset($this->options['rootName']) ? $this->options['rootName'] : 'array'; + $this->_serializedData .= $this->_serializeArray($data, $tagName, $this->options['rootAttributes']); + } + + // add doctype declaration + if ($this->options['addDoctype'] === true) { + $this->_serializedData = XML_Util::getDoctypeDeclaration($tagName, $this->options['doctype']) + . $this->options['linebreak'] + . $this->_serializedData; + } + + // build xml declaration + if ($this->options['addDecl']) { + $atts = array(); + $encoding = isset($this->options['encoding']) ? $this->options['encoding'] : null; + $this->_serializedData = XML_Util::getXMLDeclaration('1.0', $encoding) + . $this->options['linebreak'] + . $this->_serializedData; + } + + + if ($optionsBak !== null) { + $this->options = $optionsBak; + } + + return true; + } + + /** + * get the result of the serialization + * + * @access public + * @return string serialized XML + */ + function getSerializedData() + { + if ($this->_serializedData === null) { + return $this->raiseError('No serialized data available. Use XML_Serializer::serialize() first.', XML_SERIALIZER_ERROR_NO_SERIALIZATION); + } + return $this->_serializedData; + } + + /** + * serialize any value + * + * This method checks for the type of the value and calls the appropriate method + * + * @access private + * @param mixed $value + * @param string $tagName + * @param array $attributes + * @return string + */ + function _serializeValue($value, $tagName = null, $attributes = array()) + { + if (is_array($value)) { + $xml = $this->_serializeArray($value, $tagName, $attributes); + } elseif (is_object($value)) { + $xml = $this->_serializeObject($value, $tagName); + } else { + $tag = array( + 'qname' => $tagName, + 'attributes' => $attributes, + 'content' => $value + ); + $xml = $this->_createXMLTag($tag); + } + return $xml; + } + + /** + * serialize an array + * + * @access private + * @param array $array array to serialize + * @param string $tagName name of the root tag + * @param array $attributes attributes for the root tag + * @return string $string serialized data + * @uses XML_Util::isValidName() to check, whether key has to be substituted + */ + function _serializeArray(&$array, $tagName = null, $attributes = array()) + { + $_content = null; + + /** + * check for special attributes + */ + if ($this->options['attributesArray'] !== null) { + if (isset($array[$this->options['attributesArray']])) { + $attributes = $array[$this->options['attributesArray']]; + unset($array[$this->options['attributesArray']]); + } + /** + * check for special content + */ + if ($this->options['contentName'] !== null) { + if (isset($array[$this->options['contentName']])) { + $_content = $array[$this->options['contentName']]; + unset($array[$this->options['contentName']]); + } + } + } + + /* + * if mode is set to simpleXML, check whether + * the array is associative or indexed + */ + if (is_array($array) && $this->options['mode'] == 'simplexml') { + $indexed = true; + if (!count($array)) { + $indexed = false; + } + foreach ($array as $key => $val) { + if (!is_int($key)) { + $indexed = false; + break; + } + } + + if ($indexed && $this->options['mode'] == 'simplexml') { + $string = ''; + foreach ($array as $key => $val) { + if ($this->options['beautifyFilelist'] && $tagName == 'dir') { + if (!isset($this->_curdir)) { + $this->_curdir = ''; + } + $savedir = $this->_curdir; + if (isset($val['attribs'])) { + if ($val['attribs']['name'] == '/') { + $this->_curdir = '/'; + } else { + if ($this->_curdir == '/') { + $this->_curdir = ''; + } + $this->_curdir .= '/' . $val['attribs']['name']; + } + } + } + $string .= $this->_serializeValue( $val, $tagName, $attributes); + if ($this->options['beautifyFilelist'] && $tagName == 'dir') { + $string .= ' '; + if (empty($savedir)) { + unset($this->_curdir); + } else { + $this->_curdir = $savedir; + } + } + + $string .= $this->options['linebreak']; + // do indentation + if ($this->options['indent'] !== null && $this->_tagDepth > 0) { + $string .= str_repeat($this->options['indent'], $this->_tagDepth); + } + } + return rtrim($string); + } + } + + if ($this->options['scalarAsAttributes'] === true) { + foreach ($array as $key => $value) { + if (is_scalar($value) && (XML_Util::isValidName($key) === true)) { + unset($array[$key]); + $attributes[$this->options['prependAttributes'].$key] = $value; + } + } + } + + // check for empty array => create empty tag + if (empty($array)) { + $tag = array( + 'qname' => $tagName, + 'content' => $_content, + 'attributes' => $attributes + ); + + } else { + $this->_tagDepth++; + $tmp = $this->options['linebreak']; + foreach ($array as $key => $value) { + // do indentation + if ($this->options['indent'] !== null && $this->_tagDepth > 0) { + $tmp .= str_repeat($this->options['indent'], $this->_tagDepth); + } + + // copy key + $origKey = $key; + // key cannot be used as tagname => use default tag + $valid = XML_Util::isValidName($key); + if (PEAR::isError($valid)) { + if ($this->options['classAsTagName'] && is_object($value)) { + $key = get_class($value); + } else { + $key = $this->options['defaultTagName']; + } + } + $atts = array(); + if ($this->options['typeHints'] === true) { + $atts[$this->options['typeAttribute']] = gettype($value); + if ($key !== $origKey) { + $atts[$this->options['keyAttribute']] = (string)$origKey; + } + + } + if ($this->options['beautifyFilelist'] && $key == 'dir') { + if (!isset($this->_curdir)) { + $this->_curdir = ''; + } + $savedir = $this->_curdir; + if (isset($value['attribs'])) { + if ($value['attribs']['name'] == '/') { + $this->_curdir = '/'; + } else { + $this->_curdir .= '/' . $value['attribs']['name']; + } + } + } + + if (is_string($value) && $value && ($value{strlen($value) - 1} == "\n")) { + $value .= str_repeat($this->options['indent'], $this->_tagDepth); + } + $tmp .= $this->_createXMLTag(array( + 'qname' => $key, + 'attributes' => $atts, + 'content' => $value ) + ); + if ($this->options['beautifyFilelist'] && $key == 'dir') { + if (isset($value['attribs'])) { + $tmp .= ' '; + if (empty($savedir)) { + unset($this->_curdir); + } else { + $this->_curdir = $savedir; + } + } + } + $tmp .= $this->options['linebreak']; + } + + $this->_tagDepth--; + if ($this->options['indent']!==null && $this->_tagDepth>0) { + $tmp .= str_repeat($this->options['indent'], $this->_tagDepth); + } + + if (trim($tmp) === '') { + $tmp = null; + } + + $tag = array( + 'qname' => $tagName, + 'content' => $tmp, + 'attributes' => $attributes + ); + } + if ($this->options['typeHints'] === true) { + if (!isset($tag['attributes'][$this->options['typeAttribute']])) { + $tag['attributes'][$this->options['typeAttribute']] = 'array'; + } + } + + $string = $this->_createXMLTag($tag, false); + return $string; + } + + /** + * create a tag from an array + * this method awaits an array in the following format + * array( + * 'qname' => $tagName, + * 'attributes' => array(), + * 'content' => $content, // optional + * 'namespace' => $namespace // optional + * 'namespaceUri' => $namespaceUri // optional + * ) + * + * @access private + * @param array $tag tag definition + * @param boolean $replaceEntities whether to replace XML entities in content or not + * @return string $string XML tag + */ + function _createXMLTag($tag, $replaceEntities = true) + { + if ($this->options['indentAttributes'] !== false) { + $multiline = true; + $indent = str_repeat($this->options['indent'], $this->_tagDepth); + + if ($this->options['indentAttributes'] == '_auto') { + $indent .= str_repeat(' ', (strlen($tag['qname'])+2)); + + } else { + $indent .= $this->options['indentAttributes']; + } + } else { + $indent = $multiline = false; + } + + if (is_array($tag['content'])) { + if (empty($tag['content'])) { + $tag['content'] = ''; + } + } elseif(is_scalar($tag['content']) && (string)$tag['content'] == '') { + $tag['content'] = ''; + } + + if (is_scalar($tag['content']) || is_null($tag['content'])) { + if ($this->options['encoding'] == 'UTF-8' && + version_compare(phpversion(), '5.0.0', 'lt') + ) { + $tag['content'] = utf8_encode($tag['content']); + } + + if ($replaceEntities === true) { + $replaceEntities = XML_UTIL_ENTITIES_XML; + } + + $tag = XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $this->options['linebreak']); + } elseif (is_array($tag['content'])) { + $tag = $this->_serializeArray($tag['content'], $tag['qname'], $tag['attributes']); + } elseif (is_object($tag['content'])) { + $tag = $this->_serializeObject($tag['content'], $tag['qname'], $tag['attributes']); + } elseif (is_resource($tag['content'])) { + settype($tag['content'], 'string'); + $tag = XML_Util::createTagFromArray($tag, $replaceEntities); + } + return $tag; + } +}PEAR-1.9.0/PEAR/PackageFile/Parser/v1.php100664 764 764 40314 100664 12505 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v1.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * package.xml abstraction class + */ +require_once 'PEAR/PackageFile/v1.php'; +/** + * Parser for package.xml version 1.0 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Parser_v1 +{ + var $_registry; + var $_config; + var $_logger; + /** + * BC hack to allow PEAR_Common::infoFromString() to sort of + * work with the version 2.0 format - there's no filelist though + * @param PEAR_PackageFile_v2 + */ + function fromV2($packagefile) + { + $info = $packagefile->getArray(true); + $ret = new PEAR_PackageFile_v1; + $ret->fromArray($info['old']); + } + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + /** + * @param string contents of package.xml file, version 1.0 + * @return bool success of parsing + */ + function &parse($data, $file, $archive = false) + { + if (!extension_loaded('xml')) { + return PEAR::raiseError('Cannot create xml parser for parsing package.xml, no xml extension'); + } + $xp = xml_parser_create(); + if (!$xp) { + $a = &PEAR::raiseError('Cannot create xml parser for parsing package.xml'); + return $a; + } + xml_set_object($xp, $this); + xml_set_element_handler($xp, '_element_start_1_0', '_element_end_1_0'); + xml_set_character_data_handler($xp, '_pkginfo_cdata_1_0'); + xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false); + + $this->element_stack = array(); + $this->_packageInfo = array('provides' => array()); + $this->current_element = false; + unset($this->dir_install); + $this->_packageInfo['filelist'] = array(); + $this->filelist =& $this->_packageInfo['filelist']; + $this->dir_names = array(); + $this->in_changelog = false; + $this->d_i = 0; + $this->cdata = ''; + $this->_isValid = true; + + if (!xml_parse($xp, $data, 1)) { + $code = xml_get_error_code($xp); + $line = xml_get_current_line_number($xp); + xml_parser_free($xp); + $a = &PEAR::raiseError(sprintf("XML error: %s at line %d", + $str = xml_error_string($code), $line), 2); + return $a; + } + + xml_parser_free($xp); + + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + if (isset($this->_logger)) { + $pf->setLogger($this->_logger); + } + $pf->setPackagefile($file, $archive); + $pf->fromArray($this->_packageInfo); + return $pf; + } + // {{{ _unIndent() + + /** + * Unindent given string + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } elseif (trim(substr($line, 0, $indent_len))) { + $data .= ltrim($line); + } + } + return $data; + } + + // Support for package DTD v1.0: + // {{{ _element_start_1_0() + + /** + * XML parser callback for ending elements. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name name of ending element + * + * @return void + * + * @access private + */ + function _element_start_1_0($xp, $name, $attribs) + { + array_push($this->element_stack, $name); + $this->current_element = $name; + $spos = sizeof($this->element_stack) - 2; + $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : ''; + $this->current_attributes = $attribs; + $this->cdata = ''; + switch ($name) { + case 'dir': + if ($this->in_changelog) { + break; + } + if (array_key_exists('name', $attribs) && $attribs['name'] != '/') { + $attribs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attribs['name']); + if (strrpos($attribs['name'], '/') === strlen($attribs['name']) - 1) { + $attribs['name'] = substr($attribs['name'], 0, + strlen($attribs['name']) - 1); + } + if (strpos($attribs['name'], '/') === 0) { + $attribs['name'] = substr($attribs['name'], 1); + } + $this->dir_names[] = $attribs['name']; + } + if (isset($attribs['baseinstalldir'])) { + $this->dir_install = $attribs['baseinstalldir']; + } + if (isset($attribs['role'])) { + $this->dir_role = $attribs['role']; + } + break; + case 'file': + if ($this->in_changelog) { + break; + } + if (isset($attribs['name'])) { + $path = ''; + if (count($this->dir_names)) { + foreach ($this->dir_names as $dir) { + $path .= $dir . '/'; + } + } + $path .= preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attribs['name']); + unset($attribs['name']); + $this->current_path = $path; + $this->filelist[$path] = $attribs; + // Set the baseinstalldir only if the file don't have this attrib + if (!isset($this->filelist[$path]['baseinstalldir']) && + isset($this->dir_install)) + { + $this->filelist[$path]['baseinstalldir'] = $this->dir_install; + } + // Set the Role + if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) { + $this->filelist[$path]['role'] = $this->dir_role; + } + } + break; + case 'replace': + if (!$this->in_changelog) { + $this->filelist[$this->current_path]['replacements'][] = $attribs; + } + break; + case 'maintainers': + $this->_packageInfo['maintainers'] = array(); + $this->m_i = 0; // maintainers array index + break; + case 'maintainer': + // compatibility check + if (!isset($this->_packageInfo['maintainers'])) { + $this->_packageInfo['maintainers'] = array(); + $this->m_i = 0; + } + $this->_packageInfo['maintainers'][$this->m_i] = array(); + $this->current_maintainer =& $this->_packageInfo['maintainers'][$this->m_i]; + break; + case 'changelog': + $this->_packageInfo['changelog'] = array(); + $this->c_i = 0; // changelog array index + $this->in_changelog = true; + break; + case 'release': + if ($this->in_changelog) { + $this->_packageInfo['changelog'][$this->c_i] = array(); + $this->current_release = &$this->_packageInfo['changelog'][$this->c_i]; + } else { + $this->current_release = &$this->_packageInfo; + } + break; + case 'deps': + if (!$this->in_changelog) { + $this->_packageInfo['release_deps'] = array(); + } + break; + case 'dep': + // dependencies array index + if (!$this->in_changelog) { + $this->d_i++; + isset($attribs['type']) ? ($attribs['type'] = strtolower($attribs['type'])) : false; + $this->_packageInfo['release_deps'][$this->d_i] = $attribs; + } + break; + case 'configureoptions': + if (!$this->in_changelog) { + $this->_packageInfo['configure_options'] = array(); + } + break; + case 'configureoption': + if (!$this->in_changelog) { + $this->_packageInfo['configure_options'][] = $attribs; + } + break; + case 'provides': + if (empty($attribs['type']) || empty($attribs['name'])) { + break; + } + $attribs['explicit'] = true; + $this->_packageInfo['provides']["$attribs[type];$attribs[name]"] = $attribs; + break; + case 'package' : + if (isset($attribs['version'])) { + $this->_packageInfo['xsdversion'] = trim($attribs['version']); + } else { + $this->_packageInfo['xsdversion'] = '1.0'; + } + if (isset($attribs['packagerversion'])) { + $this->_packageInfo['packagerversion'] = $attribs['packagerversion']; + } + break; + } + } + + // }}} + // {{{ _element_end_1_0() + + /** + * XML parser callback for ending elements. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name name of ending element + * + * @return void + * + * @access private + */ + function _element_end_1_0($xp, $name) + { + $data = trim($this->cdata); + switch ($name) { + case 'name': + switch ($this->prev_element) { + case 'package': + $this->_packageInfo['package'] = $data; + break; + case 'maintainer': + $this->current_maintainer['name'] = $data; + break; + } + break; + case 'extends' : + $this->_packageInfo['extends'] = $data; + break; + case 'summary': + $this->_packageInfo['summary'] = $data; + break; + case 'description': + $data = $this->_unIndent($this->cdata); + $this->_packageInfo['description'] = $data; + break; + case 'user': + $this->current_maintainer['handle'] = $data; + break; + case 'email': + $this->current_maintainer['email'] = $data; + break; + case 'role': + $this->current_maintainer['role'] = $data; + break; + case 'version': + if ($this->in_changelog) { + $this->current_release['version'] = $data; + } else { + $this->_packageInfo['version'] = $data; + } + break; + case 'date': + if ($this->in_changelog) { + $this->current_release['release_date'] = $data; + } else { + $this->_packageInfo['release_date'] = $data; + } + break; + case 'notes': + // try to "de-indent" release notes in case someone + // has been over-indenting their xml ;-) + // Trim only on the right side + $data = rtrim($this->_unIndent($this->cdata)); + if ($this->in_changelog) { + $this->current_release['release_notes'] = $data; + } else { + $this->_packageInfo['release_notes'] = $data; + } + break; + case 'warnings': + if ($this->in_changelog) { + $this->current_release['release_warnings'] = $data; + } else { + $this->_packageInfo['release_warnings'] = $data; + } + break; + case 'state': + if ($this->in_changelog) { + $this->current_release['release_state'] = $data; + } else { + $this->_packageInfo['release_state'] = $data; + } + break; + case 'license': + if ($this->in_changelog) { + $this->current_release['release_license'] = $data; + } else { + $this->_packageInfo['release_license'] = $data; + } + break; + case 'dep': + if ($data && !$this->in_changelog) { + $this->_packageInfo['release_deps'][$this->d_i]['name'] = $data; + } + break; + case 'dir': + if ($this->in_changelog) { + break; + } + array_pop($this->dir_names); + break; + case 'file': + if ($this->in_changelog) { + break; + } + if ($data) { + $path = ''; + if (count($this->dir_names)) { + foreach ($this->dir_names as $dir) { + $path .= $dir . '/'; + } + } + $path .= $data; + $this->filelist[$path] = $this->current_attributes; + // Set the baseinstalldir only if the file don't have this attrib + if (!isset($this->filelist[$path]['baseinstalldir']) && + isset($this->dir_install)) + { + $this->filelist[$path]['baseinstalldir'] = $this->dir_install; + } + // Set the Role + if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) { + $this->filelist[$path]['role'] = $this->dir_role; + } + } + break; + case 'maintainer': + if (empty($this->_packageInfo['maintainers'][$this->m_i]['role'])) { + $this->_packageInfo['maintainers'][$this->m_i]['role'] = 'lead'; + } + $this->m_i++; + break; + case 'release': + if ($this->in_changelog) { + $this->c_i++; + } + break; + case 'changelog': + $this->in_changelog = false; + break; + } + array_pop($this->element_stack); + $spos = sizeof($this->element_stack) - 1; + $this->current_element = ($spos > 0) ? $this->element_stack[$spos] : ''; + $this->cdata = ''; + } + + // }}} + // {{{ _pkginfo_cdata_1_0() + + /** + * XML parser callback for character data. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name character data + * + * @return void + * + * @access private + */ + function _pkginfo_cdata_1_0($xp, $data) + { + if (isset($this->cdata)) { + $this->cdata .= $data; + } + } + + // }}} +} +?>PEAR-1.9.0/PEAR/PackageFile/Parser/v2.php100664 764 764 6215 100664 12470 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v2.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * base xml parser class + */ +require_once 'PEAR/XMLParser.php'; +require_once 'PEAR/PackageFile/v2.php'; +/** + * Parser for package.xml version 2.0 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Parser_v2 extends PEAR_XMLParser +{ + var $_config; + var $_logger; + var $_registry; + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + /** + * Unindent given string + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } else { + $data .= $line . "\n"; + } + } + return $data; + } + + /** + * post-process data + * + * @param string $data + * @param string $element element name + */ + function postProcess($data, $element) + { + if ($element == 'notes') { + return trim($this->_unIndent($data)); + } + return trim($data); + } + + /** + * @param string + * @param string file name of the package.xml + * @param string|false name of the archive this package.xml came from, if any + * @param string class name to instantiate and return. This must be PEAR_PackageFile_v2 or + * a subclass + * @return PEAR_PackageFile_v2 + */ + function &parse($data, $file, $archive = false, $class = 'PEAR_PackageFile_v2') + { + if (PEAR::isError($err = parent::parse($data, $file))) { + return $err; + } + + $ret = new $class; + $ret->encoding = $this->encoding; + $ret->setConfig($this->_config); + if (isset($this->_logger)) { + $ret->setLogger($this->_logger); + } + + $ret->fromArray($this->_unserializedData); + $ret->setPackagefile($file, $archive); + return $ret; + } +}PEAR-1.9.0/PEAR/PackageFile/v2/rw.php100664 764 764 173210 100664 11724 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a8 + */ +/** + * For base class + */ +require_once 'PEAR/PackageFile/v2.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a8 + */ +class PEAR_PackageFile_v2_rw extends PEAR_PackageFile_v2 +{ + /** + * @param string Extension name + * @return bool success of operation + */ + function setProvidesExtension($extension) + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + if (!isset($this->_packageInfo['providesextension'])) { + // ensure that the channel tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('usesrole', 'usestask', 'srcpackage', 'srcuri', 'phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), + $extension, 'providesextension'); + } + $this->_packageInfo['providesextension'] = $extension; + return true; + } + return false; + } + + function setPackage($package) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['attribs'])) { + $this->_packageInfo = array_merge(array('attribs' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0 + http://pear.php.net/dtd/tasks-1.0.xsd + http://pear.php.net/dtd/package-2.0 + http://pear.php.net/dtd/package-2.0.xsd', + )), $this->_packageInfo); + } + if (!isset($this->_packageInfo['name'])) { + return $this->_packageInfo = array_merge(array('name' => $package), + $this->_packageInfo); + } + $this->_packageInfo['name'] = $package; + } + + /** + * set this as a package.xml version 2.1 + * @access private + */ + function _setPackageVersion2_1() + { + $info = array( + 'version' => '2.1', + 'xmlns' => 'http://pear.php.net/dtd/package-2.1', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0 + http://pear.php.net/dtd/tasks-1.0.xsd + http://pear.php.net/dtd/package-2.1 + http://pear.php.net/dtd/package-2.1.xsd', + ); + if (!isset($this->_packageInfo['attribs'])) { + $this->_packageInfo = array_merge(array('attribs' => $info), $this->_packageInfo); + } else { + $this->_packageInfo['attribs'] = $info; + } + } + + function setUri($uri) + { + unset($this->_packageInfo['channel']); + $this->_isValid = 0; + if (!isset($this->_packageInfo['uri'])) { + // ensure that the uri tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('extends', 'summary', 'description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $uri, 'uri'); + } + $this->_packageInfo['uri'] = $uri; + } + + function setChannel($channel) + { + unset($this->_packageInfo['uri']); + $this->_isValid = 0; + if (!isset($this->_packageInfo['channel'])) { + // ensure that the channel tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('extends', 'summary', 'description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $channel, 'channel'); + } + $this->_packageInfo['channel'] = $channel; + } + + function setExtends($extends) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['extends'])) { + // ensure that the extends tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('summary', 'description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $extends, 'extends'); + } + $this->_packageInfo['extends'] = $extends; + } + + function setSummary($summary) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['summary'])) { + // ensure that the summary tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $summary, 'summary'); + } + $this->_packageInfo['summary'] = $summary; + } + + function setDescription($desc) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['description'])) { + // ensure that the description tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $desc, 'description'); + } + $this->_packageInfo['description'] = $desc; + } + + /** + * Adds a new maintainer - no checking of duplicates is performed, use + * updatemaintainer for that purpose. + */ + function addMaintainer($role, $handle, $name, $email, $active = 'yes') + { + if (!in_array($role, array('lead', 'developer', 'contributor', 'helper'))) { + return false; + } + if (isset($this->_packageInfo[$role])) { + if (!isset($this->_packageInfo[$role][0])) { + $this->_packageInfo[$role] = array($this->_packageInfo[$role]); + } + $this->_packageInfo[$role][] = + array( + 'name' => $name, + 'user' => $handle, + 'email' => $email, + 'active' => $active, + ); + } else { + $testarr = array('lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', + 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'); + foreach (array('lead', 'developer', 'contributor', 'helper') as $testrole) { + array_shift($testarr); + if ($role == $testrole) { + break; + } + } + if (!isset($this->_packageInfo[$role])) { + // ensure that the extends tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, $testarr, + array(), $role); + } + $this->_packageInfo[$role] = + array( + 'name' => $name, + 'user' => $handle, + 'email' => $email, + 'active' => $active, + ); + } + $this->_isValid = 0; + } + + function updateMaintainer($newrole, $handle, $name, $email, $active = 'yes') + { + $found = false; + foreach (array('lead', 'developer', 'contributor', 'helper') as $role) { + if (!isset($this->_packageInfo[$role])) { + continue; + } + $info = $this->_packageInfo[$role]; + if (!isset($info[0])) { + if ($info['user'] == $handle) { + $found = true; + break; + } + } + foreach ($info as $i => $maintainer) { + if ($maintainer['user'] == $handle) { + $found = $i; + break 2; + } + } + } + if ($found === false) { + return $this->addMaintainer($newrole, $handle, $name, $email, $active); + } + if ($found !== false) { + if ($found === true) { + unset($this->_packageInfo[$role]); + } else { + unset($this->_packageInfo[$role][$found]); + $this->_packageInfo[$role] = array_values($this->_packageInfo[$role]); + } + } + $this->addMaintainer($newrole, $handle, $name, $email, $active); + $this->_isValid = 0; + } + + function deleteMaintainer($handle) + { + $found = false; + foreach (array('lead', 'developer', 'contributor', 'helper') as $role) { + if (!isset($this->_packageInfo[$role])) { + continue; + } + if (!isset($this->_packageInfo[$role][0])) { + $this->_packageInfo[$role] = array($this->_packageInfo[$role]); + } + foreach ($this->_packageInfo[$role] as $i => $maintainer) { + if ($maintainer['user'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo[$role][$found]); + if (!count($this->_packageInfo[$role]) && $role == 'lead') { + $this->_isValid = 0; + } + if (!count($this->_packageInfo[$role])) { + unset($this->_packageInfo[$role]); + return true; + } + $this->_packageInfo[$role] = + array_values($this->_packageInfo[$role]); + if (count($this->_packageInfo[$role]) == 1) { + $this->_packageInfo[$role] = $this->_packageInfo[$role][0]; + } + return true; + } + if (count($this->_packageInfo[$role]) == 1) { + $this->_packageInfo[$role] = $this->_packageInfo[$role][0]; + } + } + return false; + } + + function setReleaseVersion($version) + { + if (isset($this->_packageInfo['version']) && + isset($this->_packageInfo['version']['release'])) { + unset($this->_packageInfo['version']['release']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $version, array( + 'version' => array('stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'release' => array('api'))); + $this->_isValid = 0; + } + + function setAPIVersion($version) + { + if (isset($this->_packageInfo['version']) && + isset($this->_packageInfo['version']['api'])) { + unset($this->_packageInfo['version']['api']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $version, array( + 'version' => array('stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'api' => array())); + $this->_isValid = 0; + } + + /** + * snapshot|devel|alpha|beta|stable + */ + function setReleaseStability($state) + { + if (isset($this->_packageInfo['stability']) && + isset($this->_packageInfo['stability']['release'])) { + unset($this->_packageInfo['stability']['release']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $state, array( + 'stability' => array('license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'release' => array('api'))); + $this->_isValid = 0; + } + + /** + * @param devel|alpha|beta|stable + */ + function setAPIStability($state) + { + if (isset($this->_packageInfo['stability']) && + isset($this->_packageInfo['stability']['api'])) { + unset($this->_packageInfo['stability']['api']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $state, array( + 'stability' => array('license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'api' => array())); + $this->_isValid = 0; + } + + function setLicense($license, $uri = false, $filesource = false) + { + if (!isset($this->_packageInfo['license'])) { + // ensure that the license tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), 0, 'license'); + } + if ($uri || $filesource) { + $attribs = array(); + if ($uri) { + $attribs['uri'] = $uri; + } + $uri = true; // for test below + if ($filesource) { + $attribs['filesource'] = $filesource; + } + } + $license = $uri ? array('attribs' => $attribs, '_content' => $license) : $license; + $this->_packageInfo['license'] = $license; + $this->_isValid = 0; + } + + function setNotes($notes) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['notes'])) { + // ensure that the notes tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $notes, 'notes'); + } + $this->_packageInfo['notes'] = $notes; + } + + /** + * This is only used at install-time, after all serialization + * is over. + * @param string file name + * @param string installed path + */ + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']); + } else { + $this->_packageInfo['filelist'][$file] = $atts['attribs']; + } + } + + /** + * Reset the listing of package contents + * @param string base installation dir for the whole package, if any + */ + function clearContents($baseinstall = false) + { + $this->_filesValid = false; + $this->_isValid = 0; + if (!isset($this->_packageInfo['contents'])) { + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', + 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), array(), 'contents'); + } + if ($this->getPackageType() != 'bundle') { + $this->_packageInfo['contents'] = + array('dir' => array('attribs' => array('name' => '/'))); + if ($baseinstall) { + $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'] = $baseinstall; + } + } else { + $this->_packageInfo['contents'] = array('bundledpackage' => array()); + } + } + + /** + * @param string relative path of the bundled package. + */ + function addBundledPackage($path) + { + if ($this->getPackageType() != 'bundle') { + return false; + } + $this->_filesValid = false; + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $path, array( + 'contents' => array('compatible', 'dependencies', 'providesextension', + 'usesrole', 'usestask', 'srcpackage', 'srcuri', 'phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), + 'bundledpackage' => array())); + } + + /** + * @param string file name + * @param PEAR_Task_Common a read/write task + */ + function addTaskToFile($filename, $task) + { + if (!method_exists($task, 'getXml')) { + return false; + } + if (!method_exists($task, 'getName')) { + return false; + } + if (!method_exists($task, 'validate')) { + return false; + } + if (!$task->validate()) { + return false; + } + if (!isset($this->_packageInfo['contents']['dir']['file'])) { + return false; + } + $this->getTasksNs(); // discover the tasks namespace if not done already + $files = $this->_packageInfo['contents']['dir']['file']; + if (!isset($files[0])) { + $files = array($files); + $ind = false; + } else { + $ind = true; + } + foreach ($files as $i => $file) { + if (isset($file['attribs'])) { + if ($file['attribs']['name'] == $filename) { + if ($ind) { + $t = isset($this->_packageInfo['contents']['dir']['file'][$i] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()]) ? + $this->_packageInfo['contents']['dir']['file'][$i] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()] : false; + if ($t && !isset($t[0])) { + $this->_packageInfo['contents']['dir']['file'][$i] + [$this->_tasksNs . ':' . $task->getName()] = array($t); + } + $this->_packageInfo['contents']['dir']['file'][$i][$this->_tasksNs . + ':' . $task->getName()][] = $task->getXml(); + } else { + $t = isset($this->_packageInfo['contents']['dir']['file'] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()]) ? $this->_packageInfo['contents']['dir']['file'] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()] : false; + if ($t && !isset($t[0])) { + $this->_packageInfo['contents']['dir']['file'] + [$this->_tasksNs . ':' . $task->getName()] = array($t); + } + $this->_packageInfo['contents']['dir']['file'][$this->_tasksNs . + ':' . $task->getName()][] = $task->getXml(); + } + return true; + } + } + } + return false; + } + + /** + * @param string path to the file + * @param string filename + * @param array extra attributes + */ + function addFile($dir, $file, $attrs) + { + if ($this->getPackageType() == 'bundle') { + return false; + } + $this->_filesValid = false; + $this->_isValid = 0; + $dir = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), $dir); + if ($dir == '/' || $dir == '') { + $dir = ''; + } else { + $dir .= '/'; + } + $attrs['name'] = $dir . $file; + if (!isset($this->_packageInfo['contents'])) { + // ensure that the contents tag is set up + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('compatible', 'dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', + 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), array(), 'contents'); + } + if (isset($this->_packageInfo['contents']['dir']['file'])) { + if (!isset($this->_packageInfo['contents']['dir']['file'][0])) { + $this->_packageInfo['contents']['dir']['file'] = + array($this->_packageInfo['contents']['dir']['file']); + } + $this->_packageInfo['contents']['dir']['file'][]['attribs'] = $attrs; + } else { + $this->_packageInfo['contents']['dir']['file']['attribs'] = $attrs; + } + } + + /** + * @param string Dependent package name + * @param string Dependent package's channel name + * @param string minimum version of specified package that this release is guaranteed to be + * compatible with + * @param string maximum version of specified package that this release is guaranteed to be + * compatible with + * @param string versions of specified package that this release is not compatible with + */ + function addCompatiblePackage($name, $channel, $min, $max, $exclude = false) + { + $this->_isValid = 0; + $set = array( + 'name' => $name, + 'channel' => $channel, + 'min' => $min, + 'max' => $max, + ); + if ($exclude) { + $set['exclude'] = $exclude; + } + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array( + 'compatible' => array('dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog') + )); + } + + /** + * Removes the tag entirely + */ + function resetUsesrole() + { + if (isset($this->_packageInfo['usesrole'])) { + unset($this->_packageInfo['usesrole']); + } + } + + /** + * @param string + * @param string package name or uri + * @param string channel name if non-uri + */ + function addUsesrole($role, $packageOrUri, $channel = false) { + $set = array('role' => $role); + if ($channel) { + $set['package'] = $packageOrUri; + $set['channel'] = $channel; + } else { + $set['uri'] = $packageOrUri; + } + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array( + 'usesrole' => array('usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog') + )); + } + + /** + * Removes the tag entirely + */ + function resetUsestask() + { + if (isset($this->_packageInfo['usestask'])) { + unset($this->_packageInfo['usestask']); + } + } + + + /** + * @param string + * @param string package name or uri + * @param string channel name if non-uri + */ + function addUsestask($task, $packageOrUri, $channel = false) { + $set = array('task' => $task); + if ($channel) { + $set['package'] = $packageOrUri; + $set['channel'] = $channel; + } else { + $set['uri'] = $packageOrUri; + } + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array( + 'usestask' => array('srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog') + )); + } + + /** + * Remove all compatible tags + */ + function clearCompatible() + { + unset($this->_packageInfo['compatible']); + } + + /** + * Reset dependencies prior to adding new ones + */ + function clearDeps() + { + if (!isset($this->_packageInfo['dependencies'])) { + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, array(), + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'))); + } + $this->_packageInfo['dependencies'] = array(); + } + + /** + * @param string minimum PHP version allowed + * @param string maximum PHP version allowed + * @param array $exclude incompatible PHP versions + */ + function setPhpDep($min, $max = false, $exclude = false) + { + $this->_isValid = 0; + $dep = + array( + 'min' => $min, + ); + if ($max) { + $dep['max'] = $max; + } + if ($exclude) { + if (count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if (isset($this->_packageInfo['dependencies']['required']['php'])) { + $this->_stack->push(__FUNCTION__, 'warning', array('dep' => + $this->_packageInfo['dependencies']['required']['php']), + 'warning: PHP dependency already exists, overwriting'); + unset($this->_packageInfo['dependencies']['required']['php']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'php' => array('pearinstaller', 'package', 'subpackage', 'extension', 'os', 'arch') + )); + return true; + } + + /** + * @param string minimum allowed PEAR installer version + * @param string maximum allowed PEAR installer version + * @param string recommended PEAR installer version + * @param array incompatible version of the PEAR installer + */ + function setPearinstallerDep($min, $max = false, $recommended = false, $exclude = false) + { + $this->_isValid = 0; + $dep = + array( + 'min' => $min, + ); + if ($max) { + $dep['max'] = $max; + } + if ($recommended) { + $dep['recommended'] = $recommended; + } + if ($exclude) { + if (count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if (isset($this->_packageInfo['dependencies']['required']['pearinstaller'])) { + $this->_stack->push(__FUNCTION__, 'warning', array('dep' => + $this->_packageInfo['dependencies']['required']['pearinstaller']), + 'warning: PEAR Installer dependency already exists, overwriting'); + unset($this->_packageInfo['dependencies']['required']['pearinstaller']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'pearinstaller' => array('package', 'subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * Mark a package as conflicting with this package + * @param string package name + * @param string package channel + * @param string extension this package provides, if any + * @param string|false minimum version required + * @param string|false maximum version allowed + * @param array|false versions to exclude from installation + */ + function addConflictingPackageDepWithChannel($name, $channel, + $providesextension = false, $min = false, $max = false, $exclude = false) + { + $this->_isValid = 0; + $dep = $this->_constructDep($name, $channel, false, $min, $max, false, + $exclude, $providesextension, false, true); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * Mark a package as conflicting with this package + * @param string package name + * @param string package channel + * @param string extension this package provides, if any + */ + function addConflictingPackageDepWithUri($name, $uri, $providesextension = false) + { + $this->_isValid = 0; + $dep = + array( + 'name' => $name, + 'uri' => $uri, + 'conflicts' => '', + ); + if ($providesextension) { + $dep['providesextension'] = $providesextension; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + function addDependencyGroup($name, $hint) + { + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, + array('attribs' => array('name' => $name, 'hint' => $hint)), + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'group' => array(), + )); + } + + /** + * @param string package name + * @param string|false channel name, false if this is a uri + * @param string|false uri name, false if this is a channel + * @param string|false minimum version required + * @param string|false maximum version allowed + * @param string|false recommended installation version + * @param array|false versions to exclude from installation + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @param bool if true, tells the installer to negate this dependency (conflicts) + * @return array + * @access private + */ + function _constructDep($name, $channel, $uri, $min, $max, $recommended, $exclude, + $providesextension = false, $nodefault = false, + $conflicts = false) + { + $dep = + array( + 'name' => $name, + ); + if ($channel) { + $dep['channel'] = $channel; + } elseif ($uri) { + $dep['uri'] = $uri; + } + if ($min) { + $dep['min'] = $min; + } + if ($max) { + $dep['max'] = $max; + } + if ($recommended) { + $dep['recommended'] = $recommended; + } + if ($exclude) { + if (is_array($exclude) && count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if ($conflicts) { + $dep['conflicts'] = ''; + } + if ($nodefault) { + $dep['nodefault'] = ''; + } + if ($providesextension) { + $dep['providesextension'] = $providesextension; + } + return $dep; + } + + /** + * @param package|subpackage + * @param string group name + * @param string package name + * @param string package channel + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array|false optional excluded versions + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @return bool false if the dependency group has not been initialized with + * {@link addDependencyGroup()}, or a subpackage is added with + * a providesextension + */ + function addGroupPackageDepWithChannel($type, $groupname, $name, $channel, $min = false, + $max = false, $recommended = false, $exclude = false, + $providesextension = false, $nodefault = false) + { + if ($type == 'subpackage' && $providesextension) { + return false; // subpackages must be php packages + } + $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude, + $providesextension, $nodefault); + return $this->_addGroupDependency($type, $dep, $groupname); + } + + /** + * @param package|subpackage + * @param string group name + * @param string package name + * @param string package uri + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @return bool false if the dependency group has not been initialized with + * {@link addDependencyGroup()} + */ + function addGroupPackageDepWithURI($type, $groupname, $name, $uri, $providesextension = false, + $nodefault = false) + { + if ($type == 'subpackage' && $providesextension) { + return false; // subpackages must be php packages + } + $dep = $this->_constructDep($name, false, $uri, false, false, false, false, + $providesextension, $nodefault); + return $this->_addGroupDependency($type, $dep, $groupname); + } + + /** + * @param string group name (must be pre-existing) + * @param string extension name + * @param string minimum version allowed + * @param string maximum version allowed + * @param string recommended version + * @param array incompatible versions + */ + function addGroupExtensionDep($groupname, $name, $min = false, $max = false, + $recommended = false, $exclude = false) + { + $this->_isValid = 0; + $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude); + return $this->_addGroupDependency('extension', $dep, $groupname); + } + + /** + * @param package|subpackage|extension + * @param array dependency contents + * @param string name of the dependency group to add this to + * @return boolean + * @access private + */ + function _addGroupDependency($type, $dep, $groupname) + { + $arr = array('subpackage', 'extension'); + if ($type != 'package') { + array_shift($arr); + } + if ($type == 'extension') { + array_shift($arr); + } + if (!isset($this->_packageInfo['dependencies']['group'])) { + return false; + } else { + if (!isset($this->_packageInfo['dependencies']['group'][0])) { + if ($this->_packageInfo['dependencies']['group']['attribs']['name'] == $groupname) { + $this->_packageInfo['dependencies']['group'] = $this->_mergeTag( + $this->_packageInfo['dependencies']['group'], $dep, + array( + $type => $arr + )); + $this->_isValid = 0; + return true; + } else { + return false; + } + } else { + foreach ($this->_packageInfo['dependencies']['group'] as $i => $group) { + if ($group['attribs']['name'] == $groupname) { + $this->_packageInfo['dependencies']['group'][$i] = $this->_mergeTag( + $this->_packageInfo['dependencies']['group'][$i], $dep, + array( + $type => $arr + )); + $this->_isValid = 0; + return true; + } + } + return false; + } + } + } + + /** + * @param optional|required + * @param string package name + * @param string package channel + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @param array|false optional excluded versions + */ + function addPackageDepWithChannel($type, $name, $channel, $min = false, $max = false, + $recommended = false, $exclude = false, + $providesextension = false, $nodefault = false) + { + if (!in_array($type, array('optional', 'required'), true)) { + $type = 'required'; + } + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude, + $providesextension, $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * @param optional|required + * @param string name of the package + * @param string uri of the package + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + */ + function addPackageDepWithUri($type, $name, $uri, $providesextension = false, + $nodefault = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, false, $uri, false, false, false, false, + $providesextension, $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * @param optional|required optional, required + * @param string package name + * @param string package channel + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array incompatible versions + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + */ + function addSubpackageDepWithChannel($type, $name, $channel, $min = false, $max = false, + $recommended = false, $exclude = false, + $nodefault = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude, + $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'subpackage' => array('extension', 'os', 'arch') + )); + } + + /** + * @param optional|required optional, required + * @param string package name + * @param string package uri for download + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + */ + function addSubpackageDepWithUri($type, $name, $uri, $nodefault = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, false, $uri, false, false, false, false, $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'subpackage' => array('extension', 'os', 'arch') + )); + } + + /** + * @param optional|required optional, required + * @param string extension name + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array incompatible versions + */ + function addExtensionDep($type, $name, $min = false, $max = false, $recommended = false, + $exclude = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'extension' => array('os', 'arch') + )); + } + + /** + * @param string Operating system name + * @param boolean true if this package cannot be installed on this OS + */ + function addOsDep($name, $conflicts = false) + { + $this->_isValid = 0; + $dep = array('name' => $name); + if ($conflicts) { + $dep['conflicts'] = ''; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'os' => array('arch') + )); + } + + /** + * @param string Architecture matching pattern + * @param boolean true if this package cannot be installed on this architecture + */ + function addArchDep($pattern, $conflicts = false) + { + $this->_isValid = 0; + $dep = array('pattern' => $pattern); + if ($conflicts) { + $dep['conflicts'] = ''; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'arch' => array() + )); + } + + /** + * Set the kind of package, and erase all release tags + * + * - a php package is a PEAR-style package + * - an extbin package is a PECL-style extension binary + * - an extsrc package is a PECL-style source for a binary + * - an zendextbin package is a PECL-style zend extension binary + * - an zendextsrc package is a PECL-style source for a zend extension binary + * - a bundle package is a collection of other pre-packaged packages + * @param php|extbin|extsrc|zendextsrc|zendextbin|bundle + * @return bool success + */ + function setPackageType($type) + { + $this->_isValid = 0; + if (!in_array($type, array('php', 'extbin', 'extsrc', 'zendextsrc', + 'zendextbin', 'bundle'))) { + return false; + } + + if (in_array($type, array('zendextsrc', 'zendextbin'))) { + $this->_setPackageVersion2_1(); + } + + if ($type != 'bundle') { + $type .= 'release'; + } + + foreach (array('phprelease', 'extbinrelease', 'extsrcrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle') as $test) { + unset($this->_packageInfo[$test]); + } + + if (!isset($this->_packageInfo[$type])) { + // ensure that the release tag is set up + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('changelog'), + array(), $type); + } + + $this->_packageInfo[$type] = array(); + return true; + } + + /** + * @return bool true if package type is set up + */ + function addRelease() + { + if ($type = $this->getPackageType()) { + if ($type != 'bundle') { + $type .= 'release'; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, array(), + array($type => array('changelog'))); + return true; + } + return false; + } + + /** + * Get the current release tag in order to add to it + * @param bool returns only releases that have installcondition if true + * @return array|null + */ + function &_getCurrentRelease($strict = true) + { + if ($p = $this->getPackageType()) { + if ($strict) { + if ($p == 'extsrc' || $p == 'zendextsrc') { + $a = null; + return $a; + } + } + if ($p != 'bundle') { + $p .= 'release'; + } + if (isset($this->_packageInfo[$p][0])) { + return $this->_packageInfo[$p][count($this->_packageInfo[$p]) - 1]; + } else { + return $this->_packageInfo[$p]; + } + } else { + $a = null; + return $a; + } + } + + /** + * Add a file to the current release that should be installed under a different name + * @param string path to file + * @param string name the file should be installed as + */ + function addInstallAs($path, $as) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $r = $this->_mergeTag($r, array('attribs' => array('name' => $path, 'as' => $as)), + array( + 'filelist' => array(), + 'install' => array('ignore') + )); + } + + /** + * Add a file to the current release that should be ignored + * @param string path to file + * @return bool success of operation + */ + function addIgnore($path) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $r = $this->_mergeTag($r, array('attribs' => array('name' => $path)), + array( + 'filelist' => array(), + 'ignore' => array() + )); + } + + /** + * Add an extension binary package for this extension source code release + * + * Note that the package must be from the same channel as the extension source package + * @param string + */ + function addBinarypackage($package) + { + if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') { + return false; + } + $r = &$this->_getCurrentRelease(false); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $r = $this->_mergeTag($r, $package, + array( + 'binarypackage' => array('filelist'), + )); + } + + /** + * Add a configureoption to an extension source package + * @param string + * @param string + * @param string + */ + function addConfigureOption($name, $prompt, $default = null) + { + if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') { + return false; + } + + $r = &$this->_getCurrentRelease(false); + if ($r === null) { + return false; + } + + $opt = array('attribs' => array('name' => $name, 'prompt' => $prompt)); + if ($default !== null) { + $opt['attribs']['default'] = $default; + } + + $this->_isValid = 0; + $r = $this->_mergeTag($r, $opt, + array( + 'configureoption' => array('binarypackage', 'filelist'), + )); + } + + /** + * Set an installation condition based on php version for the current release set + * @param string minimum version + * @param string maximum version + * @param false|array incompatible versions of PHP + */ + function setPhpInstallCondition($min, $max, $exclude = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + if (isset($r['installconditions']['php'])) { + unset($r['installconditions']['php']); + } + $dep = array('min' => $min, 'max' => $max); + if ($exclude) { + if (is_array($exclude) && count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'php' => array('extension', 'os', 'arch') + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'php' => array('extension', 'os', 'arch') + )); + } + } + + /** + * @param optional|required optional, required + * @param string extension name + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array incompatible versions + */ + function addExtensionInstallCondition($name, $min = false, $max = false, $recommended = false, + $exclude = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude); + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'extension' => array('os', 'arch') + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'extension' => array('os', 'arch') + )); + } + } + + /** + * Set an installation condition based on operating system for the current release set + * @param string OS name + * @param bool whether this OS is incompatible with the current release + */ + function setOsInstallCondition($name, $conflicts = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + if (isset($r['installconditions']['os'])) { + unset($r['installconditions']['os']); + } + $dep = array('name' => $name); + if ($conflicts) { + $dep['conflicts'] = ''; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'os' => array('arch') + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'os' => array('arch') + )); + } + } + + /** + * Set an installation condition based on architecture for the current release set + * @param string architecture pattern + * @param bool whether this arch is incompatible with the current release + */ + function setArchInstallCondition($pattern, $conflicts = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + if (isset($r['installconditions']['arch'])) { + unset($r['installconditions']['arch']); + } + $dep = array('pattern' => $pattern); + if ($conflicts) { + $dep['conflicts'] = ''; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'arch' => array() + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'arch' => array() + )); + } + } + + /** + * For extension binary releases, this is used to specify either the + * static URI to a source package, or the package name and channel of the extsrc/zendextsrc + * package it is based on. + * @param string Package name, or full URI to source package (extsrc/zendextsrc type) + */ + function setSourcePackage($packageOrUri) + { + $this->_isValid = 0; + if (isset($this->_packageInfo['channel'])) { + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), + $packageOrUri, 'srcpackage'); + } else { + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), $packageOrUri, 'srcuri'); + } + } + + /** + * Generate a valid change log entry from the current package.xml + * @param string|false + */ + function generateChangeLogEntry($notes = false) + { + return array( + 'version' => + array( + 'release' => $this->getVersion('release'), + 'api' => $this->getVersion('api'), + ), + 'stability' => + $this->getStability(), + 'date' => $this->getDate(), + 'license' => $this->getLicense(true), + 'notes' => $notes ? $notes : $this->getNotes() + ); + } + + /** + * @param string release version to set change log notes for + * @param array output of {@link generateChangeLogEntry()} + */ + function setChangelogEntry($releaseversion, $contents) + { + if (!isset($this->_packageInfo['changelog'])) { + $this->_packageInfo['changelog']['release'] = $contents; + return; + } + if (!isset($this->_packageInfo['changelog']['release'][0])) { + if ($this->_packageInfo['changelog']['release']['version']['release'] == $releaseversion) { + $this->_packageInfo['changelog']['release'] = array( + $this->_packageInfo['changelog']['release']); + } else { + $this->_packageInfo['changelog']['release'] = array( + $this->_packageInfo['changelog']['release']); + return $this->_packageInfo['changelog']['release'][] = $contents; + } + } + foreach($this->_packageInfo['changelog']['release'] as $index => $changelog) { + if (isset($changelog['version']) && + strnatcasecmp($changelog['version']['release'], $releaseversion) == 0) { + $curlog = $index; + } + } + if (isset($curlog)) { + $this->_packageInfo['changelog']['release'][$curlog] = $contents; + } else { + $this->_packageInfo['changelog']['release'][] = $contents; + } + } + + /** + * Remove the changelog entirely + */ + function clearChangeLog() + { + unset($this->_packageInfo['changelog']); + } +}PEAR-1.9.0/PEAR/PackageFile/v2/Validator.php100664 764 764 250041 100664 13217 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Validator.php 277885 2009-03-27 19:29:31Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a8 + */ +/** + * Private validation class used by PEAR_PackageFile_v2 - do not use directly, its + * sole purpose is to split up the PEAR/PackageFile/v2.php file to make it smaller + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a8 + * @access private + */ +class PEAR_PackageFile_v2_Validator +{ + /** + * @var array + */ + var $_packageInfo; + /** + * @var PEAR_PackageFile_v2 + */ + var $_pf; + /** + * @var PEAR_ErrorStack + */ + var $_stack; + /** + * @var int + */ + var $_isValid = 0; + /** + * @var int + */ + var $_filesValid = 0; + /** + * @var int + */ + var $_curState = 0; + /** + * @param PEAR_PackageFile_v2 + * @param int + */ + function validate(&$pf, $state = PEAR_VALIDATE_NORMAL) + { + $this->_pf = &$pf; + $this->_curState = $state; + $this->_packageInfo = $this->_pf->getArray(); + $this->_isValid = $this->_pf->_isValid; + $this->_filesValid = $this->_pf->_filesValid; + $this->_stack = &$pf->_stack; + $this->_stack->getErrors(true); + if (($this->_isValid & $state) == $state) { + return true; + } + if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) { + return false; + } + if (!isset($this->_packageInfo['attribs']['version']) || + ($this->_packageInfo['attribs']['version'] != '2.0' && + $this->_packageInfo['attribs']['version'] != '2.1') + ) { + $this->_noPackageVersion(); + } + $structure = + array( + 'name', + 'channel|uri', + '*extends', // can't be multiple, but this works fine + 'summary', + 'description', + '+lead', // these all need content checks + '*developer', + '*contributor', + '*helper', + 'date', + '*time', + 'version', + 'stability', + 'license->?uri->?filesource', + 'notes', + 'contents', //special validation needed + '*compatible', + 'dependencies', //special validation needed + '*usesrole', + '*usestask', // reserve these for 1.4.0a1 to implement + // this will allow a package.xml to gracefully say it + // needs a certain package installed in order to implement a role or task + '*providesextension', + '*srcpackage|*srcuri', + '+phprelease|+extsrcrelease|+extbinrelease|' . + '+zendextsrcrelease|+zendextbinrelease|bundle', //special validation needed + '*changelog', + ); + $test = $this->_packageInfo; + if (isset($test['dependencies']) && + isset($test['dependencies']['required']) && + isset($test['dependencies']['required']['pearinstaller']) && + isset($test['dependencies']['required']['pearinstaller']['min']) && + version_compare('1.9.0', + $test['dependencies']['required']['pearinstaller']['min'], '<') + ) { + $this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']); + return false; + } + // ignore post-installation array fields + if (array_key_exists('filelist', $test)) { + unset($test['filelist']); + } + if (array_key_exists('_lastmodified', $test)) { + unset($test['_lastmodified']); + } + if (array_key_exists('#binarypackage', $test)) { + unset($test['#binarypackage']); + } + if (array_key_exists('old', $test)) { + unset($test['old']); + } + if (array_key_exists('_lastversion', $test)) { + unset($test['_lastversion']); + } + if (!$this->_stupidSchemaValidate($structure, $test, '')) { + return false; + } + if (empty($this->_packageInfo['name'])) { + $this->_tagCannotBeEmpty('name'); + } + $test = isset($this->_packageInfo['uri']) ? 'uri' :'channel'; + if (empty($this->_packageInfo[$test])) { + $this->_tagCannotBeEmpty($test); + } + if (is_array($this->_packageInfo['license']) && + (!isset($this->_packageInfo['license']['_content']) || + empty($this->_packageInfo['license']['_content']))) { + $this->_tagCannotBeEmpty('license'); + } elseif (empty($this->_packageInfo['license'])) { + $this->_tagCannotBeEmpty('license'); + } + if (empty($this->_packageInfo['summary'])) { + $this->_tagCannotBeEmpty('summary'); + } + if (empty($this->_packageInfo['description'])) { + $this->_tagCannotBeEmpty('description'); + } + if (empty($this->_packageInfo['date'])) { + $this->_tagCannotBeEmpty('date'); + } + if (empty($this->_packageInfo['notes'])) { + $this->_tagCannotBeEmpty('notes'); + } + if (isset($this->_packageInfo['time']) && empty($this->_packageInfo['time'])) { + $this->_tagCannotBeEmpty('time'); + } + if (isset($this->_packageInfo['dependencies'])) { + $this->_validateDependencies(); + } + if (isset($this->_packageInfo['compatible'])) { + $this->_validateCompatible(); + } + if (!isset($this->_packageInfo['bundle'])) { + if (empty($this->_packageInfo['contents'])) { + $this->_tagCannotBeEmpty('contents'); + } + if (!isset($this->_packageInfo['contents']['dir'])) { + $this->_filelistMustContainDir('contents'); + return false; + } + if (isset($this->_packageInfo['contents']['file'])) { + $this->_filelistCannotContainFile('contents'); + return false; + } + } + $this->_validateMaintainers(); + $this->_validateStabilityVersion(); + $fail = false; + if (array_key_exists('usesrole', $this->_packageInfo)) { + $roles = $this->_packageInfo['usesrole']; + if (!is_array($roles) || !isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if (!isset($role['role'])) { + $this->_usesroletaskMustHaveRoleTask('usesrole', 'role'); + $fail = true; + } else { + if (!isset($role['channel'])) { + if (!isset($role['uri'])) { + $this->_usesroletaskMustHaveChannelOrUri($role['role'], 'usesrole'); + $fail = true; + } + } elseif (!isset($role['package'])) { + $this->_usesroletaskMustHavePackage($role['role'], 'usesrole'); + $fail = true; + } + } + } + } + if (array_key_exists('usestask', $this->_packageInfo)) { + $roles = $this->_packageInfo['usestask']; + if (!is_array($roles) || !isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if (!isset($role['task'])) { + $this->_usesroletaskMustHaveRoleTask('usestask', 'task'); + $fail = true; + } else { + if (!isset($role['channel'])) { + if (!isset($role['uri'])) { + $this->_usesroletaskMustHaveChannelOrUri($role['task'], 'usestask'); + $fail = true; + } + } elseif (!isset($role['package'])) { + $this->_usesroletaskMustHavePackage($role['task'], 'usestask'); + $fail = true; + } + } + } + } + + if ($fail) { + return false; + } + + $list = $this->_packageInfo['contents']; + if (isset($list['dir']) && is_array($list['dir']) && isset($list['dir'][0])) { + $this->_multipleToplevelDirNotAllowed(); + return $this->_isValid = 0; + } + + $this->_validateFilelist(); + $this->_validateRelease(); + if (!$this->_stack->hasErrors()) { + $chan = $this->_pf->_registry->getChannel($this->_pf->getChannel(), true); + if (PEAR::isError($chan)) { + $this->_unknownChannel($this->_pf->getChannel()); + } else { + $valpack = $chan->getValidationPackage(); + // for channel validator packages, always use the default PEAR validator. + // otherwise, they can't be installed or packaged + $validator = $chan->getValidationObject($this->_pf->getPackage()); + if (!$validator) { + $this->_stack->push(__FUNCTION__, 'error', + array_merge( + array('channel' => $chan->getName(), + 'package' => $this->_pf->getPackage()), + $valpack + ), + 'package "%channel%/%package%" cannot be properly validated without ' . + 'validation package "%channel%/%name%-%version%"'); + return $this->_isValid = 0; + } + $validator->setPackageFile($this->_pf); + $validator->validate($state); + $failures = $validator->getFailures(); + foreach ($failures['errors'] as $error) { + $this->_stack->push(__FUNCTION__, 'error', $error, + 'Channel validator error: field "%field%" - %reason%'); + } + foreach ($failures['warnings'] as $warning) { + $this->_stack->push(__FUNCTION__, 'warning', $warning, + 'Channel validator warning: field "%field%" - %reason%'); + } + } + } + + $this->_pf->_isValid = $this->_isValid = !$this->_stack->hasErrors('error'); + if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$this->_filesValid) { + if ($this->_pf->getPackageType() == 'bundle') { + if ($this->_analyzeBundledPackages()) { + $this->_filesValid = $this->_pf->_filesValid = true; + } else { + $this->_pf->_isValid = $this->_isValid = 0; + } + } else { + if (!$this->_analyzePhpFiles()) { + $this->_pf->_isValid = $this->_isValid = 0; + } else { + $this->_filesValid = $this->_pf->_filesValid = true; + } + } + } + + if ($this->_isValid) { + return $this->_pf->_isValid = $this->_isValid = $state; + } + + return $this->_pf->_isValid = $this->_isValid = 0; + } + + function _stupidSchemaValidate($structure, $xml, $root) + { + if (!is_array($xml)) { + $xml = array(); + } + $keys = array_keys($xml); + reset($keys); + $key = current($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + $unfoundtags = $optionaltags = array(); + $ret = true; + $mismatch = false; + foreach ($structure as $struc) { + if ($key) { + $tag = $xml[$key]; + } + $test = $this->_processStructure($struc); + if (isset($test['choices'])) { + $loose = true; + foreach ($test['choices'] as $choice) { + if ($key == $choice['tag']) { + $key = next($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + $unfoundtags = $optionaltags = array(); + $mismatch = false; + if ($key && $key != $choice['tag'] && isset($choice['multiple'])) { + $unfoundtags[] = $choice['tag']; + $optionaltags[] = $choice['tag']; + if ($key) { + $mismatch = true; + } + } + $ret &= $this->_processAttribs($choice, $tag, $root); + continue 2; + } else { + $unfoundtags[] = $choice['tag']; + $mismatch = true; + } + if (!isset($choice['multiple']) || $choice['multiple'] != '*') { + $loose = false; + } else { + $optionaltags[] = $choice['tag']; + } + } + if (!$loose) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } + } else { + if ($key != $test['tag']) { + if (isset($test['multiple']) && $test['multiple'] != '*') { + $unfoundtags[] = $test['tag']; + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } else { + if ($key) { + $mismatch = true; + } + $unfoundtags[] = $test['tag']; + $optionaltags[] = $test['tag']; + } + if (!isset($test['multiple'])) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } + continue; + } else { + $unfoundtags = $optionaltags = array(); + $mismatch = false; + } + $key = next($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + if ($key && $key != $test['tag'] && isset($test['multiple'])) { + $unfoundtags[] = $test['tag']; + $optionaltags[] = $test['tag']; + $mismatch = true; + } + $ret &= $this->_processAttribs($test, $tag, $root); + continue; + } + } + if (!$mismatch && count($optionaltags)) { + // don't error out on any optional tags + $unfoundtags = array_diff($unfoundtags, $optionaltags); + } + if (count($unfoundtags)) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + } elseif ($key) { + // unknown tags + $this->_invalidTagOrder('*no tags allowed here*', $key, $root); + while ($key = next($keys)) { + $this->_invalidTagOrder('*no tags allowed here*', $key, $root); + } + } + return $ret; + } + + function _processAttribs($choice, $tag, $context) + { + if (isset($choice['attribs'])) { + if (!is_array($tag)) { + $tag = array($tag); + } + $tags = $tag; + if (!isset($tags[0])) { + $tags = array($tags); + } + $ret = true; + foreach ($tags as $i => $tag) { + if (!is_array($tag) || !isset($tag['attribs'])) { + foreach ($choice['attribs'] as $attrib) { + if ($attrib{0} != '?') { + $ret &= $this->_tagHasNoAttribs($choice['tag'], + $context); + continue 2; + } + } + } + foreach ($choice['attribs'] as $attrib) { + if ($attrib{0} != '?') { + if (!isset($tag['attribs'][$attrib])) { + $ret &= $this->_tagMissingAttribute($choice['tag'], + $attrib, $context); + } + } + } + } + return $ret; + } + return true; + } + + function _processStructure($key) + { + $ret = array(); + if (count($pieces = explode('|', $key)) > 1) { + $ret['choices'] = array(); + foreach ($pieces as $piece) { + $ret['choices'][] = $this->_processStructure($piece); + } + return $ret; + } + $multi = $key{0}; + if ($multi == '+' || $multi == '*') { + $ret['multiple'] = $key{0}; + $key = substr($key, 1); + } + if (count($attrs = explode('->', $key)) > 1) { + $ret['tag'] = array_shift($attrs); + $ret['attribs'] = $attrs; + } else { + $ret['tag'] = $key; + } + return $ret; + } + + function _validateStabilityVersion() + { + $structure = array('release', 'api'); + $a = $this->_stupidSchemaValidate($structure, $this->_packageInfo['version'], ''); + $a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], ''); + if ($a) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $this->_packageInfo['version']['release'])) { + $this->_invalidVersion('release', $this->_packageInfo['version']['release']); + } + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $this->_packageInfo['version']['api'])) { + $this->_invalidVersion('api', $this->_packageInfo['version']['api']); + } + if (!in_array($this->_packageInfo['stability']['release'], + array('snapshot', 'devel', 'alpha', 'beta', 'stable'))) { + $this->_invalidState('release', $this->_packageInfo['stability']['release']); + } + if (!in_array($this->_packageInfo['stability']['api'], + array('devel', 'alpha', 'beta', 'stable'))) { + $this->_invalidState('api', $this->_packageInfo['stability']['api']); + } + } + } + + function _validateMaintainers() + { + $structure = + array( + 'name', + 'user', + 'email', + 'active', + ); + foreach (array('lead', 'developer', 'contributor', 'helper') as $type) { + if (!isset($this->_packageInfo[$type])) { + continue; + } + if (isset($this->_packageInfo[$type][0])) { + foreach ($this->_packageInfo[$type] as $lead) { + $this->_stupidSchemaValidate($structure, $lead, '<' . $type . '>'); + } + } else { + $this->_stupidSchemaValidate($structure, $this->_packageInfo[$type], + '<' . $type . '>'); + } + } + } + + function _validatePhpDep($dep, $installcondition = false) + { + $structure = array( + 'min', + '*max', + '*exclude', + ); + $type = $installcondition ? '' : ''; + $this->_stupidSchemaValidate($structure, $dep, $type); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $dep['min'])) { + $this->_invalidVersion($type . '', $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $dep['max'])) { + $this->_invalidVersion($type . '', $dep['max']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match( + '/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $exclude)) { + $this->_invalidVersion($type . '', $exclude); + } + } + } + } + + function _validatePearinstallerDep($dep) + { + $structure = array( + 'min', + '*max', + '*recommended', + '*exclude', + ); + $this->_stupidSchemaValidate($structure, $dep, ''); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion('', + $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['max'])) { + $this->_invalidVersion('', + $dep['max']); + } + } + if (isset($dep['recommended'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['recommended'])) { + $this->_invalidVersion('', + $dep['recommended']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $exclude)) { + $this->_invalidVersion('', + $exclude); + } + } + } + } + + function _validatePackageDep($dep, $group, $type = '') + { + if (isset($dep['uri'])) { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + 'uri', + 'conflicts', + '*providesextension', + ); + } else { + $structure = array( + 'name', + 'uri', + '*providesextension', + ); + } + } else { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + 'channel', + '*min', + '*max', + '*exclude', + 'conflicts', + '*providesextension', + ); + } else { + $structure = array( + 'name', + 'channel', + '*min', + '*max', + '*recommended', + '*exclude', + '*nodefault', + '*providesextension', + ); + } + } + if (isset($dep['name'])) { + $type .= '' . $dep['name'] . ''; + } + $this->_stupidSchemaValidate($structure, $dep, '' . $group . $type); + if (isset($dep['uri']) && (isset($dep['min']) || isset($dep['max']) || + isset($dep['recommended']) || isset($dep['exclude']))) { + $this->_uriDepsCannotHaveVersioning('' . $group . $type); + } + if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') { + $this->_DepchannelCannotBeUri('' . $group . $type); + } + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion('' . $group . $type . '', $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['max'])) { + $this->_invalidVersion('' . $group . $type . '', $dep['max']); + } + } + if (isset($dep['recommended'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['recommended'])) { + $this->_invalidVersion('' . $group . $type . '', + $dep['recommended']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $exclude)) { + $this->_invalidVersion('' . $group . $type . '', + $exclude); + } + } + } + } + + function _validateSubpackageDep($dep, $group) + { + $this->_validatePackageDep($dep, $group, ''); + if (isset($dep['providesextension'])) { + $this->_subpackageCannotProvideExtension(isset($dep['name']) ? $dep['name'] : ''); + } + if (isset($dep['conflicts'])) { + $this->_subpackagesCannotConflict(isset($dep['name']) ? $dep['name'] : ''); + } + } + + function _validateExtensionDep($dep, $group = false, $installcondition = false) + { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + '*min', + '*max', + '*exclude', + 'conflicts', + ); + } else { + $structure = array( + 'name', + '*min', + '*max', + '*recommended', + '*exclude', + ); + } + if ($installcondition) { + $type = ''; + } else { + $type = '' . $group . ''; + } + if (isset($dep['name'])) { + $type .= '' . $dep['name'] . ''; + } + $this->_stupidSchemaValidate($structure, $dep, $type); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '' : ''; + if ($this->_stupidSchemaValidate($structure, $dep, $type)) { + if ($dep['name'] == '*') { + if (array_key_exists('conflicts', $dep)) { + $this->_cannotConflictWithAllOs($type); + } + } + } + } + + function _validateArchDep($dep, $installcondition = false) + { + $structure = array( + 'pattern', + '*conflicts', + ); + $type = $installcondition ? '' : ''; + $this->_stupidSchemaValidate($structure, $dep, $type); + } + + function _validateInstallConditions($cond, $release) + { + $structure = array( + '*php', + '*extension', + '*os', + '*arch', + ); + if (!$this->_stupidSchemaValidate($structure, + $cond, $release)) { + return false; + } + foreach (array('php', 'extension', 'os', 'arch') as $type) { + if (isset($cond[$type])) { + $iter = $cond[$type]; + if (!is_array($iter) || !isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type == 'extension') { + $this->{"_validate{$type}Dep"}($package, false, true); + } else { + $this->{"_validate{$type}Dep"}($package, true); + } + } + } + } + } + + function _validateDependencies() + { + $structure = array( + 'required', + '*optional', + '*group->name->hint' + ); + if (!$this->_stupidSchemaValidate($structure, + $this->_packageInfo['dependencies'], '')) { + return false; + } + foreach (array('required', 'optional') as $simpledep) { + if (isset($this->_packageInfo['dependencies'][$simpledep])) { + if ($simpledep == 'optional') { + $structure = array( + '*package', + '*subpackage', + '*extension', + ); + } else { + $structure = array( + 'php', + 'pearinstaller', + '*package', + '*subpackage', + '*extension', + '*os', + '*arch', + ); + } + if ($this->_stupidSchemaValidate($structure, + $this->_packageInfo['dependencies'][$simpledep], + "<$simpledep>")) { + foreach (array('package', 'subpackage', 'extension') as $type) { + if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) { + $iter = $this->_packageInfo['dependencies'][$simpledep][$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type != 'extension') { + if (isset($package['uri'])) { + if (isset($package['channel'])) { + $this->_UrlOrChannel($type, + $package['name']); + } + } else { + if (!isset($package['channel'])) { + $this->_NoChannel($type, $package['name']); + } + } + } + $this->{"_validate{$type}Dep"}($package, "<$simpledep>"); + } + } + } + if ($simpledep == 'optional') { + continue; + } + foreach (array('php', 'pearinstaller', 'os', 'arch') as $type) { + if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) { + $iter = $this->_packageInfo['dependencies'][$simpledep][$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + $this->{"_validate{$type}Dep"}($package); + } + } + } + } + } + } + if (isset($this->_packageInfo['dependencies']['group'])) { + $groups = $this->_packageInfo['dependencies']['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + $structure = array( + '*package', + '*subpackage', + '*extension', + ); + foreach ($groups as $group) { + if ($this->_stupidSchemaValidate($structure, $group, '')) { + if (!PEAR_Validate::validGroupName($group['attribs']['name'])) { + $this->_invalidDepGroupName($group['attribs']['name']); + } + foreach (array('package', 'subpackage', 'extension') as $type) { + if (isset($group[$type])) { + $iter = $group[$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type != 'extension') { + if (isset($package['uri'])) { + if (isset($package['channel'])) { + $this->_UrlOrChannelGroup($type, + $package['name'], + $group['name']); + } + } else { + if (!isset($package['channel'])) { + $this->_NoChannelGroup($type, + $package['name'], + $group['name']); + } + } + } + $this->{"_validate{$type}Dep"}($package, ''); + } + } + } + } + } + } + } + + function _validateCompatible() + { + $compat = $this->_packageInfo['compatible']; + if (!isset($compat[0])) { + $compat = array($compat); + } + $required = array('name', 'channel', 'min', 'max', '*exclude'); + foreach ($compat as $package) { + $type = ''; + if (is_array($package) && array_key_exists('name', $package)) { + $type .= '' . $package['name'] . ''; + } + $this->_stupidSchemaValidate($required, $package, $type); + if (is_array($package) && array_key_exists('min', $package)) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $package['min'])) { + $this->_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_NoBundledPackages(); + } + if (!is_array($list['bundledpackage']) || !isset($list['bundledpackage'][0])) { + return $this->_AtLeast2BundledPackages(); + } + foreach ($list['bundledpackage'] as $package) { + if (!is_string($package)) { + $this->_bundledPackagesMustBeFilename(); + } + } + } + + function _validateFilelist($list = false, $allowignore = false, $dirs = '') + { + $iscontents = false; + if (!$list) { + $iscontents = true; + $list = $this->_packageInfo['contents']; + if (isset($this->_packageInfo['bundle'])) { + return $this->_validateBundle($list); + } + } + if ($allowignore) { + $struc = array( + '*install->name->as', + '*ignore->name' + ); + } else { + $struc = array( + '*dir->name->?baseinstalldir', + '*file->name->role->?baseinstalldir->?md5sum' + ); + if (isset($list['dir']) && isset($list['file'])) { + // stave off validation errors without requiring a set order. + $_old = $list; + if (isset($list['attribs'])) { + $list = array('attribs' => $_old['attribs']); + } + $list['dir'] = $_old['dir']; + $list['file'] = $_old['file']; + } + } + if (!isset($list['attribs']) || !isset($list['attribs']['name'])) { + $unknown = $allowignore ? '' : ''; + $dirname = $iscontents ? '' : $unknown; + } else { + $dirname = ''; + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $list['attribs']['name']))) { + // file contains .. parent directory or . cur directory + $this->_invalidDirName($list['attribs']['name']); + } + } + $res = $this->_stupidSchemaValidate($struc, $list, $dirname); + if ($allowignore && $res) { + $ignored_or_installed = array(); + $this->_pf->getFilelist(); + $fcontents = $this->_pf->getContents(); + $filelist = array(); + if (!isset($fcontents['dir']['file'][0])) { + $fcontents['dir']['file'] = array($fcontents['dir']['file']); + } + foreach ($fcontents['dir']['file'] as $file) { + $filelist[$file['attribs']['name']] = true; + } + if (isset($list['install'])) { + if (!isset($list['install'][0])) { + $list['install'] = array($list['install']); + } + foreach ($list['install'] as $file) { + if (!isset($filelist[$file['attribs']['name']])) { + $this->_notInContents($file['attribs']['name'], 'install'); + continue; + } + if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) { + $this->_multipleInstallAs($file['attribs']['name']); + } + if (!isset($ignored_or_installed[$file['attribs']['name']])) { + $ignored_or_installed[$file['attribs']['name']] = array(); + } + $ignored_or_installed[$file['attribs']['name']][] = 1; + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $file['attribs']['as']))) { + // file contains .. parent directory or . cur directory references + $this->_invalidFileInstallAs($file['attribs']['name'], + $file['attribs']['as']); + } + } + } + if (isset($list['ignore'])) { + if (!isset($list['ignore'][0])) { + $list['ignore'] = array($list['ignore']); + } + foreach ($list['ignore'] as $file) { + if (!isset($filelist[$file['attribs']['name']])) { + $this->_notInContents($file['attribs']['name'], 'ignore'); + continue; + } + if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) { + $this->_ignoreAndInstallAs($file['attribs']['name']); + } + } + } + } + if (!$allowignore && isset($list['file'])) { + if (is_string($list['file'])) { + $this->_oldStyleFileNotAllowed(); + return false; + } + if (!isset($list['file'][0])) { + // single file + $list['file'] = array($list['file']); + } + foreach ($list['file'] as $i => $file) + { + if (isset($file['attribs']) && isset($file['attribs']['name'])) { + if ($file['attribs']['name']{0} == '.' && + $file['attribs']['name']{1} == '/') { + // name is something like "./doc/whatever.txt" + $this->_invalidFileName($file['attribs']['name'], $dirname); + } + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $file['attribs']['name']))) { + // file contains .. parent directory or . cur directory + $this->_invalidFileName($file['attribs']['name'], $dirname); + } + } + if (isset($file['attribs']) && isset($file['attribs']['role'])) { + if (!$this->_validateRole($file['attribs']['role'])) { + if (isset($this->_packageInfo['usesrole'])) { + $roles = $this->_packageInfo['usesrole']; + if (!isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if ($role['role'] = $file['attribs']['role']) { + $msg = 'This package contains role "%role%" and requires ' . + 'package "%package%" to be used'; + if (isset($role['uri'])) { + $params = array('role' => $role['role'], + 'package' => $role['uri']); + } else { + $params = array('role' => $role['role'], + 'package' => $this->_pf->_registry-> + parsedPackageNameToString(array('package' => + $role['package'], 'channel' => $role['channel']), + true)); + } + $this->_stack->push('_mustInstallRole', 'error', $params, $msg); + } + } + } + $this->_invalidFileRole($file['attribs']['name'], + $dirname, $file['attribs']['role']); + } + } + if (!isset($file['attribs'])) { + continue; + } + $save = $file['attribs']; + if ($dirs) { + $save['name'] = $dirs . '/' . $save['name']; + } + unset($file['attribs']); + if (count($file) && $this->_curState != PEAR_VALIDATE_DOWNLOADING) { // has tasks + foreach ($file as $task => $value) { + if ($tagClass = $this->_pf->getTask($task)) { + if (!is_array($value) || !isset($value[0])) { + $value = array($value); + } + foreach ($value as $v) { + $ret = call_user_func(array($tagClass, 'validateXml'), + $this->_pf, $v, $this->_pf->_config, $save); + if (is_array($ret)) { + $this->_invalidTask($task, $ret, isset($save['name']) ? + $save['name'] : ''); + } + } + } else { + if (isset($this->_packageInfo['usestask'])) { + $roles = $this->_packageInfo['usestask']; + if (!isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if ($role['task'] = $task) { + $msg = 'This package contains task "%task%" and requires ' . + 'package "%package%" to be used'; + if (isset($role['uri'])) { + $params = array('task' => $role['task'], + 'package' => $role['uri']); + } else { + $params = array('task' => $role['task'], + 'package' => $this->_pf->_registry-> + parsedPackageNameToString(array('package' => + $role['package'], 'channel' => $role['channel']), + true)); + } + $this->_stack->push('_mustInstallTask', 'error', + $params, $msg); + } + } + } + $this->_unknownTask($task, $save['name']); + } + } + } + } + } + if (isset($list['ignore'])) { + if (!$allowignore) { + $this->_ignoreNotAllowed('ignore'); + } + } + if (isset($list['install'])) { + if (!$allowignore) { + $this->_ignoreNotAllowed('install'); + } + } + if (isset($list['file'])) { + if ($allowignore) { + $this->_fileNotAllowed('file'); + } + } + if (isset($list['dir'])) { + if ($allowignore) { + $this->_fileNotAllowed('dir'); + } else { + if (!isset($list['dir'][0])) { + $list['dir'] = array($list['dir']); + } + foreach ($list['dir'] as $dir) { + if (isset($dir['attribs']) && isset($dir['attribs']['name'])) { + if ($dir['attribs']['name'] == '/' || + !isset($this->_packageInfo['contents']['dir']['dir'])) { + // always use nothing if the filelist has already been flattened + $newdirs = ''; + } elseif ($dirs == '') { + $newdirs = $dir['attribs']['name']; + } else { + $newdirs = $dirs . '/' . $dir['attribs']['name']; + } + } else { + $newdirs = $dirs; + } + $this->_validateFilelist($dir, $allowignore, $newdirs); + } + } + } + } + + function _validateRelease() + { + if (isset($this->_packageInfo['phprelease'])) { + $release = 'phprelease'; + if (isset($this->_packageInfo['providesextension'])) { + $this->_cannotProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo['phprelease']; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, ''); + } + } + foreach (array('', 'zend') as $prefix) { + $releasetype = $prefix . 'extsrcrelease'; + if (isset($this->_packageInfo[$releasetype])) { + $release = $releasetype; + if (!isset($this->_packageInfo['providesextension'])) { + $this->_mustProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo[$releasetype]; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*configureoption->name->prompt->?default', + '*binarypackage', + '*filelist', + ), $rel, '<' . $releasetype . '>'); + if (isset($rel['binarypackage'])) { + if (!is_array($rel['binarypackage']) || !isset($rel['binarypackage'][0])) { + $rel['binarypackage'] = array($rel['binarypackage']); + } + foreach ($rel['binarypackage'] as $bin) { + if (!is_string($bin)) { + $this->_binaryPackageMustBePackagename(); + } + } + } + } + } + $releasetype = 'extbinrelease'; + if (isset($this->_packageInfo[$releasetype])) { + $release = $releasetype; + if (!isset($this->_packageInfo['providesextension'])) { + $this->_mustProvideExtension($release); + } + if (isset($this->_packageInfo['channel']) && + !isset($this->_packageInfo['srcpackage'])) { + $this->_mustSrcPackage($release); + } + if (isset($this->_packageInfo['uri']) && !isset($this->_packageInfo['srcuri'])) { + $this->_mustSrcuri($release); + } + $releases = $this->_packageInfo[$releasetype]; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, '<' . $releasetype . '>'); + } + } + } + if (isset($this->_packageInfo['bundle'])) { + $release = 'bundle'; + if (isset($this->_packageInfo['providesextension'])) { + $this->_cannotProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo['bundle']; + if (!is_array($releases) || !isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, ''); + } + } + foreach ($releases as $rel) { + if (is_array($rel) && array_key_exists('installconditions', $rel)) { + $this->_validateInstallConditions($rel['installconditions'], + "<$release>"); + } + if (is_array($rel) && array_key_exists('filelist', $rel)) { + if ($rel['filelist']) { + + $this->_validateFilelist($rel['filelist'], true); + } + } + } + } + + /** + * This is here to allow role extension through plugins + * @param string + */ + function _validateRole($role) + { + return in_array($role, PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())); + } + + function _pearVersionTooLow($version) + { + $this->_stack->push(__FUNCTION__, 'error', + array('version' => $version), + 'This package.xml requires PEAR version %version% to parse properly, we are ' . + 'version 1.9.0'); + } + + function _invalidTagOrder($oktags, $actual, $root) + { + $this->_stack->push(__FUNCTION__, 'error', + array('oktags' => $oktags, 'actual' => $actual, 'root' => $root), + 'Invalid tag order in %root%, found <%actual%> expected one of "%oktags%"'); + } + + function _ignoreNotAllowed($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '<%type%> is not allowed inside global , only inside ' . + '//, use and only'); + } + + function _fileNotAllowed($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '<%type%> is not allowed inside release , only inside ' . + ', use and only'); + } + + function _oldStyleFileNotAllowed() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Old-style name is not allowed. Use' . + ''); + } + + function _tagMissingAttribute($tag, $attr, $context) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, + 'attribute' => $attr, 'context' => $context), + 'tag <%tag%> in context "%context%" has no attribute "%attribute%"'); + } + + function _tagHasNoAttribs($tag, $context) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, + 'context' => $context), + 'tag <%tag%> has no attributes in context "%context%"'); + } + + function _invalidInternalStructure() + { + $this->_stack->push(__FUNCTION__, 'exception', array(), + 'internal array was not generated by compatible parser, or extreme parser error, cannot continue'); + } + + function _invalidFileRole($file, $dir, $role) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file, 'dir' => $dir, 'role' => $role, + 'roles' => PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())), + 'File "%file%" in directory "%dir%" has invalid role "%role%", should be one of %roles%'); + } + + function _invalidFileName($file, $dir) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file), + 'File "%file%" in directory "%dir%" cannot begin with "./" or contain ".."'); + } + + function _invalidFileInstallAs($file, $as) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file, 'as' => $as), + 'File "%file%" cannot contain "./" or contain ".."'); + } + + function _invalidDirName($dir) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'dir' => $file), + 'Directory "%dir%" cannot begin with "./" or contain ".."'); + } + + function _filelistCannotContainFile($filelist) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist), + '<%tag%> can only contain , contains . Use ' . + ' as the first dir element'); + } + + function _filelistMustContainDir($filelist) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist), + '<%tag%> must contain . Use as the ' . + 'first dir element'); + } + + function _tagCannotBeEmpty($tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag), + '<%tag%> cannot be empty (<%tag%/>)'); + } + + function _UrlOrChannel($type, $name) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name), + 'Required dependency <%type%> "%name%" can have either url OR ' . + 'channel attributes, and not both'); + } + + function _NoChannel($type, $name) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name), + 'Required dependency <%type%> "%name%" must have either url OR ' . + 'channel attributes'); + } + + function _UrlOrChannelGroup($type, $name, $group) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name, 'group' => $group), + 'Group "%group%" dependency <%type%> "%name%" can have either url OR ' . + 'channel attributes, and not both'); + } + + function _NoChannelGroup($type, $name, $group) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name, 'group' => $group), + 'Group "%group%" dependency <%type%> "%name%" must have either url OR ' . + 'channel attributes'); + } + + function _unknownChannel($channel) + { + $this->_stack->push(__FUNCTION__, 'error', array('channel' => $channel), + 'Unknown channel "%channel%"'); + } + + function _noPackageVersion() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'package.xml tag has no version attribute, or version is not 2.0'); + } + + function _NoBundledPackages() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'No tag was found in , required for bundle packages'); + } + + function _AtLeast2BundledPackages() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'At least 2 packages must be bundled in a bundle package'); + } + + function _ChannelOrUri($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Bundled package "%name%" can have either a uri or a channel, not both'); + } + + function _noChildTag($child, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('child' => $child, 'tag' => $tag), + 'Tag <%tag%> is missing child tag <%child%>'); + } + + function _invalidVersion($type, $value) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value), + 'Version type <%type%> is not a valid version (%value%)'); + } + + function _invalidState($type, $value) + { + $states = array('stable', 'beta', 'alpha', 'devel'); + if ($type != 'api') { + $states[] = 'snapshot'; + } + if (strtolower($value) == 'rc') { + $this->_stack->push(__FUNCTION__, 'error', + array('version' => $this->_packageInfo['version']['release']), + 'RC is not a state, it is a version postfix, try %version%RC1, stability beta'); + } + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value, + 'types' => $states), + 'Stability type <%type%> is not a valid stability (%value%), must be one of ' . + '%types%'); + } + + function _invalidTask($task, $ret, $file) + { + switch ($ret[0]) { + case PEAR_TASK_ERROR_MISSING_ATTRIB : + $info = array('attrib' => $ret[1], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> is missing attribute "%attrib%" in file %file%'; + break; + case PEAR_TASK_ERROR_NOATTRIBS : + $info = array('task' => $task, 'file' => $file); + $msg = 'task <%task%> has no attributes in file %file%'; + break; + case PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE : + $info = array('attrib' => $ret[1], 'values' => $ret[3], + 'was' => $ret[2], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> attribute "%attrib%" has the wrong value "%was%" '. + 'in file %file%, expecting one of "%values%"'; + break; + case PEAR_TASK_ERROR_INVALID : + $info = array('reason' => $ret[1], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> in file %file% is invalid because of "%reason%"'; + break; + } + $this->_stack->push(__FUNCTION__, 'error', $info, $msg); + } + + function _unknownTask($task, $file) + { + $this->_stack->push(__FUNCTION__, 'error', array('task' => $task, 'file' => $file), + 'Unknown task "%task%" passed in file '); + } + + function _subpackageCannotProvideExtension($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Subpackage dependency "%name%" cannot use , ' . + 'only package dependencies can use this tag'); + } + + function _subpackagesCannotConflict($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Subpackage dependency "%name%" cannot use , ' . + 'only package dependencies can use this tag'); + } + + function _cannotProvideExtension($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages cannot use , only extbinrelease, extsrcrelease, zendextsrcrelease, and zendextbinrelease can provide a PHP extension'); + } + + function _mustProvideExtension($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages must use to indicate which PHP extension is provided'); + } + + function _cannotHaveSrcpackage($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages cannot specify a source code package, only extension binaries may use the tag'); + } + + function _mustSrcPackage($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '/ packages must specify a source code package with '); + } + + function _mustSrcuri($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '/ packages must specify a source code package with '); + } + + function _uriDepsCannotHaveVersioning($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: dependencies with a tag cannot have any versioning information'); + } + + function _conflictingDepsCannotHaveVersioning($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: conflicting dependencies cannot have versioning info, use to ' . + 'exclude specific versions of a dependency'); + } + + function _DepchannelCannotBeUri($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: channel cannot be __uri, this is a pseudo-channel reserved for uri ' . + 'dependencies only'); + } + + function _bundledPackagesMustBeFilename() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + ' tags must contain only the filename of a package release ' . + 'in the bundle'); + } + + function _binaryPackageMustBePackagename() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + ' tags must contain the name of a package that is ' . + 'a compiled version of this extsrc/zendextsrc package'); + } + + function _fileNotFound($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'File "%file%" in package.xml does not exist'); + } + + function _notInContents($file, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file, 'tag' => $tag), + '<%tag% name="%file%"> is invalid, file is not in '); + } + + function _cannotValidateNoPathSet() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Cannot validate files, no path to package file is set (use setPackageFile())'); + } + + function _usesroletaskMustHaveChannelOrUri($role, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag), + '<%tag%> for role "%role%" must contain either , or and '); + } + + function _usesroletaskMustHavePackage($role, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag), + '<%tag%> for role "%role%" must contain '); + } + + function _usesroletaskMustHaveRoleTask($tag, $type) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'type' => $type), + '<%tag%> must contain <%type%> defining the %type% to be used'); + } + + function _cannotConflictWithAllOs($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag), + '%tag% cannot conflict with all OSes'); + } + + function _invalidDepGroupName($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Invalid dependency group name "%name%"'); + } + + function _multipleToplevelDirNotAllowed() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Multiple top-level tags are not allowed. Enclose them ' . + 'in a '); + } + + function _multipleInstallAs($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Only one tag is allowed for file "%file%"'); + } + + function _ignoreAndInstallAs($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Cannot have both and tags for file "%file%"'); + } + + function _analyzeBundledPackages() + { + if (!$this->_isValid) { + return false; + } + if (!$this->_pf->getPackageType() == 'bundle') { + return false; + } + if (!isset($this->_pf->_packageFile)) { + return false; + } + $dir_prefix = dirname($this->_pf->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') : + array($common, 'log'); + $info = $this->_pf->getContents(); + $info = $info['bundledpackage']; + if (!is_array($info)) { + $info = array($info); + } + $pkg = &new PEAR_PackageFile($this->_pf->_config); + foreach ($info as $package) { + if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $package)) { + $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $package); + $this->_isValid = 0; + continue; + } + call_user_func_array($log, array(1, "Analyzing bundled package $package")); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $pkg->fromAnyFile($dir_prefix . DIRECTORY_SEPARATOR . $package, + PEAR_VALIDATE_NORMAL); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + call_user_func_array($log, array(0, "ERROR: package $package is not a valid " . + 'package')); + $inf = $ret->getUserInfo(); + if (is_array($inf)) { + foreach ($inf as $err) { + call_user_func_array($log, array(1, $err['message'])); + } + } + return false; + } + } + return true; + } + + function _analyzePhpFiles() + { + if (!$this->_isValid) { + return false; + } + if (!isset($this->_pf->_packageFile)) { + $this->_cannotValidateNoPathSet(); + return false; + } + $dir_prefix = dirname($this->_pf->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') : + array(&$common, 'log'); + $info = $this->_pf->getContents(); + if (!$info || !isset($info['dir']['file'])) { + $this->_tagCannotBeEmpty('contents>_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $file); + $this->_isValid = 0; + continue; + } + if (in_array($fa['role'], PEAR_Installer_Role::getPhpRoles()) && $dir_prefix) { + call_user_func_array($log, array(1, "Analyzing $file")); + $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file); + if ($srcinfo) { + $provides = array_merge($provides, $this->_buildProvidesArray($srcinfo)); + } + } + } + $this->_packageName = $pn = $this->_pf->getPackage(); + $pnl = strlen($pn); + foreach ($provides as $key => $what) { + if (isset($what['explicit']) || !$what) { + // skip conformance checks if the provides entry is + // specified in the package.xml file + continue; + } + extract($what); + if ($type == 'class') { + if (!strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn), + 'in %file%: %type% "%name%" not prefixed with package name "%package%"'); + } elseif ($type == 'function') { + if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn), + 'in %file%: %type% "%name%" not prefixed with package name "%package%"'); + } + } + return $this->_isValid; + } + + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @param boolean whether to analyze $file as the file contents + * @return mixed + */ + function analyzeSourceCode($file, $string = false) + { + if (!function_exists("token_get_all")) { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Parser error: token_get_all() function must exist to analyze source code, PHP may have been compiled with --disable-tokenizer'); + return false; + } + + if (!defined('T_DOC_COMMENT')) { + define('T_DOC_COMMENT', T_COMMENT); + } + + if (!defined('T_INTERFACE')) { + define('T_INTERFACE', -1); + } + + if (!defined('T_IMPLEMENTS')) { + define('T_IMPLEMENTS', -1); + } + + if ($string) { + $contents = $file; + } else { + if (!$fp = @fopen($file, "r")) { + return false; + } + fclose($fp); + $contents = file_get_contents($file); + } + + // Silence this function so we can catch PHP Warnings and show our own custom message + $tokens = @token_get_all($contents); + if (isset($php_errormsg)) { + if (isset($this->_stack)) { + $pn = $this->_pf->getPackage(); + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'package' => $pn), + 'in %file%: Could not process file for unkown reasons,' . + ' possibly a PHP parse error in %file% from %package%'); + } + } +/* + for ($i = 0; $i < sizeof($tokens); $i++) { + @list($token, $data) = $tokens[$i]; + if (is_string($token)) { + var_dump($token); + } else { + print token_name($token) . ' '; + var_dump(rtrim($data)); + } + } +*/ + $look_for = 0; + $paren_level = 0; + $bracket_level = 0; + $brace_level = 0; + $lastphpdoc = ''; + $current_class = ''; + $current_interface = ''; + $current_class_level = -1; + $current_function = ''; + $current_function_level = -1; + $declared_classes = array(); + $declared_interfaces = array(); + $declared_functions = array(); + $declared_methods = array(); + $used_classes = array(); + $used_functions = array(); + $extends = array(); + $implements = array(); + $nodeps = array(); + $inquote = false; + $interface = false; + for ($i = 0; $i < sizeof($tokens); $i++) { + if (is_array($tokens[$i])) { + list($token, $data) = $tokens[$i]; + } else { + $token = $tokens[$i]; + $data = ''; + } + + if ($inquote) { + if ($token != '"' && $token != T_END_HEREDOC) { + continue; + } else { + $inquote = false; + continue; + } + } + + switch ($token) { + case T_WHITESPACE : + continue; + case ';': + if ($interface) { + $current_function = ''; + $current_function_level = -1; + } + break; + case '"': + case T_START_HEREDOC: + $inquote = true; + break; + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case '{': $brace_level++; continue 2; + case '}': + $brace_level--; + if ($current_class_level == $brace_level) { + $current_class = ''; + $current_class_level = -1; + } + if ($current_function_level == $brace_level) { + $current_function = ''; + $current_function_level = -1; + } + continue 2; + case '[': $bracket_level++; continue 2; + case ']': $bracket_level--; continue 2; + case '(': $paren_level++; continue 2; + case ')': $paren_level--; continue 2; + case T_INTERFACE: + $interface = true; + case T_CLASS: + if (($current_class_level != -1) || ($current_function_level != -1)) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Parser error: invalid PHP found in file "%file%"'); + } else { + PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"", + PEAR_COMMON_ERROR_INVALIDPHP); + } + + return false; + } + case T_FUNCTION: + case T_NEW: + case T_EXTENDS: + case T_IMPLEMENTS: + $look_for = $token; + continue 2; + case T_STRING: + if (version_compare(zend_version(), '2.0', '<')) { + if (in_array(strtolower($data), + array('public', 'private', 'protected', 'abstract', + 'interface', 'implements', 'throw') + ) + ) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'warning', array( + 'file' => $file), + 'Error, PHP5 token encountered in %file%,' . + ' analysis should be in PHP5'); + } else { + PEAR::raiseError('Error: PHP5 token encountered in ' . $file . + 'packaging should be done in PHP 5'); + return false; + } + } + } + + if ($look_for == T_CLASS) { + $current_class = $data; + $current_class_level = $brace_level; + $declared_classes[] = $current_class; + } elseif ($look_for == T_INTERFACE) { + $current_interface = $data; + $current_class_level = $brace_level; + $declared_interfaces[] = $current_interface; + } elseif ($look_for == T_IMPLEMENTS) { + $implements[$current_class] = $data; + } elseif ($look_for == T_EXTENDS) { + $extends[$current_class] = $data; + } elseif ($look_for == T_FUNCTION) { + if ($current_class) { + $current_function = "$current_class::$data"; + $declared_methods[$current_class][] = $data; + } elseif ($current_interface) { + $current_function = "$current_interface::$data"; + $declared_methods[$current_interface][] = $data; + } else { + $current_function = $data; + $declared_functions[] = $current_function; + } + + $current_function_level = $brace_level; + $m = array(); + } elseif ($look_for == T_NEW) { + $used_classes[$data] = true; + } + + $look_for = 0; + continue 2; + case T_VARIABLE: + $look_for = 0; + continue 2; + case T_DOC_COMMENT: + case T_COMMENT: + if (preg_match('!^/\*\*\s!', $data)) { + $lastphpdoc = $data; + if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) { + $nodeps = array_merge($nodeps, $m[1]); + } + } + continue 2; + case T_DOUBLE_COLON: + if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file), + 'Parser error: invalid PHP found in file "%file%"'); + } else { + PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"", + PEAR_COMMON_ERROR_INVALIDPHP); + } + + return false; + } + + $class = $tokens[$i - 1][1]; + if (strtolower($class) != 'parent') { + $used_classes[$class] = true; + } + + continue 2; + } + } + + return array( + "source_file" => $file, + "declared_classes" => $declared_classes, + "declared_interfaces" => $declared_interfaces, + "declared_methods" => $declared_methods, + "declared_functions" => $declared_functions, + "used_classes" => array_diff(array_keys($used_classes), $nodeps), + "inheritance" => $extends, + "implements" => $implements, + ); + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access private + * + */ + function _buildProvidesArray($srcinfo) + { + if (!$this->_isValid) { + return array(); + } + + $providesret = array(); + $file = basename($srcinfo['source_file']); + $pn = isset($this->_pf) ? $this->_pf->getPackage() : ''; + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($providesret[$key])) { + continue; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $providesret[$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($providesret[$key])) { + continue; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($providesret[$key])) { + continue; + } + + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + + return $providesret; + } +}PEAR-1.9.0/PEAR/PackageFile/v1.php100664 764 764 143760 100664 11302 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v1.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * For error handling + */ +require_once 'PEAR/ErrorStack.php'; + +/** + * Error code if parsing is attempted with no xml extension + */ +define('PEAR_PACKAGEFILE_ERROR_NO_XML_EXT', 3); + +/** + * Error code if creating the xml parser resource fails + */ +define('PEAR_PACKAGEFILE_ERROR_CANT_MAKE_PARSER', 4); + +/** + * Error code used for all sax xml parsing errors + */ +define('PEAR_PACKAGEFILE_ERROR_PARSER_ERROR', 5); + +/** + * Error code used when there is no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_NAME', 6); + +/** + * Error code when a package name is not valid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_NAME', 7); + +/** + * Error code used when no summary is parsed + */ +define('PEAR_PACKAGEFILE_ERROR_NO_SUMMARY', 8); + +/** + * Error code for summaries that are more than 1 line + */ +define('PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY', 9); + +/** + * Error code used when no description is present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION', 10); + +/** + * Error code used when no license is present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_LICENSE', 11); + +/** + * Error code used when a version number is not present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_VERSION', 12); + +/** + * Error code used when a version number is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_VERSION', 13); + +/** + * Error code when release state is missing + */ +define('PEAR_PACKAGEFILE_ERROR_NO_STATE', 14); + +/** + * Error code when release state is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_STATE', 15); + +/** + * Error code when release state is missing + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DATE', 16); + +/** + * Error code when release state is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DATE', 17); + +/** + * Error code when no release notes are found + */ +define('PEAR_PACKAGEFILE_ERROR_NO_NOTES', 18); + +/** + * Error code when no maintainers are found + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS', 19); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE', 20); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE', 21); + +/** + * Error code when a maintainer has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME', 22); + +/** + * Error code when a maintainer has no email + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL', 23); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_MAINTROLE', 24); + +/** + * Error code when a dependency is not a PHP dependency, but has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPNAME', 25); + +/** + * Error code when a dependency has no type (pkg, php, etc.) + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE', 26); + +/** + * Error code when a dependency has no relation (lt, ge, has, etc.) + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPREL', 27); + +/** + * Error code when a dependency is not a 'has' relation, but has no version + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION', 28); + +/** + * Error code when a dependency has an invalid relation + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPREL', 29); + +/** + * Error code when a dependency has an invalid type + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPTYPE', 30); + +/** + * Error code when a dependency has an invalid optional option + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL', 31); + +/** + * Error code when a dependency is a pkg dependency, and has an invalid package name + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPNAME', 32); + +/** + * Error code when a dependency has a channel="foo" attribute, and foo is not a registered channel + */ +define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_DEPCHANNEL', 33); + +/** + * Error code when rel="has" and version attribute is present. + */ +define('PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED', 34); + +/** + * Error code when type="php" and dependency name is present + */ +define('PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED', 35); + +/** + * Error code when a configure option has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_CONFNAME', 36); + +/** + * Error code when a configure option has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT', 37); + +/** + * Error code when a file in the filelist has an invalid role + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE', 38); + +/** + * Error code when a file in the filelist has no role + */ +define('PEAR_PACKAGEFILE_ERROR_NO_FILEROLE', 39); + +/** + * Error code when analyzing a php source file that has parse errors + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE', 40); + +/** + * Error code when analyzing a php source file reveals a source element + * without a package name prefix + */ +define('PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX', 41); + +/** + * Error code when an unknown channel is specified + */ +define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_CHANNEL', 42); + +/** + * Error code when no files are found in the filelist + */ +define('PEAR_PACKAGEFILE_ERROR_NO_FILES', 43); + +/** + * Error code when a file is not valid php according to _analyzeSourceCode() + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_FILE', 44); + +/** + * Error code when the channel validator returns an error or warning + */ +define('PEAR_PACKAGEFILE_ERROR_CHANNELVAL', 45); + +/** + * Error code when a php5 package is packaged in php4 (analysis doesn't work) + */ +define('PEAR_PACKAGEFILE_ERROR_PHP5', 46); + +/** + * Error code when a file is listed in package.xml but does not exist + */ +define('PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND', 47); + +/** + * Error code when a + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_v1 +{ + /** + * @access private + * @var PEAR_ErrorStack + * @access private + */ + var $_stack; + + /** + * A registry object, used to access the package name validation regex for non-standard channels + * @var PEAR_Registry + * @access private + */ + var $_registry; + + /** + * An object that contains a log method that matches PEAR_Common::log's signature + * @var object + * @access private + */ + var $_logger; + + /** + * Parsed package information + * @var array + * @access private + */ + var $_packageInfo; + + /** + * path to package.xml + * @var string + * @access private + */ + var $_packageFile; + + /** + * path to package .tgz or false if this is a local/extracted package.xml + * @var string + * @access private + */ + var $_archiveFile; + + /** + * @var int + * @access private + */ + var $_isValid = 0; + + /** + * Determines whether this packagefile was initialized only with partial package info + * + * If this package file was constructed via parsing REST, it will only contain + * + * - package name + * - channel name + * - dependencies + * @var boolean + * @access private + */ + var $_incomplete = true; + + /** + * @param bool determines whether to return a PEAR_Error object, or use the PEAR_ErrorStack + * @param string Name of Error Stack class to use. + */ + function PEAR_PackageFile_v1() + { + $this->_stack = &new PEAR_ErrorStack('PEAR_PackageFile_v1'); + $this->_stack->setErrorMessageTemplate($this->_getErrorMessage()); + $this->_isValid = 0; + } + + function installBinary($installer) + { + return false; + } + + function isExtension($name) + { + return false; + } + + function setConfig(&$config) + { + $this->_config = &$config; + $this->_registry = &$config->getRegistry(); + } + + function setRequestedGroup() + { + // placeholder + } + + /** + * For saving in the registry. + * + * Set the last version that was installed + * @param string + */ + function setLastInstalledVersion($version) + { + $this->_packageInfo['_lastversion'] = $version; + } + + /** + * @return string|false + */ + function getLastInstalledVersion() + { + if (isset($this->_packageInfo['_lastversion'])) { + return $this->_packageInfo['_lastversion']; + } + return false; + } + + function getInstalledBinary() + { + return false; + } + + function listPostinstallScripts() + { + return false; + } + + function initPostinstallScripts() + { + return false; + } + + function setLogger(&$logger) + { + if ($logger && (!is_object($logger) || !method_exists($logger, 'log'))) { + return PEAR::raiseError('Logger must be compatible with PEAR_Common::log'); + } + $this->_logger = &$logger; + } + + function setPackagefile($file, $archive = false) + { + $this->_packageFile = $file; + $this->_archiveFile = $archive ? $archive : $file; + } + + function getPackageFile() + { + return isset($this->_packageFile) ? $this->_packageFile : false; + } + + function getPackageType() + { + return 'php'; + } + + function getArchiveFile() + { + return $this->_archiveFile; + } + + function packageInfo($field) + { + if (!is_string($field) || empty($field) || + !isset($this->_packageInfo[$field])) { + return false; + } + return $this->_packageInfo[$field]; + } + + function setDirtree($path) + { + if (!isset($this->_packageInfo['dirtree'])) { + $this->_packageInfo['dirtree'] = array(); + } + $this->_packageInfo['dirtree'][$path] = true; + } + + function getDirtree() + { + if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) { + return $this->_packageInfo['dirtree']; + } + return false; + } + + function resetDirtree() + { + unset($this->_packageInfo['dirtree']); + } + + function fromArray($pinfo) + { + $this->_incomplete = false; + $this->_packageInfo = $pinfo; + } + + function isIncomplete() + { + return $this->_incomplete; + } + + function getChannel() + { + return 'pear.php.net'; + } + + function getUri() + { + return false; + } + + function getTime() + { + return false; + } + + function getExtends() + { + if (isset($this->_packageInfo['extends'])) { + return $this->_packageInfo['extends']; + } + return false; + } + + /** + * @return array + */ + function toArray() + { + if (!$this->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + return $this->getArray(); + } + + function getArray() + { + return $this->_packageInfo; + } + + function getName() + { + return $this->getPackage(); + } + + function getPackage() + { + if (isset($this->_packageInfo['package'])) { + return $this->_packageInfo['package']; + } + return false; + } + + /** + * WARNING - don't use this unless you know what you are doing + */ + function setRawPackage($package) + { + $this->_packageInfo['package'] = $package; + } + + function setPackage($package) + { + $this->_packageInfo['package'] = $package; + $this->_isValid = false; + } + + function getVersion() + { + if (isset($this->_packageInfo['version'])) { + return $this->_packageInfo['version']; + } + return false; + } + + function setVersion($version) + { + $this->_packageInfo['version'] = $version; + $this->_isValid = false; + } + + function clearMaintainers() + { + unset($this->_packageInfo['maintainers']); + } + + function getMaintainers() + { + if (isset($this->_packageInfo['maintainers'])) { + return $this->_packageInfo['maintainers']; + } + return false; + } + + /** + * Adds a new maintainer - no checking of duplicates is performed, use + * updatemaintainer for that purpose. + */ + function addMaintainer($role, $handle, $name, $email) + { + $this->_packageInfo['maintainers'][] = + array('handle' => $handle, 'role' => $role, 'email' => $email, 'name' => $name); + $this->_isValid = false; + } + + function updateMaintainer($role, $handle, $name, $email) + { + $found = false; + if (!isset($this->_packageInfo['maintainers']) || + !is_array($this->_packageInfo['maintainers'])) { + return $this->addMaintainer($role, $handle, $name, $email); + } + foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) { + if ($maintainer['handle'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo['maintainers'][$found]); + $this->_packageInfo['maintainers'] = + array_values($this->_packageInfo['maintainers']); + } + $this->addMaintainer($role, $handle, $name, $email); + } + + function deleteMaintainer($handle) + { + $found = false; + foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) { + if ($maintainer['handle'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo['maintainers'][$found]); + $this->_packageInfo['maintainers'] = + array_values($this->_packageInfo['maintainers']); + return true; + } + return false; + } + + function getState() + { + if (isset($this->_packageInfo['release_state'])) { + return $this->_packageInfo['release_state']; + } + return false; + } + + function setRawState($state) + { + $this->_packageInfo['release_state'] = $state; + } + + function setState($state) + { + $this->_packageInfo['release_state'] = $state; + $this->_isValid = false; + } + + function getDate() + { + if (isset($this->_packageInfo['release_date'])) { + return $this->_packageInfo['release_date']; + } + return false; + } + + function setDate($date) + { + $this->_packageInfo['release_date'] = $date; + $this->_isValid = false; + } + + function getLicense() + { + if (isset($this->_packageInfo['release_license'])) { + return $this->_packageInfo['release_license']; + } + return false; + } + + function setLicense($date) + { + $this->_packageInfo['release_license'] = $date; + $this->_isValid = false; + } + + function getSummary() + { + if (isset($this->_packageInfo['summary'])) { + return $this->_packageInfo['summary']; + } + return false; + } + + function setSummary($summary) + { + $this->_packageInfo['summary'] = $summary; + $this->_isValid = false; + } + + function getDescription() + { + if (isset($this->_packageInfo['description'])) { + return $this->_packageInfo['description']; + } + return false; + } + + function setDescription($desc) + { + $this->_packageInfo['description'] = $desc; + $this->_isValid = false; + } + + function getNotes() + { + if (isset($this->_packageInfo['release_notes'])) { + return $this->_packageInfo['release_notes']; + } + return false; + } + + function setNotes($notes) + { + $this->_packageInfo['release_notes'] = $notes; + $this->_isValid = false; + } + + function getDeps() + { + if (isset($this->_packageInfo['release_deps'])) { + return $this->_packageInfo['release_deps']; + } + return false; + } + + /** + * Reset dependencies prior to adding new ones + */ + function clearDeps() + { + unset($this->_packageInfo['release_deps']); + } + + function addPhpDep($version, $rel) + { + $this->_isValid = false; + $this->_packageInfo['release_deps'][] = + array('type' => 'php', + 'rel' => $rel, + 'version' => $version); + } + + function addPackageDep($name, $version, $rel, $optional = 'no') + { + $this->_isValid = false; + $dep = + array('type' => 'pkg', + 'name' => $name, + 'rel' => $rel, + 'optional' => $optional); + if ($rel != 'has' && $rel != 'not') { + $dep['version'] = $version; + } + $this->_packageInfo['release_deps'][] = $dep; + } + + function addExtensionDep($name, $version, $rel, $optional = 'no') + { + $this->_isValid = false; + $this->_packageInfo['release_deps'][] = + array('type' => 'ext', + 'name' => $name, + 'rel' => $rel, + 'version' => $version, + 'optional' => $optional); + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setDeps($deps) + { + $this->_packageInfo['release_deps'] = $deps; + } + + function hasDeps() + { + return isset($this->_packageInfo['release_deps']) && + count($this->_packageInfo['release_deps']); + } + + function getDependencyGroup($group) + { + return false; + } + + function isCompatible($pf) + { + return false; + } + + function isSubpackageOf($p) + { + return $p->isSubpackage($this); + } + + function isSubpackage($p) + { + return false; + } + + function dependsOn($package, $channel) + { + if (strtolower($channel) != 'pear.php.net') { + return false; + } + if (!($deps = $this->getDeps())) { + return false; + } + foreach ($deps as $dep) { + if ($dep['type'] != 'pkg') { + continue; + } + if (strtolower($dep['name']) == strtolower($package)) { + return true; + } + } + return false; + } + + function getConfigureOptions() + { + if (isset($this->_packageInfo['configure_options'])) { + return $this->_packageInfo['configure_options']; + } + return false; + } + + function hasConfigureOptions() + { + return isset($this->_packageInfo['configure_options']) && + count($this->_packageInfo['configure_options']); + } + + function addConfigureOption($name, $prompt, $default = false) + { + $o = array('name' => $name, 'prompt' => $prompt); + if ($default !== false) { + $o['default'] = $default; + } + if (!isset($this->_packageInfo['configure_options'])) { + $this->_packageInfo['configure_options'] = array(); + } + $this->_packageInfo['configure_options'][] = $o; + } + + function clearConfigureOptions() + { + unset($this->_packageInfo['configure_options']); + } + + function getProvides() + { + if (isset($this->_packageInfo['provides'])) { + return $this->_packageInfo['provides']; + } + return false; + } + + function getProvidesExtension() + { + return false; + } + + function addFile($dir, $file, $attrs) + { + $dir = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), $dir); + if ($dir == '/' || $dir == '') { + $dir = ''; + } else { + $dir .= '/'; + } + $file = $dir . $file; + $file = preg_replace('![\\/]+!', '/', $file); + $this->_packageInfo['filelist'][$file] = $attrs; + } + + function getInstallationFilelist() + { + return $this->getFilelist(); + } + + function getFilelist() + { + if (isset($this->_packageInfo['filelist'])) { + return $this->_packageInfo['filelist']; + } + return false; + } + + function setFileAttribute($file, $attr, $value) + { + $this->_packageInfo['filelist'][$file][$attr] = $value; + } + + function resetFilelist() + { + $this->_packageInfo['filelist'] = array(); + } + + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts); + } else { + $this->_packageInfo['filelist'][$file] = $atts; + } + } + + function getChangelog() + { + if (isset($this->_packageInfo['changelog'])) { + return $this->_packageInfo['changelog']; + } + return false; + } + + function getPackagexmlVersion() + { + return '1.0'; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getValidationWarnings($purge = true) + { + return $this->_stack->getErrors($purge); + } + + // }}} + /** + * Validation error. Also marks the object contents as invalid + * @param error code + * @param array error information + * @access private + */ + function _validateError($code, $params = array()) + { + $this->_stack->push($code, 'error', $params, false, false, debug_backtrace()); + $this->_isValid = false; + } + + /** + * Validation warning. Does not mark the object contents invalid. + * @param error code + * @param array error information + * @access private + */ + function _validateWarning($code, $params = array()) + { + $this->_stack->push($code, 'warning', $params, false, false, debug_backtrace()); + } + + /** + * @param integer error code + * @access protected + */ + function _getErrorMessage() + { + return array( + PEAR_PACKAGEFILE_ERROR_NO_NAME => + 'Missing Package Name', + PEAR_PACKAGEFILE_ERROR_NO_SUMMARY => + 'No summary found', + PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY => + 'Summary should be on one line', + PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION => + 'Missing description', + PEAR_PACKAGEFILE_ERROR_NO_LICENSE => + 'Missing license', + PEAR_PACKAGEFILE_ERROR_NO_VERSION => + 'No release version found', + PEAR_PACKAGEFILE_ERROR_NO_STATE => + 'No release state found', + PEAR_PACKAGEFILE_ERROR_NO_DATE => + 'No release date found', + PEAR_PACKAGEFILE_ERROR_NO_NOTES => + 'No release notes found', + PEAR_PACKAGEFILE_ERROR_NO_LEAD => + 'Package must have at least one lead maintainer', + PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS => + 'No maintainers found, at least one must be defined', + PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE => + 'Maintainer %index% has no handle (user ID at channel server)', + PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE => + 'Maintainer %index% has no role', + PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME => + 'Maintainer %index% has no name', + PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL => + 'Maintainer %index% has no email', + PEAR_PACKAGEFILE_ERROR_NO_DEPNAME => + 'Dependency %index% is not a php dependency, and has no name', + PEAR_PACKAGEFILE_ERROR_NO_DEPREL => + 'Dependency %index% has no relation (rel)', + PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE => + 'Dependency %index% has no type', + PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED => + 'PHP Dependency %index% has a name attribute of "%name%" which will be' . + ' ignored!', + PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION => + 'Dependency %index% is not a rel="has" or rel="not" dependency, ' . + 'and has no version', + PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION => + 'Dependency %index% is a type="php" dependency, ' . + 'and has no version', + PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED => + 'Dependency %index% is a rel="%rel%" dependency, versioning is ignored', + PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL => + 'Dependency %index% has invalid optional value "%opt%", should be yes or no', + PEAR_PACKAGEFILE_PHP_NO_NOT => + 'Dependency %index%: php dependencies cannot use "not" rel, use "ne"' . + ' to exclude specific versions', + PEAR_PACKAGEFILE_ERROR_NO_CONFNAME => + 'Configure Option %index% has no name', + PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT => + 'Configure Option %index% has no prompt', + PEAR_PACKAGEFILE_ERROR_NO_FILES => + 'No files in section of package.xml', + PEAR_PACKAGEFILE_ERROR_NO_FILEROLE => + 'File "%file%" has no role, expecting one of "%roles%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE => + 'File "%file%" has invalid role "%role%", expecting one of "%roles%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME => + 'File "%file%" cannot start with ".", cannot package or install', + PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE => + 'Parser error: invalid PHP found in file "%file%"', + PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX => + 'in %file%: %type% "%name%" not prefixed with package name "%package%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILE => + 'Parser error: invalid PHP file "%file%"', + PEAR_PACKAGEFILE_ERROR_CHANNELVAL => + 'Channel validator error: field "%field%" - %reason%', + PEAR_PACKAGEFILE_ERROR_PHP5 => + 'Error, PHP5 token encountered in %file%, analysis should be in PHP5', + PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND => + 'File "%file%" in package.xml does not exist', + PEAR_PACKAGEFILE_ERROR_NON_ISO_CHARS => + 'Package.xml contains non-ISO-8859-1 characters, and may not validate', + ); + } + + /** + * Validate XML package definition file. + * + * @access public + * @return boolean + */ + function validate($state = PEAR_VALIDATE_NORMAL, $nofilechecking = false) + { + if (($this->_isValid & $state) == $state) { + return true; + } + $this->_isValid = true; + $info = $this->_packageInfo; + if (empty($info['package'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NAME); + $this->_packageName = $pn = 'unknown'; + } else { + $this->_packageName = $pn = $info['package']; + } + + if (empty($info['summary'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_SUMMARY); + } elseif (strpos(trim($info['summary']), "\n") !== false) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $info['summary'])); + } + if (empty($info['description'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION); + } + if (empty($info['release_license'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LICENSE); + } + if (empty($info['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_VERSION); + } + if (empty($info['release_state'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_STATE); + } + if (empty($info['release_date'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DATE); + } + if (empty($info['release_notes'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NOTES); + } + if (empty($info['maintainers'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS); + } else { + $haslead = false; + $i = 1; + foreach ($info['maintainers'] as $m) { + if (empty($m['handle'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE, + array('index' => $i)); + } + if (empty($m['role'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE, + array('index' => $i, 'roles' => PEAR_Common::getUserRoles())); + } elseif ($m['role'] == 'lead') { + $haslead = true; + } + if (empty($m['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME, + array('index' => $i)); + } + if (empty($m['email'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL, + array('index' => $i)); + } + $i++; + } + if (!$haslead) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LEAD); + } + } + if (!empty($info['release_deps'])) { + $i = 1; + foreach ($info['release_deps'] as $d) { + if (!isset($d['type']) || empty($d['type'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE, + array('index' => $i, 'types' => PEAR_Common::getDependencyTypes())); + continue; + } + if (!isset($d['rel']) || empty($d['rel'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPREL, + array('index' => $i, 'rels' => PEAR_Common::getDependencyRelations())); + continue; + } + if (!empty($d['optional'])) { + if (!in_array($d['optional'], array('yes', 'no'))) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL, + array('index' => $i, 'opt' => $d['optional'])); + } + } + if ($d['rel'] != 'has' && $d['rel'] != 'not' && empty($d['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION, + array('index' => $i)); + } elseif (($d['rel'] == 'has' || $d['rel'] == 'not') && !empty($d['version'])) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED, + array('index' => $i, 'rel' => $d['rel'])); + } + if ($d['type'] == 'php' && !empty($d['name'])) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED, + array('index' => $i, 'name' => $d['name'])); + } elseif ($d['type'] != 'php' && empty($d['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPNAME, + array('index' => $i)); + } + if ($d['type'] == 'php' && empty($d['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION, + array('index' => $i)); + } + if (($d['rel'] == 'not') && ($d['type'] == 'php')) { + $this->_validateError(PEAR_PACKAGEFILE_PHP_NO_NOT, + array('index' => $i)); + } + $i++; + } + } + if (!empty($info['configure_options'])) { + $i = 1; + foreach ($info['configure_options'] as $c) { + if (empty($c['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFNAME, + array('index' => $i)); + } + if (empty($c['prompt'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT, + array('index' => $i)); + } + $i++; + } + } + if (empty($info['filelist'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILES); + $errors[] = 'no files'; + } else { + foreach ($info['filelist'] as $file => $fa) { + if (empty($fa['role'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILEROLE, + array('file' => $file, 'roles' => PEAR_Common::getFileRoles())); + continue; + } elseif (!in_array($fa['role'], PEAR_Common::getFileRoles())) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE, + array('file' => $file, 'role' => $fa['role'], 'roles' => PEAR_Common::getFileRoles())); + } + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', str_replace('\\', '/', $file))) { + // file contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file)); + } + if (isset($fa['install-as']) && + preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $fa['install-as']))) { + // install-as contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file . ' [installed as ' . $fa['install-as'] . ']')); + } + if (isset($fa['baseinstalldir']) && + preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $fa['baseinstalldir']))) { + // install-as contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file . ' [baseinstalldir ' . $fa['baseinstalldir'] . ']')); + } + } + } + if (isset($this->_registry) && $this->_isValid) { + $chan = $this->_registry->getChannel('pear.php.net'); + if (PEAR::isError($chan)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $chan->getMessage()); + return $this->_isValid = 0; + } + $validator = $chan->getValidationObject(); + $validator->setPackageFile($this); + $validator->validate($state); + $failures = $validator->getFailures(); + foreach ($failures['errors'] as $error) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $error); + } + foreach ($failures['warnings'] as $warning) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $warning); + } + } + if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$nofilechecking) { + if ($this->_analyzePhpFiles()) { + $this->_isValid = true; + } + } + if ($this->_isValid) { + return $this->_isValid = $state; + } + return $this->_isValid = 0; + } + + function _analyzePhpFiles() + { + if (!$this->_isValid) { + return false; + } + if (!isset($this->_packageFile)) { + return false; + } + $dir_prefix = dirname($this->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_logger) ? array(&$this->_logger, 'log') : + array($common, 'log'); + $info = $this->getFilelist(); + foreach ($info as $file => $fa) { + if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND, + array('file' => realpath($dir_prefix) . DIRECTORY_SEPARATOR . $file)); + continue; + } + if ($fa['role'] == 'php' && $dir_prefix) { + call_user_func_array($log, array(1, "Analyzing $file")); + $srcinfo = $this->_analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file); + if ($srcinfo) { + $this->_buildProvidesArray($srcinfo); + } + } + } + $this->_packageName = $pn = $this->getPackage(); + $pnl = strlen($pn); + if (isset($this->_packageInfo['provides'])) { + foreach ((array) $this->_packageInfo['provides'] as $key => $what) { + if (isset($what['explicit'])) { + // skip conformance checks if the provides entry is + // specified in the package.xml file + continue; + } + extract($what); + if ($type == 'class') { + if (!strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX, + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn)); + } elseif ($type == 'function') { + if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX, + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn)); + } + } + } + return $this->_isValid; + } + + /** + * Get the default xml generator object + * + * @return PEAR_PackageFile_Generator_v1 + */ + function &getDefaultGenerator() + { + if (!class_exists('PEAR_PackageFile_Generator_v1')) { + require_once 'PEAR/PackageFile/Generator/v1.php'; + } + $a = &new PEAR_PackageFile_Generator_v1($this); + return $a; + } + + /** + * Get the contents of a file listed within the package.xml + * @param string + * @return string + */ + function getFileContents($file) + { + if ($this->_archiveFile == $this->_packageFile) { // unpacked + $dir = dirname($this->_packageFile); + $file = $dir . DIRECTORY_SEPARATOR . $file; + $file = str_replace(array('/', '\\'), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file); + if (file_exists($file) && is_readable($file)) { + return implode('', file($file)); + } + } else { // tgz + if (!class_exists('Archive_Tar')) { + require_once 'Archive/Tar.php'; + } + $tar = &new Archive_Tar($this->_archiveFile); + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + if ($file != 'package.xml' && $file != 'package2.xml') { + $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file; + } + $file = $tar->extractInString($file); + $tar->popErrorHandling(); + if (PEAR::isError($file)) { + return PEAR::raiseError("Cannot locate file '$file' in archive"); + } + return $file; + } + } + + // {{{ analyzeSourceCode() + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @return mixed + * @access private + */ + function _analyzeSourceCode($file) + { + if (!function_exists("token_get_all")) { + return false; + } + if (!defined('T_DOC_COMMENT')) { + define('T_DOC_COMMENT', T_COMMENT); + } + if (!defined('T_INTERFACE')) { + define('T_INTERFACE', -1); + } + if (!defined('T_IMPLEMENTS')) { + define('T_IMPLEMENTS', -1); + } + if (!$fp = @fopen($file, "r")) { + return false; + } + fclose($fp); + $contents = file_get_contents($file); + $tokens = token_get_all($contents); +/* + for ($i = 0; $i < sizeof($tokens); $i++) { + @list($token, $data) = $tokens[$i]; + if (is_string($token)) { + var_dump($token); + } else { + print token_name($token) . ' '; + var_dump(rtrim($data)); + } + } +*/ + $look_for = 0; + $paren_level = 0; + $bracket_level = 0; + $brace_level = 0; + $lastphpdoc = ''; + $current_class = ''; + $current_interface = ''; + $current_class_level = -1; + $current_function = ''; + $current_function_level = -1; + $declared_classes = array(); + $declared_interfaces = array(); + $declared_functions = array(); + $declared_methods = array(); + $used_classes = array(); + $used_functions = array(); + $extends = array(); + $implements = array(); + $nodeps = array(); + $inquote = false; + $interface = false; + for ($i = 0; $i < sizeof($tokens); $i++) { + if (is_array($tokens[$i])) { + list($token, $data) = $tokens[$i]; + } else { + $token = $tokens[$i]; + $data = ''; + } + if ($inquote) { + if ($token != '"' && $token != T_END_HEREDOC) { + continue; + } else { + $inquote = false; + continue; + } + } + switch ($token) { + case T_WHITESPACE : + continue; + case ';': + if ($interface) { + $current_function = ''; + $current_function_level = -1; + } + break; + case '"': + case T_START_HEREDOC: + $inquote = true; + break; + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case '{': $brace_level++; continue 2; + case '}': + $brace_level--; + if ($current_class_level == $brace_level) { + $current_class = ''; + $current_class_level = -1; + } + if ($current_function_level == $brace_level) { + $current_function = ''; + $current_function_level = -1; + } + continue 2; + case '[': $bracket_level++; continue 2; + case ']': $bracket_level--; continue 2; + case '(': $paren_level++; continue 2; + case ')': $paren_level--; continue 2; + case T_INTERFACE: + $interface = true; + case T_CLASS: + if (($current_class_level != -1) || ($current_function_level != -1)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE, + array('file' => $file)); + return false; + } + case T_FUNCTION: + case T_NEW: + case T_EXTENDS: + case T_IMPLEMENTS: + $look_for = $token; + continue 2; + case T_STRING: + if (version_compare(zend_version(), '2.0', '<')) { + if (in_array(strtolower($data), + array('public', 'private', 'protected', 'abstract', + 'interface', 'implements', 'throw') + )) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_PHP5, + array($file)); + } + } + if ($look_for == T_CLASS) { + $current_class = $data; + $current_class_level = $brace_level; + $declared_classes[] = $current_class; + } elseif ($look_for == T_INTERFACE) { + $current_interface = $data; + $current_class_level = $brace_level; + $declared_interfaces[] = $current_interface; + } elseif ($look_for == T_IMPLEMENTS) { + $implements[$current_class] = $data; + } elseif ($look_for == T_EXTENDS) { + $extends[$current_class] = $data; + } elseif ($look_for == T_FUNCTION) { + if ($current_class) { + $current_function = "$current_class::$data"; + $declared_methods[$current_class][] = $data; + } elseif ($current_interface) { + $current_function = "$current_interface::$data"; + $declared_methods[$current_interface][] = $data; + } else { + $current_function = $data; + $declared_functions[] = $current_function; + } + $current_function_level = $brace_level; + $m = array(); + } elseif ($look_for == T_NEW) { + $used_classes[$data] = true; + } + $look_for = 0; + continue 2; + case T_VARIABLE: + $look_for = 0; + continue 2; + case T_DOC_COMMENT: + case T_COMMENT: + if (preg_match('!^/\*\*\s!', $data)) { + $lastphpdoc = $data; + if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) { + $nodeps = array_merge($nodeps, $m[1]); + } + } + continue 2; + case T_DOUBLE_COLON: + if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE, + array('file' => $file)); + return false; + } + $class = $tokens[$i - 1][1]; + if (strtolower($class) != 'parent') { + $used_classes[$class] = true; + } + continue 2; + } + } + return array( + "source_file" => $file, + "declared_classes" => $declared_classes, + "declared_interfaces" => $declared_interfaces, + "declared_methods" => $declared_methods, + "declared_functions" => $declared_functions, + "used_classes" => array_diff(array_keys($used_classes), $nodeps), + "inheritance" => $extends, + "implements" => $implements, + ); + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access private + * + */ + function _buildProvidesArray($srcinfo) + { + if (!$this->_isValid) { + return false; + } + $file = basename($srcinfo['source_file']); + $pn = $this->getPackage(); + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($this->_packageInfo['provides'][$key])) { + continue; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $this->_packageInfo['provides'][$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($this->_packageInfo['provides'][$key])) { + continue; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($this->_packageInfo['provides'][$key])) { + continue; + } + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + // }}} +} +?> +PEAR-1.9.0/PEAR/PackageFile/v2.php100664 764 764 207576 100664 11311 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v2.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * For error handling + */ +require_once 'PEAR/ErrorStack.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_v2 +{ + + /** + * Parsed package information + * @var array + * @access private + */ + var $_packageInfo = array(); + + /** + * path to package .tgz or false if this is a local/extracted package.xml + * @var string|false + * @access private + */ + var $_archiveFile; + + /** + * path to package .xml or false if this is an abstract parsed-from-string xml + * @var string|false + * @access private + */ + var $_packageFile; + + /** + * This is used by file analysis routines to log progress information + * @var PEAR_Common + * @access protected + */ + var $_logger; + + /** + * This is set to the highest validation level that has been validated + * + * If the package.xml is invalid or unknown, this is set to 0. If + * normal validation has occurred, this is set to PEAR_VALIDATE_NORMAL. If + * downloading/installation validation has occurred it is set to PEAR_VALIDATE_DOWNLOADING + * or INSTALLING, and so on up to PEAR_VALIDATE_PACKAGING. This allows validation + * "caching" to occur, which is particularly important for package validation, so + * that PHP files are not validated twice + * @var int + * @access private + */ + var $_isValid = 0; + + /** + * True if the filelist has been validated + * @param bool + */ + var $_filesValid = false; + + /** + * @var PEAR_Registry + * @access protected + */ + var $_registry; + + /** + * @var PEAR_Config + * @access protected + */ + var $_config; + + /** + * Optional Dependency group requested for installation + * @var string + * @access private + */ + var $_requestedGroup = false; + + /** + * @var PEAR_ErrorStack + * @access protected + */ + var $_stack; + + /** + * Namespace prefix used for tasks in this package.xml - use tasks: whenever possible + */ + var $_tasksNs; + + /** + * Determines whether this packagefile was initialized only with partial package info + * + * If this package file was constructed via parsing REST, it will only contain + * + * - package name + * - channel name + * - dependencies + * @var boolean + * @access private + */ + var $_incomplete = true; + + /** + * @var PEAR_PackageFile_v2_Validator + */ + var $_v2Validator; + + /** + * The constructor merely sets up the private error stack + */ + function PEAR_PackageFile_v2() + { + $this->_stack = new PEAR_ErrorStack('PEAR_PackageFile_v2', false, null); + $this->_isValid = false; + } + + /** + * To make unit-testing easier + * @param PEAR_Frontend_* + * @param array options + * @param PEAR_Config + * @return PEAR_Downloader + * @access protected + */ + function &getPEARDownloader(&$i, $o, &$c) + { + $z = &new PEAR_Downloader($i, $o, $c); + return $z; + } + + /** + * To make unit-testing easier + * @param PEAR_Config + * @param array options + * @param array package name as returned from {@link PEAR_Registry::parsePackageName()} + * @param int PEAR_VALIDATE_* constant + * @return PEAR_Dependency2 + * @access protected + */ + function &getPEARDependency2(&$c, $o, $p, $s = PEAR_VALIDATE_INSTALLING) + { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + $z = &new PEAR_Dependency2($c, $o, $p, $s); + return $z; + } + + function getInstalledBinary() + { + return isset($this->_packageInfo['#binarypackage']) ? $this->_packageInfo['#binarypackage'] : + false; + } + + /** + * Installation of source package has failed, attempt to download and install the + * binary version of this package. + * @param PEAR_Installer + * @return array|false + */ + function installBinary(&$installer) + { + if (!OS_WINDOWS) { + $a = false; + return $a; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $releasetype = $this->getPackageType() . 'release'; + if (!is_array($installer->getInstallPackages())) { + $a = false; + return $a; + } + foreach ($installer->getInstallPackages() as $p) { + if ($p->isExtension($this->_packageInfo['providesextension'])) { + if ($p->getPackageType() != 'extsrc' && $p->getPackageType() != 'zendextsrc') { + $a = false; + return $a; // the user probably downloaded it separately + } + } + } + if (isset($this->_packageInfo[$releasetype]['binarypackage'])) { + $installer->log(0, 'Attempting to download binary version of extension "' . + $this->_packageInfo['providesextension'] . '"'); + $params = $this->_packageInfo[$releasetype]['binarypackage']; + if (!is_array($params) || !isset($params[0])) { + $params = array($params); + } + if (isset($this->_packageInfo['channel'])) { + foreach ($params as $i => $param) { + $params[$i] = array('channel' => $this->_packageInfo['channel'], + 'package' => $param, 'version' => $this->getVersion()); + } + } + $dl = &$this->getPEARDownloader($installer->ui, $installer->getOptions(), + $installer->config); + $verbose = $dl->config->get('verbose'); + $dl->config->set('verbose', -1); + foreach ($params as $param) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $dl->download(array($param)); + PEAR::popErrorHandling(); + if (is_array($ret) && count($ret)) { + break; + } + } + $dl->config->set('verbose', $verbose); + if (is_array($ret)) { + if (count($ret) == 1) { + $pf = $ret[0]->getPackageFile(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $installer->install($ret[0]); + PEAR::popErrorHandling(); + if (is_array($err)) { + $this->_packageInfo['#binarypackage'] = $ret[0]->getPackage(); + // "install" self, so all dependencies will work transparently + $this->_registry->addPackage2($this); + $installer->log(0, 'Download and install of binary extension "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pf->getChannel(), + 'package' => $pf->getPackage()), true) . '" successful'); + $a = array($ret[0], $err); + return $a; + } + $installer->log(0, 'Download and install of binary extension "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pf->getChannel(), + 'package' => $pf->getPackage()), true) . '" failed'); + } + } + } + } + $a = false; + return $a; + } + + /** + * @return string|false Extension name + */ + function getProvidesExtension() + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + if (isset($this->_packageInfo['providesextension'])) { + return $this->_packageInfo['providesextension']; + } + } + return false; + } + + /** + * @param string Extension name + * @return bool + */ + function isExtension($extension) + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + return $this->_packageInfo['providesextension'] == $extension; + } + return false; + } + + /** + * Tests whether every part of the package.xml 1.0 is represented in + * this package.xml 2.0 + * @param PEAR_PackageFile_v1 + * @return bool + */ + function isEquivalent($pf1) + { + if (!$pf1) { + return true; + } + if ($this->getPackageType() == 'bundle') { + return false; + } + $this->_stack->getErrors(true); + if (!$pf1->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + $pass = true; + if ($pf1->getPackage() != $this->getPackage()) { + $this->_differentPackage($pf1->getPackage()); + $pass = false; + } + if ($pf1->getVersion() != $this->getVersion()) { + $this->_differentVersion($pf1->getVersion()); + $pass = false; + } + if (trim($pf1->getSummary()) != $this->getSummary()) { + $this->_differentSummary($pf1->getSummary()); + $pass = false; + } + if (preg_replace('/\s+/', '', $pf1->getDescription()) != + preg_replace('/\s+/', '', $this->getDescription())) { + $this->_differentDescription($pf1->getDescription()); + $pass = false; + } + if ($pf1->getState() != $this->getState()) { + $this->_differentState($pf1->getState()); + $pass = false; + } + if (!strstr(preg_replace('/\s+/', '', $this->getNotes()), + preg_replace('/\s+/', '', $pf1->getNotes()))) { + $this->_differentNotes($pf1->getNotes()); + $pass = false; + } + $mymaintainers = $this->getMaintainers(); + $yourmaintainers = $pf1->getMaintainers(); + for ($i1 = 0; $i1 < count($yourmaintainers); $i1++) { + $reset = false; + for ($i2 = 0; $i2 < count($mymaintainers); $i2++) { + if ($mymaintainers[$i2]['handle'] == $yourmaintainers[$i1]['handle']) { + if ($mymaintainers[$i2]['role'] != $yourmaintainers[$i1]['role']) { + $this->_differentRole($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['role'], $mymaintainers[$i2]['role']); + $pass = false; + } + if ($mymaintainers[$i2]['email'] != $yourmaintainers[$i1]['email']) { + $this->_differentEmail($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['email'], $mymaintainers[$i2]['email']); + $pass = false; + } + if ($mymaintainers[$i2]['name'] != $yourmaintainers[$i1]['name']) { + $this->_differentName($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['name'], $mymaintainers[$i2]['name']); + $pass = false; + } + unset($mymaintainers[$i2]); + $mymaintainers = array_values($mymaintainers); + unset($yourmaintainers[$i1]); + $yourmaintainers = array_values($yourmaintainers); + $reset = true; + break; + } + } + if ($reset) { + $i1 = -1; + } + } + $this->_unmatchedMaintainers($mymaintainers, $yourmaintainers); + $filelist = $this->getFilelist(); + foreach ($pf1->getFilelist() as $file => $atts) { + if (!isset($filelist[$file])) { + $this->_missingFile($file); + $pass = false; + } + } + return $pass; + } + + function _differentPackage($package) + { + $this->_stack->push(__FUNCTION__, 'error', array('package' => $package, + 'self' => $this->getPackage()), + 'package.xml 1.0 package "%package%" does not match "%self%"'); + } + + function _differentVersion($version) + { + $this->_stack->push(__FUNCTION__, 'error', array('version' => $version, + 'self' => $this->getVersion()), + 'package.xml 1.0 version "%version%" does not match "%self%"'); + } + + function _differentState($state) + { + $this->_stack->push(__FUNCTION__, 'error', array('state' => $state, + 'self' => $this->getState()), + 'package.xml 1.0 state "%state%" does not match "%self%"'); + } + + function _differentRole($handle, $role, $selfrole) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'role' => $role, 'self' => $selfrole), + 'package.xml 1.0 maintainer "%handle%" role "%role%" does not match "%self%"'); + } + + function _differentEmail($handle, $email, $selfemail) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'email' => $email, 'self' => $selfemail), + 'package.xml 1.0 maintainer "%handle%" email "%email%" does not match "%self%"'); + } + + function _differentName($handle, $name, $selfname) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'name' => $name, 'self' => $selfname), + 'package.xml 1.0 maintainer "%handle%" name "%name%" does not match "%self%"'); + } + + function _unmatchedMaintainers($my, $yours) + { + if ($my) { + array_walk($my, create_function('&$i, $k', '$i = $i["handle"];')); + $this->_stack->push(__FUNCTION__, 'error', array('handles' => $my), + 'package.xml 2.0 has unmatched extra maintainers "%handles%"'); + } + if ($yours) { + array_walk($yours, create_function('&$i, $k', '$i = $i["handle"];')); + $this->_stack->push(__FUNCTION__, 'error', array('handles' => $yours), + 'package.xml 1.0 has unmatched extra maintainers "%handles%"'); + } + } + + function _differentNotes($notes) + { + $truncnotes = strlen($notes) < 25 ? $notes : substr($notes, 0, 24) . '...'; + $truncmynotes = strlen($this->getNotes()) < 25 ? $this->getNotes() : + substr($this->getNotes(), 0, 24) . '...'; + $this->_stack->push(__FUNCTION__, 'error', array('notes' => $truncnotes, + 'self' => $truncmynotes), + 'package.xml 1.0 release notes "%notes%" do not match "%self%"'); + } + + function _differentSummary($summary) + { + $truncsummary = strlen($summary) < 25 ? $summary : substr($summary, 0, 24) . '...'; + $truncmysummary = strlen($this->getsummary()) < 25 ? $this->getSummary() : + substr($this->getsummary(), 0, 24) . '...'; + $this->_stack->push(__FUNCTION__, 'error', array('summary' => $truncsummary, + 'self' => $truncmysummary), + 'package.xml 1.0 summary "%summary%" does not match "%self%"'); + } + + function _differentDescription($description) + { + $truncdescription = trim(strlen($description) < 25 ? $description : substr($description, 0, 24) . '...'); + $truncmydescription = trim(strlen($this->getDescription()) < 25 ? $this->getDescription() : + substr($this->getdescription(), 0, 24) . '...'); + $this->_stack->push(__FUNCTION__, 'error', array('description' => $truncdescription, + 'self' => $truncmydescription), + 'package.xml 1.0 description "%description%" does not match "%self%"'); + } + + function _missingFile($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'package.xml 1.0 file "%file%" is not present in '); + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawState($state) + { + if (!isset($this->_packageInfo['stability'])) { + $this->_packageInfo['stability'] = array(); + } + $this->_packageInfo['stability']['release'] = $state; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawCompatible($compatible) + { + $this->_packageInfo['compatible'] = $compatible; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawPackage($package) + { + $this->_packageInfo['name'] = $package; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawChannel($channel) + { + $this->_packageInfo['channel'] = $channel; + } + + function setRequestedGroup($group) + { + $this->_requestedGroup = $group; + } + + function getRequestedGroup() + { + if (isset($this->_requestedGroup)) { + return $this->_requestedGroup; + } + return false; + } + + /** + * For saving in the registry. + * + * Set the last version that was installed + * @param string + */ + function setLastInstalledVersion($version) + { + $this->_packageInfo['_lastversion'] = $version; + } + + /** + * @return string|false + */ + function getLastInstalledVersion() + { + if (isset($this->_packageInfo['_lastversion'])) { + return $this->_packageInfo['_lastversion']; + } + return false; + } + + /** + * Determines whether this package.xml has post-install scripts or not + * @return array|false + */ + function listPostinstallScripts() + { + $filelist = $this->getFilelist(); + $contents = $this->getContents(); + $contents = $contents['dir']['file']; + if (!is_array($contents) || !isset($contents[0])) { + $contents = array($contents); + } + $taskfiles = array(); + foreach ($contents as $file) { + $atts = $file['attribs']; + unset($file['attribs']); + if (count($file)) { + $taskfiles[$atts['name']] = $file; + } + } + $common = new PEAR_Common; + $common->debug = $this->_config->get('verbose'); + $this->_scripts = array(); + $ret = array(); + foreach ($taskfiles as $name => $tasks) { + if (!isset($filelist[$name])) { + // ignored files will not be in the filelist + continue; + } + $atts = $filelist[$name]; + foreach ($tasks as $tag => $raw) { + $task = $this->getTask($tag); + $task = &new $task($this->_config, $common, PEAR_TASK_INSTALL); + if ($task->isScript()) { + $ret[] = $filelist[$name]['installed_as']; + } + } + } + if (count($ret)) { + return $ret; + } + return false; + } + + /** + * Initialize post-install scripts for running + * + * This method can be used to detect post-install scripts, as the return value + * indicates whether any exist + * @return bool + */ + function initPostinstallScripts() + { + $filelist = $this->getFilelist(); + $contents = $this->getContents(); + $contents = $contents['dir']['file']; + if (!is_array($contents) || !isset($contents[0])) { + $contents = array($contents); + } + $taskfiles = array(); + foreach ($contents as $file) { + $atts = $file['attribs']; + unset($file['attribs']); + if (count($file)) { + $taskfiles[$atts['name']] = $file; + } + } + $common = new PEAR_Common; + $common->debug = $this->_config->get('verbose'); + $this->_scripts = array(); + foreach ($taskfiles as $name => $tasks) { + if (!isset($filelist[$name])) { + // file was not installed due to installconditions + continue; + } + $atts = $filelist[$name]; + foreach ($tasks as $tag => $raw) { + $taskname = $this->getTask($tag); + $task = &new $taskname($this->_config, $common, PEAR_TASK_INSTALL); + if (!$task->isScript()) { + continue; // scripts are only handled after installation + } + $lastversion = isset($this->_packageInfo['_lastversion']) ? + $this->_packageInfo['_lastversion'] : null; + $task->init($raw, $atts, $lastversion); + $res = $task->startSession($this, $atts['installed_as']); + if (!$res) { + continue; // skip this file + } + if (PEAR::isError($res)) { + return $res; + } + $assign = &$task; + $this->_scripts[] = &$assign; + } + } + if (count($this->_scripts)) { + return true; + } + return false; + } + + function runPostinstallScripts() + { + if ($this->initPostinstallScripts()) { + $ui = &PEAR_Frontend::singleton(); + if ($ui) { + $ui->runPostinstallScripts($this->_scripts, $this); + } + } + } + + + /** + * Convert a recursive set of and tags into a single tag with + * tags. + */ + function flattenFilelist() + { + if (isset($this->_packageInfo['bundle'])) { + return; + } + $filelist = array(); + if (isset($this->_packageInfo['contents']['dir']['dir'])) { + $this->_getFlattenedFilelist($filelist, $this->_packageInfo['contents']['dir']); + if (!isset($filelist[1])) { + $filelist = $filelist[0]; + } + $this->_packageInfo['contents']['dir']['file'] = $filelist; + unset($this->_packageInfo['contents']['dir']['dir']); + } else { + // else already flattened but check for baseinstalldir propagation + if (isset($this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'])) { + if (isset($this->_packageInfo['contents']['dir']['file'][0])) { + foreach ($this->_packageInfo['contents']['dir']['file'] as $i => $file) { + if (isset($file['attribs']['baseinstalldir'])) { + continue; + } + $this->_packageInfo['contents']['dir']['file'][$i]['attribs']['baseinstalldir'] + = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir']; + } + } else { + if (!isset($this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'])) { + $this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'] + = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir']; + } + } + } + } + } + + /** + * @param array the final flattened file list + * @param array the current directory being processed + * @param string|false any recursively inherited baeinstalldir attribute + * @param string private recursion variable + * @return array + * @access protected + */ + function _getFlattenedFilelist(&$files, $dir, $baseinstall = false, $path = '') + { + if (isset($dir['attribs']) && isset($dir['attribs']['baseinstalldir'])) { + $baseinstall = $dir['attribs']['baseinstalldir']; + } + if (isset($dir['dir'])) { + if (!isset($dir['dir'][0])) { + $dir['dir'] = array($dir['dir']); + } + foreach ($dir['dir'] as $subdir) { + if (!isset($subdir['attribs']) || !isset($subdir['attribs']['name'])) { + $name = '*unknown*'; + } else { + $name = $subdir['attribs']['name']; + } + $newpath = empty($path) ? $name : + $path . '/' . $name; + $this->_getFlattenedFilelist($files, $subdir, + $baseinstall, $newpath); + } + } + if (isset($dir['file'])) { + if (!isset($dir['file'][0])) { + $dir['file'] = array($dir['file']); + } + foreach ($dir['file'] as $file) { + $attrs = $file['attribs']; + $name = $attrs['name']; + if ($baseinstall && !isset($attrs['baseinstalldir'])) { + $attrs['baseinstalldir'] = $baseinstall; + } + $attrs['name'] = empty($path) ? $name : $path . '/' . $name; + $attrs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attrs['name']); + $file['attribs'] = $attrs; + $files[] = $file; + } + } + } + + function setConfig(&$config) + { + $this->_config = &$config; + $this->_registry = &$config->getRegistry(); + } + + function setLogger(&$logger) + { + if (!is_object($logger) || !method_exists($logger, 'log')) { + return PEAR::raiseError('Logger must be compatible with PEAR_Common::log'); + } + $this->_logger = &$logger; + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setDeps($deps) + { + $this->_packageInfo['dependencies'] = $deps; + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setCompatible($compat) + { + $this->_packageInfo['compatible'] = $compat; + } + + function setPackagefile($file, $archive = false) + { + $this->_packageFile = $file; + $this->_archiveFile = $archive ? $archive : $file; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getValidationWarnings($purge = true) + { + return $this->_stack->getErrors($purge); + } + + function getPackageFile() + { + return $this->_packageFile; + } + + function getArchiveFile() + { + return $this->_archiveFile; + } + + + /** + * Directly set the array that defines this packagefile + * + * WARNING: no validation. This should only be performed by internal methods + * inside PEAR or by inputting an array saved from an existing PEAR_PackageFile_v2 + * @param array + */ + function fromArray($pinfo) + { + unset($pinfo['old']); + unset($pinfo['xsdversion']); + $this->_incomplete = false; + $this->_packageInfo = $pinfo; + } + + function isIncomplete() + { + return $this->_incomplete; + } + + /** + * @return array + */ + function toArray($forreg = false) + { + if (!$this->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + return $this->getArray($forreg); + } + + function getArray($forReg = false) + { + if ($forReg) { + $arr = $this->_packageInfo; + $arr['old'] = array(); + $arr['old']['version'] = $this->getVersion(); + $arr['old']['release_date'] = $this->getDate(); + $arr['old']['release_state'] = $this->getState(); + $arr['old']['release_license'] = $this->getLicense(); + $arr['old']['release_notes'] = $this->getNotes(); + $arr['old']['release_deps'] = $this->getDeps(); + $arr['old']['maintainers'] = $this->getMaintainers(); + $arr['xsdversion'] = '2.0'; + return $arr; + } else { + $info = $this->_packageInfo; + unset($info['dirtree']); + if (isset($info['_lastversion'])) { + unset($info['_lastversion']); + } + if (isset($info['#binarypackage'])) { + unset($info['#binarypackage']); + } + return $info; + } + } + + function packageInfo($field) + { + $arr = $this->getArray(true); + if ($field == 'state') { + return $arr['stability']['release']; + } + if ($field == 'api-version') { + return $arr['version']['api']; + } + if ($field == 'api-state') { + return $arr['stability']['api']; + } + if (isset($arr['old'][$field])) { + if (!is_string($arr['old'][$field])) { + return null; + } + return $arr['old'][$field]; + } + if (isset($arr[$field])) { + if (!is_string($arr[$field])) { + return null; + } + return $arr[$field]; + } + return null; + } + + function getName() + { + return $this->getPackage(); + } + + function getPackage() + { + if (isset($this->_packageInfo['name'])) { + return $this->_packageInfo['name']; + } + return false; + } + + function getChannel() + { + if (isset($this->_packageInfo['uri'])) { + return '__uri'; + } + if (isset($this->_packageInfo['channel'])) { + return strtolower($this->_packageInfo['channel']); + } + return false; + } + + function getUri() + { + if (isset($this->_packageInfo['uri'])) { + return $this->_packageInfo['uri']; + } + return false; + } + + function getExtends() + { + if (isset($this->_packageInfo['extends'])) { + return $this->_packageInfo['extends']; + } + return false; + } + + function getSummary() + { + if (isset($this->_packageInfo['summary'])) { + return $this->_packageInfo['summary']; + } + return false; + } + + function getDescription() + { + if (isset($this->_packageInfo['description'])) { + return $this->_packageInfo['description']; + } + return false; + } + + function getMaintainers($raw = false) + { + if (!isset($this->_packageInfo['lead'])) { + return false; + } + if ($raw) { + $ret = array('lead' => $this->_packageInfo['lead']); + (isset($this->_packageInfo['developer'])) ? + $ret['developer'] = $this->_packageInfo['developer'] :null; + (isset($this->_packageInfo['contributor'])) ? + $ret['contributor'] = $this->_packageInfo['contributor'] :null; + (isset($this->_packageInfo['helper'])) ? + $ret['helper'] = $this->_packageInfo['helper'] :null; + return $ret; + } else { + $ret = array(); + $leads = isset($this->_packageInfo['lead'][0]) ? $this->_packageInfo['lead'] : + array($this->_packageInfo['lead']); + foreach ($leads as $lead) { + $s = $lead; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'lead'; + $ret[] = $s; + } + if (isset($this->_packageInfo['developer'])) { + $leads = isset($this->_packageInfo['developer'][0]) ? + $this->_packageInfo['developer'] : + array($this->_packageInfo['developer']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'developer'; + $ret[] = $s; + } + } + if (isset($this->_packageInfo['contributor'])) { + $leads = isset($this->_packageInfo['contributor'][0]) ? + $this->_packageInfo['contributor'] : + array($this->_packageInfo['contributor']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'contributor'; + $ret[] = $s; + } + } + if (isset($this->_packageInfo['helper'])) { + $leads = isset($this->_packageInfo['helper'][0]) ? + $this->_packageInfo['helper'] : + array($this->_packageInfo['helper']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'helper'; + $ret[] = $s; + } + } + return $ret; + } + return false; + } + + function getLeads() + { + if (isset($this->_packageInfo['lead'])) { + return $this->_packageInfo['lead']; + } + return false; + } + + function getDevelopers() + { + if (isset($this->_packageInfo['developer'])) { + return $this->_packageInfo['developer']; + } + return false; + } + + function getContributors() + { + if (isset($this->_packageInfo['contributor'])) { + return $this->_packageInfo['contributor']; + } + return false; + } + + function getHelpers() + { + if (isset($this->_packageInfo['helper'])) { + return $this->_packageInfo['helper']; + } + return false; + } + + function setDate($date) + { + if (!isset($this->_packageInfo['date'])) { + // ensure that the extends tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', + 'zendextbinrelease', 'bundle', 'changelog'), array(), 'date'); + } + $this->_packageInfo['date'] = $date; + $this->_isValid = 0; + } + + function setTime($time) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['time'])) { + // ensure that the time tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', + 'zendextbinrelease', 'bundle', 'changelog'), $time, 'time'); + } + $this->_packageInfo['time'] = $time; + } + + function getDate() + { + if (isset($this->_packageInfo['date'])) { + return $this->_packageInfo['date']; + } + return false; + } + + function getTime() + { + if (isset($this->_packageInfo['time'])) { + return $this->_packageInfo['time']; + } + return false; + } + + /** + * @param package|api version category to return + */ + function getVersion($key = 'release') + { + if (isset($this->_packageInfo['version'][$key])) { + return $this->_packageInfo['version'][$key]; + } + return false; + } + + function getStability() + { + if (isset($this->_packageInfo['stability'])) { + return $this->_packageInfo['stability']; + } + return false; + } + + function getState($key = 'release') + { + if (isset($this->_packageInfo['stability'][$key])) { + return $this->_packageInfo['stability'][$key]; + } + return false; + } + + function getLicense($raw = false) + { + if (isset($this->_packageInfo['license'])) { + if ($raw) { + return $this->_packageInfo['license']; + } + if (is_array($this->_packageInfo['license'])) { + return $this->_packageInfo['license']['_content']; + } else { + return $this->_packageInfo['license']; + } + } + return false; + } + + function getLicenseLocation() + { + if (!isset($this->_packageInfo['license']) || !is_array($this->_packageInfo['license'])) { + return false; + } + return $this->_packageInfo['license']['attribs']; + } + + function getNotes() + { + if (isset($this->_packageInfo['notes'])) { + return $this->_packageInfo['notes']; + } + return false; + } + + /** + * Return the tag contents, if any + * @return array|false + */ + function getUsesrole() + { + if (isset($this->_packageInfo['usesrole'])) { + return $this->_packageInfo['usesrole']; + } + return false; + } + + /** + * Return the tag contents, if any + * @return array|false + */ + function getUsestask() + { + if (isset($this->_packageInfo['usestask'])) { + return $this->_packageInfo['usestask']; + } + return false; + } + + /** + * This should only be used to retrieve filenames and install attributes + */ + function getFilelist($preserve = false) + { + if (isset($this->_packageInfo['filelist']) && !$preserve) { + return $this->_packageInfo['filelist']; + } + $this->flattenFilelist(); + if ($contents = $this->getContents()) { + $ret = array(); + if (!isset($contents['dir'])) { + return false; + } + if (!isset($contents['dir']['file'][0])) { + $contents['dir']['file'] = array($contents['dir']['file']); + } + foreach ($contents['dir']['file'] as $file) { + $name = $file['attribs']['name']; + if (!$preserve) { + $file = $file['attribs']; + } + $ret[$name] = $file; + } + if (!$preserve) { + $this->_packageInfo['filelist'] = $ret; + } + return $ret; + } + return false; + } + + /** + * Return configure options array, if any + * + * @return array|false + */ + function getConfigureOptions() + { + if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') { + return false; + } + + $releases = $this->getReleases(); + if (isset($releases[0])) { + $releases = $releases[0]; + } + + if (isset($releases['configureoption'])) { + if (!isset($releases['configureoption'][0])) { + $releases['configureoption'] = array($releases['configureoption']); + } + + for ($i = 0; $i < count($releases['configureoption']); $i++) { + $releases['configureoption'][$i] = $releases['configureoption'][$i]['attribs']; + } + + return $releases['configureoption']; + } + + return false; + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function resetFilelist() + { + $this->_packageInfo['filelist'] = array(); + } + + /** + * Retrieve a list of files that should be installed on this computer + * @return array + */ + function getInstallationFilelist($forfilecheck = false) + { + $contents = $this->getFilelist(true); + if (isset($contents['dir']['attribs']['baseinstalldir'])) { + $base = $contents['dir']['attribs']['baseinstalldir']; + } + if (isset($this->_packageInfo['bundle'])) { + return PEAR::raiseError( + 'Exception: bundles should be handled in download code only'); + } + $release = $this->getReleases(); + if ($release) { + if (!isset($release[0])) { + if (!isset($release['installconditions']) && !isset($release['filelist'])) { + if ($forfilecheck) { + return $this->getFilelist(); + } + return $contents; + } + $release = array($release); + } + $depchecker = &$this->getPEARDependency2($this->_config, array(), + array('channel' => $this->getChannel(), 'package' => $this->getPackage()), + PEAR_VALIDATE_INSTALLING); + foreach ($release as $instance) { + if (isset($instance['installconditions'])) { + $installconditions = $instance['installconditions']; + if (is_array($installconditions)) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($installconditions as $type => $conditions) { + if (!isset($conditions[0])) { + $conditions = array($conditions); + } + foreach ($conditions as $condition) { + $ret = $depchecker->{"validate{$type}Dependency"}($condition); + if (PEAR::isError($ret)) { + PEAR::popErrorHandling(); + continue 3; // skip this release + } + } + } + PEAR::popErrorHandling(); + } + } + // this is the release to use + if (isset($instance['filelist'])) { + // ignore files + if (isset($instance['filelist']['ignore'])) { + $ignore = isset($instance['filelist']['ignore'][0]) ? + $instance['filelist']['ignore'] : + array($instance['filelist']['ignore']); + foreach ($ignore as $ig) { + unset ($contents[$ig['attribs']['name']]); + } + } + // install files as this name + if (isset($instance['filelist']['install'])) { + $installas = isset($instance['filelist']['install'][0]) ? + $instance['filelist']['install'] : + array($instance['filelist']['install']); + foreach ($installas as $as) { + $contents[$as['attribs']['name']]['attribs']['install-as'] = + $as['attribs']['as']; + } + } + } + if ($forfilecheck) { + foreach ($contents as $file => $attrs) { + $contents[$file] = $attrs['attribs']; + } + } + return $contents; + } + } else { // simple release - no installconditions or install-as + if ($forfilecheck) { + return $this->getFilelist(); + } + return $contents; + } + // no releases matched + return PEAR::raiseError('No releases in package.xml matched the existing operating ' . + 'system, extensions installed, or architecture, cannot install'); + } + + /** + * This is only used at install-time, after all serialization + * is over. + * @param string file name + * @param string installed path + */ + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + function getInstalledLocation($file) + { + if (isset($this->_packageInfo['filelist'][$file]['installed_as'])) { + return $this->_packageInfo['filelist'][$file]['installed_as']; + } + return false; + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']); + } else { + $this->_packageInfo['filelist'][$file] = $atts['attribs']; + } + } + + /** + * Retrieve the contents tag + */ + function getContents() + { + if (isset($this->_packageInfo['contents'])) { + return $this->_packageInfo['contents']; + } + return false; + } + + /** + * @param string full path to file + * @param string attribute name + * @param string attribute value + * @param int risky but fast - use this to choose a file based on its position in the list + * of files. Index is zero-based like PHP arrays. + * @return bool success of operation + */ + function setFileAttribute($filename, $attr, $value, $index = false) + { + $this->_isValid = 0; + if (in_array($attr, array('role', 'name', 'baseinstalldir'))) { + $this->_filesValid = false; + } + if ($index !== false && + isset($this->_packageInfo['contents']['dir']['file'][$index]['attribs'])) { + $this->_packageInfo['contents']['dir']['file'][$index]['attribs'][$attr] = $value; + return true; + } + if (!isset($this->_packageInfo['contents']['dir']['file'])) { + return false; + } + $files = $this->_packageInfo['contents']['dir']['file']; + if (!isset($files[0])) { + $files = array($files); + $ind = false; + } else { + $ind = true; + } + foreach ($files as $i => $file) { + if (isset($file['attribs'])) { + if ($file['attribs']['name'] == $filename) { + if ($ind) { + $this->_packageInfo['contents']['dir']['file'][$i]['attribs'][$attr] = $value; + } else { + $this->_packageInfo['contents']['dir']['file']['attribs'][$attr] = $value; + } + return true; + } + } + } + return false; + } + + function setDirtree($path) + { + if (!isset($this->_packageInfo['dirtree'])) { + $this->_packageInfo['dirtree'] = array(); + } + $this->_packageInfo['dirtree'][$path] = true; + } + + function getDirtree() + { + if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) { + return $this->_packageInfo['dirtree']; + } + return false; + } + + function resetDirtree() + { + unset($this->_packageInfo['dirtree']); + } + + /** + * Determines whether this package claims it is compatible with the version of + * the package that has a recommended version dependency + * @param PEAR_PackageFile_v2|PEAR_PackageFile_v1|PEAR_Downloader_Package + * @return boolean + */ + function isCompatible($pf) + { + if (!isset($this->_packageInfo['compatible'])) { + return false; + } + if (!isset($this->_packageInfo['channel'])) { + return false; + } + $me = $pf->getVersion(); + $compatible = $this->_packageInfo['compatible']; + if (!isset($compatible[0])) { + $compatible = array($compatible); + } + $found = false; + foreach ($compatible as $info) { + if (strtolower($info['name']) == strtolower($pf->getPackage())) { + if (strtolower($info['channel']) == strtolower($pf->getChannel())) { + $found = true; + break; + } + } + } + if (!$found) { + return false; + } + if (isset($info['exclude'])) { + if (!isset($info['exclude'][0])) { + $info['exclude'] = array($info['exclude']); + } + foreach ($info['exclude'] as $exclude) { + if (version_compare($me, $exclude, '==')) { + return false; + } + } + } + if (version_compare($me, $info['min'], '>=') && version_compare($me, $info['max'], '<=')) { + return true; + } + return false; + } + + /** + * @return array|false + */ + function getCompatible() + { + if (isset($this->_packageInfo['compatible'])) { + return $this->_packageInfo['compatible']; + } + return false; + } + + function getDependencies() + { + if (isset($this->_packageInfo['dependencies'])) { + return $this->_packageInfo['dependencies']; + } + return false; + } + + function isSubpackageOf($p) + { + return $p->isSubpackage($this); + } + + /** + * Determines whether the passed in package is a subpackage of this package. + * + * No version checking is done, only name verification. + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + */ + function isSubpackage($p) + { + $sub = array(); + if (isset($this->_packageInfo['dependencies']['required']['subpackage'])) { + $sub = $this->_packageInfo['dependencies']['required']['subpackage']; + if (!isset($sub[0])) { + $sub = array($sub); + } + } + if (isset($this->_packageInfo['dependencies']['optional']['subpackage'])) { + $sub1 = $this->_packageInfo['dependencies']['optional']['subpackage']; + if (!isset($sub1[0])) { + $sub1 = array($sub1); + } + $sub = array_merge($sub, $sub1); + } + if (isset($this->_packageInfo['dependencies']['group'])) { + $group = $this->_packageInfo['dependencies']['group']; + if (!isset($group[0])) { + $group = array($group); + } + foreach ($group as $deps) { + if (isset($deps['subpackage'])) { + $sub2 = $deps['subpackage']; + if (!isset($sub2[0])) { + $sub2 = array($sub2); + } + $sub = array_merge($sub, $sub2); + } + } + } + foreach ($sub as $dep) { + if (strtolower($dep['name']) == strtolower($p->getPackage())) { + if (isset($dep['channel'])) { + if (strtolower($dep['channel']) == strtolower($p->getChannel())) { + return true; + } + } else { + if ($dep['uri'] == $p->getURI()) { + return true; + } + } + } + } + return false; + } + + function dependsOn($package, $channel) + { + if (!($deps = $this->getDependencies())) { + return false; + } + foreach (array('package', 'subpackage') as $type) { + foreach (array('required', 'optional') as $needed) { + if (isset($deps[$needed][$type])) { + if (!isset($deps[$needed][$type][0])) { + $deps[$needed][$type] = array($deps[$needed][$type]); + } + foreach ($deps[$needed][$type] as $dep) { + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (strtolower($dep['name']) == strtolower($package) && + $depchannel == $channel) { + return true; + } + } + } + } + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $dep['group'] = array($deps['group']); + } + foreach ($deps['group'] as $group) { + if (isset($group[$type])) { + if (!is_array($group[$type])) { + $group[$type] = array($group[$type]); + } + foreach ($group[$type] as $dep) { + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (strtolower($dep['name']) == strtolower($package) && + $depchannel == $channel) { + return true; + } + } + } + } + } + } + return false; + } + + /** + * Get the contents of a dependency group + * @param string + * @return array|false + */ + function getDependencyGroup($name) + { + $name = strtolower($name); + if (!isset($this->_packageInfo['dependencies']['group'])) { + return false; + } + $groups = $this->_packageInfo['dependencies']['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + foreach ($groups as $group) { + if (strtolower($group['attribs']['name']) == $name) { + return $group; + } + } + return false; + } + + /** + * Retrieve a partial package.xml 1.0 representation of dependencies + * + * a very limited representation of dependencies is returned by this method. + * The tag for excluding certain versions of a dependency is + * completely ignored. In addition, dependency groups are ignored, with the + * assumption that all dependencies in dependency groups are also listed in + * the optional group that work with all dependency groups + * @param boolean return package.xml 2.0 tag + * @return array|false + */ + function getDeps($raw = false, $nopearinstaller = false) + { + if (isset($this->_packageInfo['dependencies'])) { + if ($raw) { + return $this->_packageInfo['dependencies']; + } + $ret = array(); + $map = array( + 'php' => 'php', + 'package' => 'pkg', + 'subpackage' => 'pkg', + 'extension' => 'ext', + 'os' => 'os', + 'pearinstaller' => 'pkg', + ); + foreach (array('required', 'optional') as $type) { + $optional = ($type == 'optional') ? 'yes' : 'no'; + if (!isset($this->_packageInfo['dependencies'][$type]) + || empty($this->_packageInfo['dependencies'][$type])) { + continue; + } + foreach ($this->_packageInfo['dependencies'][$type] as $dtype => $deps) { + if ($dtype == 'pearinstaller' && $nopearinstaller) { + continue; + } + if (!isset($deps[0])) { + $deps = array($deps); + } + foreach ($deps as $dep) { + if (!isset($map[$dtype])) { + // no support for arch type + continue; + } + if ($dtype == 'pearinstaller') { + $dep['name'] = 'PEAR'; + $dep['channel'] = 'pear.php.net'; + } + $s = array('type' => $map[$dtype]); + if (isset($dep['channel'])) { + $s['channel'] = $dep['channel']; + } + if (isset($dep['uri'])) { + $s['uri'] = $dep['uri']; + } + if (isset($dep['name'])) { + $s['name'] = $dep['name']; + } + if (isset($dep['conflicts'])) { + $s['rel'] = 'not'; + } else { + if (!isset($dep['min']) && + !isset($dep['max'])) { + $s['rel'] = 'has'; + $s['optional'] = $optional; + } elseif (isset($dep['min']) && + isset($dep['max'])) { + $s['rel'] = 'ge'; + $s1 = $s; + $s1['rel'] = 'le'; + $s['version'] = $dep['min']; + $s1['version'] = $dep['max']; + if (isset($dep['channel'])) { + $s1['channel'] = $dep['channel']; + } + if ($dtype != 'php') { + $s['name'] = $dep['name']; + $s1['name'] = $dep['name']; + } + $s['optional'] = $optional; + $s1['optional'] = $optional; + $ret[] = $s1; + } elseif (isset($dep['min'])) { + if (isset($dep['exclude']) && + $dep['exclude'] == $dep['min']) { + $s['rel'] = 'gt'; + } else { + $s['rel'] = 'ge'; + } + $s['version'] = $dep['min']; + $s['optional'] = $optional; + if ($dtype != 'php') { + $s['name'] = $dep['name']; + } + } elseif (isset($dep['max'])) { + if (isset($dep['exclude']) && + $dep['exclude'] == $dep['max']) { + $s['rel'] = 'lt'; + } else { + $s['rel'] = 'le'; + } + $s['version'] = $dep['max']; + $s['optional'] = $optional; + if ($dtype != 'php') { + $s['name'] = $dep['name']; + } + } + } + $ret[] = $s; + } + } + } + if (count($ret)) { + return $ret; + } + } + return false; + } + + /** + * @return php|extsrc|extbin|zendextsrc|zendextbin|bundle|false + */ + function getPackageType() + { + if (isset($this->_packageInfo['phprelease'])) { + return 'php'; + } + if (isset($this->_packageInfo['extsrcrelease'])) { + return 'extsrc'; + } + if (isset($this->_packageInfo['extbinrelease'])) { + return 'extbin'; + } + if (isset($this->_packageInfo['zendextsrcrelease'])) { + return 'zendextsrc'; + } + if (isset($this->_packageInfo['zendextbinrelease'])) { + return 'zendextbin'; + } + if (isset($this->_packageInfo['bundle'])) { + return 'bundle'; + } + return false; + } + + /** + * @return array|false + */ + function getReleases() + { + $type = $this->getPackageType(); + if ($type != 'bundle') { + $type .= 'release'; + } + if ($this->getPackageType() && isset($this->_packageInfo[$type])) { + return $this->_packageInfo[$type]; + } + return false; + } + + /** + * @return array + */ + function getChangelog() + { + if (isset($this->_packageInfo['changelog'])) { + return $this->_packageInfo['changelog']; + } + return false; + } + + function hasDeps() + { + return isset($this->_packageInfo['dependencies']); + } + + function getPackagexmlVersion() + { + if (isset($this->_packageInfo['zendextsrcrelease'])) { + return '2.1'; + } + if (isset($this->_packageInfo['zendextbinrelease'])) { + return '2.1'; + } + return '2.0'; + } + + /** + * @return array|false + */ + function getSourcePackage() + { + if (isset($this->_packageInfo['extbinrelease']) || + isset($this->_packageInfo['zendextbinrelease'])) { + return array('channel' => $this->_packageInfo['srcchannel'], + 'package' => $this->_packageInfo['srcpackage']); + } + return false; + } + + function getBundledPackages() + { + if (isset($this->_packageInfo['bundle'])) { + return $this->_packageInfo['contents']['bundledpackage']; + } + return false; + } + + function getLastModified() + { + if (isset($this->_packageInfo['_lastmodified'])) { + return $this->_packageInfo['_lastmodified']; + } + return false; + } + + /** + * Get the contents of a file listed within the package.xml + * @param string + * @return string + */ + function getFileContents($file) + { + if ($this->_archiveFile == $this->_packageFile) { // unpacked + $dir = dirname($this->_packageFile); + $file = $dir . DIRECTORY_SEPARATOR . $file; + $file = str_replace(array('/', '\\'), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file); + if (file_exists($file) && is_readable($file)) { + return implode('', file($file)); + } + } else { // tgz + $tar = &new Archive_Tar($this->_archiveFile); + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + if ($file != 'package.xml' && $file != 'package2.xml') { + $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file; + } + $file = $tar->extractInString($file); + $tar->popErrorHandling(); + if (PEAR::isError($file)) { + return PEAR::raiseError("Cannot locate file '$file' in archive"); + } + return $file; + } + } + + function &getRW() + { + if (!class_exists('PEAR_PackageFile_v2_rw')) { + require_once 'PEAR/PackageFile/v2/rw.php'; + } + $a = new PEAR_PackageFile_v2_rw; + foreach (get_object_vars($this) as $name => $unused) { + if (!isset($this->$name)) { + continue; + } + if ($name == '_config' || $name == '_logger'|| $name == '_registry' || + $name == '_stack') { + $a->$name = &$this->$name; + } else { + $a->$name = $this->$name; + } + } + return $a; + } + + function &getDefaultGenerator() + { + if (!class_exists('PEAR_PackageFile_Generator_v2')) { + require_once 'PEAR/PackageFile/Generator/v2.php'; + } + $a = &new PEAR_PackageFile_Generator_v2($this); + return $a; + } + + function analyzeSourceCode($file, $string = false) + { + if (!isset($this->_v2Validator) || + !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'PEAR/PackageFile/v2/Validator.php'; + } + $this->_v2Validator = new PEAR_PackageFile_v2_Validator; + } + return $this->_v2Validator->analyzeSourceCode($file, $string); + } + + function validate($state = PEAR_VALIDATE_NORMAL) + { + if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) { + return false; + } + if (!isset($this->_v2Validator) || + !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'PEAR/PackageFile/v2/Validator.php'; + } + $this->_v2Validator = new PEAR_PackageFile_v2_Validator; + } + if (isset($this->_packageInfo['xsdversion'])) { + unset($this->_packageInfo['xsdversion']); + } + return $this->_v2Validator->validate($this, $state); + } + + function getTasksNs() + { + if (!isset($this->_tasksNs)) { + if (isset($this->_packageInfo['attribs'])) { + foreach ($this->_packageInfo['attribs'] as $name => $value) { + if ($value == 'http://pear.php.net/dtd/tasks-1.0') { + $this->_tasksNs = str_replace('xmlns:', '', $name); + break; + } + } + } + } + return $this->_tasksNs; + } + + /** + * Determine whether a task name is a valid task. Custom tasks may be defined + * using subdirectories by putting a "-" in the name, as in + * + * Note that this method will auto-load the task class file and test for the existence + * of the name with "-" replaced by "_" as in PEAR/Task/mycustom/task.php makes class + * PEAR_Task_mycustom_task + * @param string + * @return boolean + */ + function getTask($task) + { + $this->getTasksNs(); + // transform all '-' to '/' and 'tasks:' to '' so tasks:replace becomes replace + $task = str_replace(array($this->_tasksNs . ':', '-'), array('', ' '), $task); + $taskfile = str_replace(' ', '/', ucwords($task)); + $task = str_replace(array(' ', '/'), '_', ucwords($task)); + if (class_exists("PEAR_Task_$task")) { + return "PEAR_Task_$task"; + } + $fp = @fopen("PEAR/Task/$taskfile.php", 'r', true); + if ($fp) { + fclose($fp); + require_once "PEAR/Task/$taskfile.php"; + return "PEAR_Task_$task"; + } + return false; + } + + /** + * Key-friendly array_splice + * @param tagname to splice a value in before + * @param mixed the value to splice in + * @param string the new tag name + */ + function _ksplice($array, $key, $value, $newkey) + { + $offset = array_search($key, array_keys($array)); + $after = array_slice($array, $offset); + $before = array_slice($array, 0, $offset); + $before[$newkey] = $value; + return array_merge($before, $after); + } + + /** + * @param array a list of possible keys, in the order they may occur + * @param mixed contents of the new package.xml tag + * @param string tag name + * @access private + */ + function _insertBefore($array, $keys, $contents, $newkey) + { + foreach ($keys as $key) { + if (isset($array[$key])) { + return $array = $this->_ksplice($array, $key, $contents, $newkey); + } + } + $array[$newkey] = $contents; + return $array; + } + + /** + * @param subsection of {@link $_packageInfo} + * @param array|string tag contents + * @param array format: + *
    +     * array(
    +     *   tagname => array(list of tag names that follow this one),
    +     *   childtagname => array(list of child tag names that follow this one),
    +     * )
    +     * 
    + * + * This allows construction of nested tags + * @access private + */ + function _mergeTag($manip, $contents, $order) + { + if (count($order)) { + foreach ($order as $tag => $curorder) { + if (!isset($manip[$tag])) { + // ensure that the tag is set up + $manip = $this->_insertBefore($manip, $curorder, array(), $tag); + } + if (count($order) > 1) { + $manip[$tag] = $this->_mergeTag($manip[$tag], $contents, array_slice($order, 1)); + return $manip; + } + } + } else { + return $manip; + } + if (is_array($manip[$tag]) && !empty($manip[$tag]) && isset($manip[$tag][0])) { + $manip[$tag][] = $contents; + } else { + if (!count($manip[$tag])) { + $manip[$tag] = $contents; + } else { + $manip[$tag] = array($manip[$tag]); + $manip[$tag][] = $contents; + } + } + return $manip; + } +} +?> +PEAR-1.9.0/PEAR/REST/10.php100664 764 764 77605 100664 7602 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: 10.php 287558 2009-08-21 22:21:28Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a12 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'PEAR/REST.php'; + +/** + * Implement REST 1.0 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a12 + */ +class PEAR_REST_10 +{ + /** + * @var PEAR_REST + */ + var $_rest; + function PEAR_REST_10($config, $options = array()) + { + $this->_rest = &new PEAR_REST($config, $options); + } + + /** + * Retrieve information about a remote package to be downloaded from a REST server + * + * @param string $base The uri to prepend to all REST calls + * @param array $packageinfo an array of format: + *
    +     *  array(
    +     *   'package' => 'packagename',
    +     *   'channel' => 'channelname',
    +     *  ['state' => 'alpha' (or valid state),]
    +     *  -or-
    +     *  ['version' => '1.whatever']
    +     * 
    + * @param string $prefstate Current preferred_state config variable value + * @param bool $installed the installed version of this package to compare against + * @return array|false|PEAR_Error see {@link _returnDownloadURL()} + */ + function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $packageinfo['channel']; + $package = $packageinfo['package']; + $state = isset($packageinfo['state']) ? $packageinfo['state'] : null; + $version = isset($packageinfo['version']) ? $packageinfo['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('No releases available for package "' . + $channel . '/' . $package . '"'); + } + + if (!isset($info['r'])) { + return false; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + + if (isset($state)) { + // try our preferred state first + if ($release['s'] == $state) { + $found = true; + break; + } + // see if there is something newer and more stable + // bug #7221 + if (in_array($release['s'], $this->betterStates($state), true)) { + $found = true; + break; + } + } elseif (isset($version)) { + if ($release['v'] == $version) { + $found = true; + break; + } + } else { + if (in_array($release['s'], $states)) { + $found = true; + break; + } + } + } + + return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel); + } + + function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage, + $prefstate = 'stable', $installed = false, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $dependency['channel']; + $package = $dependency['name']; + $state = isset($dependency['state']) ? $dependency['state'] : null; + $version = isset($dependency['version']) ? $dependency['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package'] + . '" dependency "' . $channel . '/' . $package . '" has no releases'); + } + + if (!is_array($info) || !isset($info['r'])) { + return false; + } + + $exclude = array(); + $min = $max = $recommended = false; + if ($xsdversion == '1.0') { + switch ($dependency['rel']) { + case 'ge' : + $min = $dependency['version']; + break; + case 'gt' : + $min = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'eq' : + $recommended = $dependency['version']; + break; + case 'lt' : + $max = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'le' : + $max = $dependency['version']; + break; + case 'ne' : + $exclude = array($dependency['version']); + break; + } + } else { + $min = isset($dependency['min']) ? $dependency['min'] : false; + $max = isset($dependency['max']) ? $dependency['max'] : false; + $recommended = isset($dependency['recommended']) ? + $dependency['recommended'] : false; + if (isset($dependency['exclude'])) { + if (!isset($dependency['exclude'][0])) { + $exclude = array($dependency['exclude']); + } + } + } + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + if (in_array($release['v'], $exclude)) { // skip excluded versions + continue; + } + // allow newer releases to say "I'm OK with the dependent package" + if ($xsdversion == '2.0' && isset($release['co'])) { + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + foreach ($release['co'] as $entry) { + if (isset($entry['x']) && !is_array($entry['x'])) { + $entry['x'] = array($entry['x']); + } elseif (!isset($entry['x'])) { + $entry['x'] = array(); + } + if ($entry['c'] == $deppackage['channel'] && + strtolower($entry['p']) == strtolower($deppackage['package']) && + version_compare($deppackage['version'], $entry['min'], '>=') && + version_compare($deppackage['version'], $entry['max'], '<=') && + !in_array($release['v'], $entry['x'])) { + $recommended = $release['v']; + break; + } + } + } + if ($recommended) { + if ($release['v'] != $recommended) { // if we want a specific + // version, then skip all others + continue; + } else { + if (!in_array($release['s'], $states)) { + // the stability is too low, but we must return the + // recommended version if possible + return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel); + } + } + } + if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions + continue; + } + if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions + continue; + } + if ($installed && version_compare($release['v'], $installed, '<')) { + continue; + } + if (in_array($release['s'], $states)) { // if in the preferred state... + $found = true; // ... then use it + break; + } + } + return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel); + } + + /** + * Take raw data and return the array needed for processing a download URL + * + * @param string $base REST base uri + * @param string $package Package name + * @param array $release an array of format array('v' => version, 's' => state) + * describing the release to download + * @param array $info list of all releases as defined by allreleases.xml + * @param bool|null $found determines whether the release was found or this is the next + * best alternative. If null, then versions were skipped because + * of PHP dependency + * @return array|PEAR_Error + * @access private + */ + function _returnDownloadURL($base, $package, $release, $info, $found, $phpversion = false, $channel = false) + { + if (!$found) { + $release = $info['r'][0]; + } + + $packageLower = strtolower($package); + $pinfo = $this->_rest->retrieveCacheFirst($base . 'p/' . $packageLower . '/' . + 'info.xml', false, false, $channel); + if (PEAR::isError($pinfo)) { + return PEAR::raiseError('Package "' . $package . + '" does not have REST info xml available'); + } + + $releaseinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' . + $release['v'] . '.xml', false, false, $channel); + if (PEAR::isError($releaseinfo)) { + return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] . + '" does not have REST xml available'); + } + + $packagexml = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' . + 'deps.' . $release['v'] . '.txt', false, true, $channel); + if (PEAR::isError($packagexml)) { + return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] . + '" does not have REST dependency information available'); + } + + $packagexml = unserialize($packagexml); + if (!$packagexml) { + $packagexml = array(); + } + + $allinfo = $this->_rest->retrieveData($base . 'r/' . $packageLower . + '/allreleases.xml', false, false, $channel); + if (!is_array($allinfo['r']) || !isset($allinfo['r'][0])) { + $allinfo['r'] = array($allinfo['r']); + } + + $compatible = false; + foreach ($allinfo['r'] as $release) { + if ($release['v'] != $releaseinfo['v']) { + continue; + } + + if (!isset($release['co'])) { + break; + } + + $compatible = array(); + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + + foreach ($release['co'] as $entry) { + $comp = array(); + $comp['name'] = $entry['p']; + $comp['channel'] = $entry['c']; + $comp['min'] = $entry['min']; + $comp['max'] = $entry['max']; + if (isset($entry['x']) && !is_array($entry['x'])) { + $comp['exclude'] = $entry['x']; + } + + $compatible[] = $comp; + } + + if (count($compatible) == 1) { + $compatible = $compatible[0]; + } + + break; + } + + $deprecated = false; + if (isset($pinfo['dc']) && isset($pinfo['dp'])) { + if (is_array($pinfo['dp'])) { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp']['_content'])); + } else { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp'])); + } + } + + $return = array( + 'version' => $releaseinfo['v'], + 'info' => $packagexml, + 'package' => $releaseinfo['p']['_content'], + 'stability' => $releaseinfo['st'], + 'compatible' => $compatible, + 'deprecated' => $deprecated, + ); + + if ($found) { + $return['url'] = $releaseinfo['g']; + return $return; + } + + $return['php'] = $phpversion; + return $return; + } + + function listPackages($base, $channel = false) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return array(); + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + return $packagelist['p']; + } + + /** + * List all categories of a REST server + * + * @param string $base base URL of the server + * @return array of categorynames + */ + function listCategories($base, $channel = false) + { + $categories = array(); + + // c/categories.xml does not exist; + // check for every package its category manually + // This is SLOOOWWWW : /// + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + $ret = array(); + return $ret; + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($packagelist['p'] as $package) { + $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($inf)) { + PEAR::popErrorHandling(); + return $inf; + } + $cat = $inf['ca']['_content']; + if (!isset($categories[$cat])) { + $categories[$cat] = $inf['ca']; + } + } + + return array_values($categories); + } + + /** + * List a category of a REST server + * + * @param string $base base URL of the server + * @param string $category name of the category + * @param boolean $info also download full package info + * @return array of packagenames + */ + function listCategory($base, $category, $info = false, $channel = false) + { + // gives '404 Not Found' error when category doesn't exist + $packagelist = $this->_rest->retrieveData($base.'c/'.urlencode($category).'/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return array(); + } + + if (!is_array($packagelist['p']) || + !isset($packagelist['p'][0])) { // only 1 pkg + $packagelist = array($packagelist['p']); + } else { + $packagelist = $packagelist['p']; + } + + if ($info == true) { + // get individual package info + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($packagelist as $i => $packageitem) { + $url = sprintf('%s'.'r/%s/latest.txt', + $base, + strtolower($packageitem['_content'])); + $version = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($version)) { + break; // skipit + } + $url = sprintf('%s'.'r/%s/%s.xml', + $base, + strtolower($packageitem['_content']), + $version); + $info = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($info)) { + break; // skipit + } + $packagelist[$i]['info'] = $info; + } + PEAR::popErrorHandling(); + } + + return $packagelist; + } + + + function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + if ($this->_rest->config->get('verbose') > 0) { + $ui = &PEAR_Frontend::singleton(); + $ui->log('Retrieving data...0%', true); + } + $ret = array(); + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return $ret; + } + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + // only search-packagename = quicksearch ! + if ($searchpackage && (!$searchsummary || empty($searchpackage))) { + $newpackagelist = array(); + foreach ($packagelist['p'] as $package) { + if (!empty($searchpackage) && stristr($package, $searchpackage) !== false) { + $newpackagelist[] = $package; + } + } + $packagelist['p'] = $newpackagelist; + } + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $next = .1; + foreach ($packagelist['p'] as $progress => $package) { + if ($this->_rest->config->get('verbose') > 0) { + if ($progress / count($packagelist['p']) >= $next) { + if ($next == .5) { + $ui->log('50%', false); + } else { + $ui->log('.', false); + } + $next += .1; + } + } + + if ($basic) { // remote-list command + if ($dostable) { + $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/stable.txt', false, false, $channel); + } else { + $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/latest.txt', false, false, $channel); + } + if (PEAR::isError($latest)) { + $latest = false; + } + $info = array('stable' => $latest); + } else { // list-all command + $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($inf)) { + PEAR::popErrorHandling(); + return $inf; + } + if ($searchpackage) { + $found = (!empty($searchpackage) && stristr($package, $searchpackage) !== false); + if (!$found && !(isset($searchsummary) && !empty($searchsummary) + && (stristr($inf['s'], $searchsummary) !== false + || stristr($inf['d'], $searchsummary) !== false))) + { + continue; + }; + } + $releases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + if (PEAR::isError($releases)) { + continue; + } + if (!isset($releases['r'][0])) { + $releases['r'] = array($releases['r']); + } + unset($latest); + unset($unstable); + unset($stable); + unset($state); + foreach ($releases['r'] as $release) { + if (!isset($latest)) { + if ($dostable && $release['s'] == 'stable') { + $latest = $release['v']; + $state = 'stable'; + } + if (!$dostable) { + $latest = $release['v']; + $state = $release['s']; + } + } + if (!isset($stable) && $release['s'] == 'stable') { + $stable = $release['v']; + if (!isset($unstable)) { + $unstable = $stable; + } + } + if (!isset($unstable) && $release['s'] != 'stable') { + $latest = $unstable = $release['v']; + $state = $release['s']; + } + if (isset($latest) && !isset($state)) { + $state = $release['s']; + } + if (isset($latest) && isset($stable) && isset($unstable)) { + break; + } + } + $deps = array(); + if (!isset($unstable)) { + $unstable = false; + $state = 'stable'; + if (isset($stable)) { + $latest = $unstable = $stable; + } + } else { + $latest = $unstable; + } + if (!isset($latest)) { + $latest = false; + } + if ($latest) { + $d = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' . + $latest . '.txt', false, false, $channel); + if (!PEAR::isError($d)) { + $d = unserialize($d); + if ($d) { + if (isset($d['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + if (!isset($pf)) { + $pf = new PEAR_PackageFile_v2; + } + $pf->setDeps($d); + $tdeps = $pf->getDeps(); + } else { + $tdeps = $d; + } + foreach ($tdeps as $dep) { + if ($dep['type'] !== 'pkg') { + continue; + } + $deps[] = $dep; + } + } + } + } + if (!isset($stable)) { + $stable = '-n/a-'; + } + if (!$searchpackage) { + $info = array('stable' => $latest, 'summary' => $inf['s'], 'description' => + $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'], + 'unstable' => $unstable, 'state' => $state); + } else { + $info = array('stable' => $stable, 'summary' => $inf['s'], 'description' => + $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'], + 'unstable' => $unstable, 'state' => $state); + } + } + $ret[$package] = $info; + } + PEAR::popErrorHandling(); + return $ret; + } + + function listLatestUpgrades($base, $pref_state, $installed, $channel, &$reg) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + $ret = array(); + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return $ret; + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + foreach ($packagelist['p'] as $package) { + if (!isset($installed[strtolower($package)])) { + continue; + } + + $inst_version = $reg->packageInfo($package, 'version', $channel); + $inst_state = $reg->packageInfo($package, 'release_state', $channel); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + PEAR::popErrorHandling(); + if (PEAR::isError($info)) { + continue; // no remote releases + } + + if (!isset($info['r'])) { + continue; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + // $info['r'] is sorted by version number + usort($info['r'], array($this, '_sortReleasesByVersionNumber')); + foreach ($info['r'] as $release) { + if ($inst_version && version_compare($release['v'], $inst_version, '<=')) { + // not newer than the one installed + break; + } + + // new version > installed version + if (!$pref_state) { + // every state is a good state + $found = true; + break; + } else { + $new_state = $release['s']; + // if new state >= installed state: go + if (in_array($new_state, $this->betterStates($inst_state, true))) { + $found = true; + break; + } else { + // only allow to lower the state of package, + // if new state >= preferred state: go + if (in_array($new_state, $this->betterStates($pref_state, true))) { + $found = true; + break; + } + } + } + } + + if (!$found) { + continue; + } + + $relinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/' . + $release['v'] . '.xml', false, false, $channel); + if (PEAR::isError($relinfo)) { + return $relinfo; + } + + $ret[$package] = array( + 'version' => $release['v'], + 'state' => $release['s'], + 'filesize' => $relinfo['f'], + ); + } + + return $ret; + } + + function packageInfo($base, $package, $channel = false) + { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pinfo = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($pinfo)) { + PEAR::popErrorHandling(); + return PEAR::raiseError('Unknown package: "' . $package . '" in channel "' . $channel . '"' . "\n". 'Debug: ' . + $pinfo->getMessage()); + } + + $releases = array(); + $allreleases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + if (!PEAR::isError($allreleases)) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + + if (!is_array($allreleases['r']) || !isset($allreleases['r'][0])) { + $allreleases['r'] = array($allreleases['r']); + } + + $pf = new PEAR_PackageFile_v2; + foreach ($allreleases['r'] as $release) { + $ds = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' . + $release['v'] . '.txt', false, false, $channel); + if (PEAR::isError($ds)) { + continue; + } + + if (!isset($latest)) { + $latest = $release['v']; + } + + $pf->setDeps(unserialize($ds)); + $ds = $pf->getDeps(); + $info = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) + . '/' . $release['v'] . '.xml', false, false, $channel); + + if (PEAR::isError($info)) { + continue; + } + + $releases[$release['v']] = array( + 'doneby' => $info['m'], + 'license' => $info['l'], + 'summary' => $info['s'], + 'description' => $info['d'], + 'releasedate' => $info['da'], + 'releasenotes' => $info['n'], + 'state' => $release['s'], + 'deps' => $ds ? $ds : array(), + ); + } + } else { + $latest = ''; + } + + PEAR::popErrorHandling(); + if (isset($pinfo['dc']) && isset($pinfo['dp'])) { + if (is_array($pinfo['dp'])) { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp']['_content'])); + } else { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp'])); + } + } else { + $deprecated = false; + } + + if (!isset($latest)) { + $latest = ''; + } + + return array( + 'name' => $pinfo['n'], + 'channel' => $pinfo['c'], + 'category' => $pinfo['ca']['_content'], + 'stable' => $latest, + 'license' => $pinfo['l'], + 'summary' => $pinfo['s'], + 'description' => $pinfo['d'], + 'releases' => $releases, + 'deprecated' => $deprecated, + ); + } + + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + + if ($include) { + $i--; + } + + return array_slice($states, $i + 1); + } + + /** + * Sort releases by version number + * + * @access private + */ + function _sortReleasesByVersionNumber($a, $b) + { + if (version_compare($a['v'], $b['v'], '=')) { + return 0; + } + + if (version_compare($a['v'], $b['v'], '>')) { + return -1; + } + + if (version_compare($a['v'], $b['v'], '<')) { + return 1; + } + } +}PEAR-1.9.0/PEAR/REST/11.php100664 764 764 26054 100664 7573 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: 11.php 286670 2009-08-02 14:16:06Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.3 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'PEAR/REST.php'; + +/** + * Implement REST 1.1 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.3 + */ +class PEAR_REST_11 +{ + /** + * @var PEAR_REST + */ + var $_rest; + + function PEAR_REST_11($config, $options = array()) + { + $this->_rest = &new PEAR_REST($config, $options); + } + + function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false) + { + $categorylist = $this->_rest->retrieveData($base . 'c/categories.xml', false, false, $channel); + if (PEAR::isError($categorylist)) { + return $categorylist; + } + + $ret = array(); + if (!is_array($categorylist['c']) || !isset($categorylist['c'][0])) { + $categorylist['c'] = array($categorylist['c']); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + + foreach ($categorylist['c'] as $progress => $category) { + $category = $category['_content']; + $packagesinfo = $this->_rest->retrieveData($base . + 'c/' . urlencode($category) . '/packagesinfo.xml', false, false, $channel); + + if (PEAR::isError($packagesinfo)) { + continue; + } + + if (!is_array($packagesinfo) || !isset($packagesinfo['pi'])) { + continue; + } + + if (!is_array($packagesinfo['pi']) || !isset($packagesinfo['pi'][0])) { + $packagesinfo['pi'] = array($packagesinfo['pi']); + } + + foreach ($packagesinfo['pi'] as $packageinfo) { + if (empty($packageinfo)) { + continue; + } + + $info = $packageinfo['p']; + $package = $info['n']; + $releases = isset($packageinfo['a']) ? $packageinfo['a'] : false; + unset($latest); + unset($unstable); + unset($stable); + unset($state); + + if ($releases) { + if (!isset($releases['r'][0])) { + $releases['r'] = array($releases['r']); + } + + foreach ($releases['r'] as $release) { + if (!isset($latest)) { + if ($dostable && $release['s'] == 'stable') { + $latest = $release['v']; + $state = 'stable'; + } + if (!$dostable) { + $latest = $release['v']; + $state = $release['s']; + } + } + + if (!isset($stable) && $release['s'] == 'stable') { + $stable = $release['v']; + if (!isset($unstable)) { + $unstable = $stable; + } + } + + if (!isset($unstable) && $release['s'] != 'stable') { + $unstable = $release['v']; + $state = $release['s']; + } + + if (isset($latest) && !isset($state)) { + $state = $release['s']; + } + + if (isset($latest) && isset($stable) && isset($unstable)) { + break; + } + } + } + + if ($basic) { // remote-list command + if (!isset($latest)) { + $latest = false; + } + + if ($dostable) { + // $state is not set if there are no releases + if (isset($state) && $state == 'stable') { + $ret[$package] = array('stable' => $latest); + } else { + $ret[$package] = array('stable' => '-n/a-'); + } + } else { + $ret[$package] = array('stable' => $latest); + } + + continue; + } + + // list-all command + if (!isset($unstable)) { + $unstable = false; + $state = 'stable'; + if (isset($stable)) { + $latest = $unstable = $stable; + } + } else { + $latest = $unstable; + } + + if (!isset($latest)) { + $latest = false; + } + + $deps = array(); + if ($latest && isset($packageinfo['deps'])) { + if (!is_array($packageinfo['deps']) || + !isset($packageinfo['deps'][0]) + ) { + $packageinfo['deps'] = array($packageinfo['deps']); + } + + $d = false; + foreach ($packageinfo['deps'] as $dep) { + if ($dep['v'] == $latest) { + $d = unserialize($dep['d']); + } + } + + if ($d) { + if (isset($d['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + + if (!isset($pf)) { + $pf = new PEAR_PackageFile_v2; + } + + $pf->setDeps($d); + $tdeps = $pf->getDeps(); + } else { + $tdeps = $d; + } + + foreach ($tdeps as $dep) { + if ($dep['type'] !== 'pkg') { + continue; + } + + $deps[] = $dep; + } + } + } + + $info = array( + 'stable' => $latest, + 'summary' => $info['s'], + 'description' => $info['d'], + 'deps' => $deps, + 'category' => $info['ca']['_content'], + 'unstable' => $unstable, + 'state' => $state + ); + $ret[$package] = $info; + } + } + + PEAR::popErrorHandling(); + return $ret; + } + + /** + * List all categories of a REST server + * + * @param string $base base URL of the server + * @return array of categorynames + */ + function listCategories($base, $channel = false) + { + $categorylist = $this->_rest->retrieveData($base . 'c/categories.xml', false, false, $channel); + if (PEAR::isError($categorylist)) { + return $categorylist; + } + + if (!is_array($categorylist) || !isset($categorylist['c'])) { + return array(); + } + + if (isset($categorylist['c']['_content'])) { + // only 1 category + $categorylist['c'] = array($categorylist['c']); + } + + return $categorylist['c']; + } + + /** + * List packages in a category of a REST server + * + * @param string $base base URL of the server + * @param string $category name of the category + * @param boolean $info also download full package info + * @return array of packagenames + */ + function listCategory($base, $category, $info = false, $channel = false) + { + if ($info == false) { + $url = '%s'.'c/%s/packages.xml'; + } else { + $url = '%s'.'c/%s/packagesinfo.xml'; + } + $url = sprintf($url, + $base, + urlencode($category)); + + // gives '404 Not Found' error when category doesn't exist + $packagelist = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + if (!is_array($packagelist)) { + return array(); + } + + if ($info == false) { + if (!isset($packagelist['p'])) { + return array(); + } + if (!is_array($packagelist['p']) || + !isset($packagelist['p'][0])) { // only 1 pkg + $packagelist = array($packagelist['p']); + } else { + $packagelist = $packagelist['p']; + } + return $packagelist; + } + + // info == true + if (!isset($packagelist['pi'])) { + return array(); + } + + if (!is_array($packagelist['pi']) || + !isset($packagelist['pi'][0])) { // only 1 pkg + $packagelist_pre = array($packagelist['pi']); + } else { + $packagelist_pre = $packagelist['pi']; + } + + $packagelist = array(); + foreach ($packagelist_pre as $i => $item) { + // compatibility with r/.xml + if (isset($item['a']['r'][0])) { + // multiple releases + $item['p']['v'] = $item['a']['r'][0]['v']; + $item['p']['st'] = $item['a']['r'][0]['s']; + } elseif (isset($item['a'])) { + // first and only release + $item['p']['v'] = $item['a']['r']['v']; + $item['p']['st'] = $item['a']['r']['s']; + } + + $packagelist[$i] = array('attribs' => $item['p']['r'], + '_content' => $item['p']['n'], + 'info' => $item['p']); + } + + return $packagelist; + } + + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + if ($include) { + $i--; + } + return array_slice($states, $i + 1); + } +} +?>PEAR-1.9.0/PEAR/REST/13.php100664 764 764 26544 100664 7601 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: 13.php 287110 2009-08-11 18:51:15Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a12 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'PEAR/REST.php'; +require_once 'PEAR/REST/10.php'; + +/** + * Implement REST 1.3 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a12 + */ +class PEAR_REST_13 extends PEAR_REST_10 +{ + /** + * Retrieve information about a remote package to be downloaded from a REST server + * + * This is smart enough to resolve the minimum PHP version dependency prior to download + * @param string $base The uri to prepend to all REST calls + * @param array $packageinfo an array of format: + *
    +     *  array(
    +     *   'package' => 'packagename',
    +     *   'channel' => 'channelname',
    +     *  ['state' => 'alpha' (or valid state),]
    +     *  -or-
    +     *  ['version' => '1.whatever']
    +     * 
    + * @param string $prefstate Current preferred_state config variable value + * @param bool $installed the installed version of this package to compare against + * @return array|false|PEAR_Error see {@link _returnDownloadURL()} + */ + function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $packageinfo['channel']; + $package = $packageinfo['package']; + $state = isset($packageinfo['state']) ? $packageinfo['state'] : null; + $version = isset($packageinfo['version']) ? $packageinfo['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases2.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('No releases available for package "' . + $channel . '/' . $package . '"'); + } + + if (!isset($info['r'])) { + return false; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + $skippedphp = false; + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + + if (isset($state)) { + // try our preferred state first + if ($release['s'] == $state) { + if (!isset($version) && version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + + // see if there is something newer and more stable + // bug #7221 + if (in_array($release['s'], $this->betterStates($state), true)) { + if (!isset($version) && version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + } elseif (isset($version)) { + if ($release['v'] == $version) { + if (!isset($this->_rest->_options['force']) && + !isset($version) && + version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + } else { + if (in_array($release['s'], $states)) { + if (version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + } + } + + if (!$found && $skippedphp) { + $found = null; + } + + return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel); + } + + function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage, + $prefstate = 'stable', $installed = false, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $dependency['channel']; + $package = $dependency['name']; + $state = isset($dependency['state']) ? $dependency['state'] : null; + $version = isset($dependency['version']) ? $dependency['version'] : null; + $restFile = $base . 'r/' . strtolower($package) .'/allreleases2.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package'] + . '" dependency "' . $channel . '/' . $package . '" has no releases'); + } + + if (!is_array($info) || !isset($info['r'])) { + return false; + } + + $exclude = array(); + $min = $max = $recommended = false; + if ($xsdversion == '1.0') { + $pinfo['package'] = $dependency['name']; + $pinfo['channel'] = 'pear.php.net'; // this is always true - don't change this + switch ($dependency['rel']) { + case 'ge' : + $min = $dependency['version']; + break; + case 'gt' : + $min = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'eq' : + $recommended = $dependency['version']; + break; + case 'lt' : + $max = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'le' : + $max = $dependency['version']; + break; + case 'ne' : + $exclude = array($dependency['version']); + break; + } + } else { + $pinfo['package'] = $dependency['name']; + $min = isset($dependency['min']) ? $dependency['min'] : false; + $max = isset($dependency['max']) ? $dependency['max'] : false; + $recommended = isset($dependency['recommended']) ? + $dependency['recommended'] : false; + if (isset($dependency['exclude'])) { + if (!isset($dependency['exclude'][0])) { + $exclude = array($dependency['exclude']); + } + } + } + + $skippedphp = $found = $release = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + + if (in_array($release['v'], $exclude)) { // skip excluded versions + continue; + } + + // allow newer releases to say "I'm OK with the dependent package" + if ($xsdversion == '2.0' && isset($release['co'])) { + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + + foreach ($release['co'] as $entry) { + if (isset($entry['x']) && !is_array($entry['x'])) { + $entry['x'] = array($entry['x']); + } elseif (!isset($entry['x'])) { + $entry['x'] = array(); + } + + if ($entry['c'] == $deppackage['channel'] && + strtolower($entry['p']) == strtolower($deppackage['package']) && + version_compare($deppackage['version'], $entry['min'], '>=') && + version_compare($deppackage['version'], $entry['max'], '<=') && + !in_array($release['v'], $entry['x'])) { + if (version_compare($release['m'], phpversion(), '>')) { + // skip dependency releases that require a PHP version + // newer than our PHP version + $skippedphp = $release; + continue; + } + + $recommended = $release['v']; + break; + } + } + } + + if ($recommended) { + if ($release['v'] != $recommended) { // if we want a specific + // version, then skip all others + continue; + } + + if (!in_array($release['s'], $states)) { + // the stability is too low, but we must return the + // recommended version if possible + return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel); + } + } + + if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions + continue; + } + + if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions + continue; + } + + if ($installed && version_compare($release['v'], $installed, '<')) { + continue; + } + + if (in_array($release['s'], $states)) { // if in the preferred state... + if (version_compare($release['m'], phpversion(), '>')) { + // skip dependency releases that require a PHP version + // newer than our PHP version + $skippedphp = $release; + continue; + } + + $found = true; // ... then use it + break; + } + } + + if (!$found && $skippedphp) { + $found = null; + } + + return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel); + } +}PEAR-1.9.0/PEAR/Task/Postinstallscript/rw.php100664 764 764 13311 100664 13660 - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Postinstallscript.php'; +/** + * Abstracts the postinstallscript file task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Postinstallscript_rw extends PEAR_Task_Postinstallscript +{ + /** + * parent package file object + * + * @var PEAR_PackageFile_v2_rw + */ + var $_pkg; + /** + * Enter description here... + * + * @param PEAR_PackageFile_v2_rw $pkg + * @param PEAR_Config $config + * @param PEAR_Frontend $logger + * @param array $fileXml + * @return PEAR_Task_Postinstallscript_rw + */ + function PEAR_Task_Postinstallscript_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents); + } + + function getName() + { + return 'postinstallscript'; + } + + /** + * add a simple to the post-install script + * + * Order is significant, so call this method in the same + * sequence the users should see the paramgroups. The $params + * parameter should either be the result of a call to {@link getParam()} + * or an array of calls to getParam(). + * + * Use {@link addConditionTypeGroup()} to add a containing + * a tag + * @param string $id id as seen by the script + * @param array|false $params array of getParam() calls, or false for no params + * @param string|false $instructions + */ + function addParamGroup($id, $params = false, $instructions = false) + { + if ($params && isset($params[0]) && !isset($params[1])) { + $params = $params[0]; + } + $stuff = + array( + $this->_pkg->getTasksNs() . ':id' => $id, + ); + if ($instructions) { + $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions; + } + if ($params) { + $stuff[$this->_pkg->getTasksNs() . ':param'] = $params; + } + $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff; + } + + /** + * add a complex to the post-install script with conditions + * + * This inserts a with + * + * Order is significant, so call this method in the same + * sequence the users should see the paramgroups. The $params + * parameter should either be the result of a call to {@link getParam()} + * or an array of calls to getParam(). + * + * Use {@link addParamGroup()} to add a simple + * + * @param string $id id as seen by the script + * @param string $oldgroup id of the section referenced by + * + * @param string $param name of the from the older section referenced + * by + * @param string $value value to match of the parameter + * @param string $conditiontype one of '=', '!=', 'preg_match' + * @param array|false $params array of getParam() calls, or false for no params + * @param string|false $instructions + */ + function addConditionTypeGroup($id, $oldgroup, $param, $value, $conditiontype = '=', + $params = false, $instructions = false) + { + if ($params && isset($params[0]) && !isset($params[1])) { + $params = $params[0]; + } + $stuff = array( + $this->_pkg->getTasksNs() . ':id' => $id, + ); + if ($instructions) { + $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions; + } + $stuff[$this->_pkg->getTasksNs() . ':name'] = $oldgroup . '::' . $param; + $stuff[$this->_pkg->getTasksNs() . ':conditiontype'] = $conditiontype; + $stuff[$this->_pkg->getTasksNs() . ':value'] = $value; + if ($params) { + $stuff[$this->_pkg->getTasksNs() . ':param'] = $params; + } + $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff; + } + + function getXml() + { + return $this->_params; + } + + /** + * Use to set up a param tag for use in creating a paramgroup + * @static + */ + function getParam($name, $prompt, $type = 'string', $default = null) + { + if ($default !== null) { + return + array( + $this->_pkg->getTasksNs() . ':name' => $name, + $this->_pkg->getTasksNs() . ':prompt' => $prompt, + $this->_pkg->getTasksNs() . ':type' => $type, + $this->_pkg->getTasksNs() . ':default' => $default + ); + } + return + array( + $this->_pkg->getTasksNs() . ':name' => $name, + $this->_pkg->getTasksNs() . ':prompt' => $prompt, + $this->_pkg->getTasksNs() . ':type' => $type, + ); + } +} +?>PEAR-1.9.0/PEAR/Task/Replace/rw.php100664 764 764 3115 100664 11453 - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Replace.php'; +/** + * Abstracts the replace task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Replace_rw extends PEAR_Task_Replace +{ + function PEAR_Task_Replace_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents); + } + + function setInfo($from, $to, $type) + { + $this->_params = array('attribs' => array('from' => $from, 'to' => $to, 'type' => $type)); + } + + function getName() + { + return 'replace'; + } + + function getXml() + { + return $this->_params; + } +} +?>PEAR-1.9.0/PEAR/Task/Unixeol/rw.php100664 764 764 2535 100664 11530 - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Unixeol.php'; +/** + * Abstracts the unixeol task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Unixeol_rw extends PEAR_Task_Unixeol +{ + function PEAR_Task_Unixeol_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return true; + } + + function getName() + { + return 'unixeol'; + } + + function getXml() + { + return ''; + } +} +?>PEAR-1.9.0/PEAR/Task/Windowseol/rw.php100664 764 764 2562 100664 12237 - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Windowseol.php'; +/** + * Abstracts the windowseol task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Windowseol_rw extends PEAR_Task_Windowseol +{ + function PEAR_Task_Windowseol_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return true; + } + + function getName() + { + return 'windowseol'; + } + + function getXml() + { + return ''; + } +} +?>PEAR-1.9.0/PEAR/Task/Common.php100664 764 764 13736 100664 10732 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 276394 2009-02-25 00:15:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/**#@+ + * Error codes for task validation routines + */ +define('PEAR_TASK_ERROR_NOATTRIBS', 1); +define('PEAR_TASK_ERROR_MISSING_ATTRIB', 2); +define('PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE', 3); +define('PEAR_TASK_ERROR_INVALID', 4); +/**#@-*/ +define('PEAR_TASK_PACKAGE', 1); +define('PEAR_TASK_INSTALL', 2); +define('PEAR_TASK_PACKAGEANDINSTALL', 3); +/** + * A task is an operation that manipulates the contents of a file. + * + * Simple tasks operate on 1 file. Multiple tasks are executed after all files have been + * processed and installed, and are designed to operate on all files containing the task. + * The Post-install script task simply takes advantage of the fact that it will be run + * after installation, replace is a simple task. + * + * Combining tasks is possible, but ordering is significant. + * + * + * + * + * + * + * This will first replace any instance of @data-dir@ in the test.php file + * with the path to the current data directory. Then, it will include the + * test.php file and run the script it contains to configure the package post-installation. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + * @abstract + */ +class PEAR_Task_Common +{ + /** + * Valid types for this version are 'simple' and 'multiple' + * + * - simple tasks operate on the contents of a file and write out changes to disk + * - multiple tasks operate on the contents of many files and write out the + * changes directly to disk + * + * Child task classes must override this property. + * @access protected + */ + var $type = 'simple'; + /** + * Determines which install phase this task is executed under + */ + var $phase = PEAR_TASK_INSTALL; + /** + * @access protected + */ + var $config; + /** + * @access protected + */ + var $registry; + /** + * @access protected + */ + var $logger; + /** + * @access protected + */ + var $installphase; + /** + * @param PEAR_Config + * @param PEAR_Common + */ + function PEAR_Task_Common(&$config, &$logger, $phase) + { + $this->config = &$config; + $this->registry = &$config->getRegistry(); + $this->logger = &$logger; + $this->installphase = $phase; + if ($this->type == 'multiple') { + $GLOBALS['_PEAR_TASK_POSTINSTANCES'][get_class($this)][] = &$this; + } + } + + /** + * Validate the basic contents of a task tag. + * @param PEAR_PackageFile_v2 + * @param array + * @param PEAR_Config + * @param array the entire parsed tag + * @return true|array On error, return an array in format: + * array(PEAR_TASK_ERROR_???[, param1][, param2][, ...]) + * + * For PEAR_TASK_ERROR_MISSING_ATTRIB, pass the attribute name in + * For PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, pass the attribute name and an array + * of legal values in + * @static + * @abstract + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param array attributes from the tag containing this task + * @param string|null last installed version of this package + * @abstract + */ + function init($xml, $fileAttributes, $lastVersion) + { + } + + /** + * Begin a task processing session. All multiple tasks will be processed after each file + * has been successfully installed, all simple tasks should perform their task here and + * return any errors using the custom throwError() method to allow forward compatibility + * + * This method MUST NOT write out any changes to disk + * @param PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + * @abstract + */ + function startSession($pkg, $contents, $dest) + { + } + + /** + * This method is used to process each of the tasks for a particular multiple class + * type. Simple tasks need not implement this method. + * @param array an array of tasks + * @access protected + * @static + * @abstract + */ + function run($tasks) + { + } + + /** + * @static + * @final + */ + function hasPostinstallTasks() + { + return isset($GLOBALS['_PEAR_TASK_POSTINSTANCES']); + } + + /** + * @static + * @final + */ + function runPostinstallTasks() + { + foreach ($GLOBALS['_PEAR_TASK_POSTINSTANCES'] as $class => $tasks) { + $err = call_user_func(array($class, 'run'), + $GLOBALS['_PEAR_TASK_POSTINSTANCES'][$class]); + if ($err) { + return PEAR_Task_Common::throwError($err); + } + } + unset($GLOBALS['_PEAR_TASK_POSTINSTANCES']); + } + + /** + * Determines whether a role is a script + * @return bool + */ + function isScript() + { + return $this->type == 'script'; + } + + function throwError($msg, $code = -1) + { + include_once 'PEAR.php'; + return PEAR::raiseError($msg, $code); + } +} +?>PEAR-1.9.0/PEAR/Task/Postinstallscript.php100664 764 764 34122 100664 13233 + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Postinstallscript.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the postinstallscript file task. + * + * Note that post-install scripts are handled separately from installation, by the + * "pear run-scripts" command + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Postinstallscript extends PEAR_Task_Common +{ + var $type = 'script'; + var $_class; + var $_params; + var $_obj; + /** + * + * @var PEAR_PackageFile_v2 + */ + var $_pkg; + var $_contents; + var $phase = PEAR_TASK_INSTALL; + + /** + * Validate the raw xml at parsing-time. + * + * This also attempts to validate the script to make sure it meets the criteria + * for a post-install script + * @param PEAR_PackageFile_v2 + * @param array The XML contents of the tag + * @param PEAR_Config + * @param array the entire parsed tag + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($fileXml['role'] != 'php') { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must be role="php"'); + } + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $file = $pkg->getFileContents($fileXml['name']); + if (PEAR::isError($file)) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" is not valid: ' . + $file->getMessage()); + } elseif ($file === null) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" could not be retrieved for processing!'); + } else { + $analysis = $pkg->analyzeSourceCode($file, true); + if (!$analysis) { + PEAR::popErrorHandling(); + $warnings = ''; + foreach ($pkg->getValidationWarnings() as $warn) { + $warnings .= $warn['message'] . "\n"; + } + return array(PEAR_TASK_ERROR_INVALID, 'Analysis of post-install script "' . + $fileXml['name'] . '" failed: ' . $warnings); + } + if (count($analysis['declared_classes']) != 1) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare exactly 1 class'); + } + $class = $analysis['declared_classes'][0]; + if ($class != str_replace(array('/', '.php'), array('_', ''), + $fileXml['name']) . '_postinstall') { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" class "' . $class . '" must be named "' . + str_replace(array('/', '.php'), array('_', ''), + $fileXml['name']) . '_postinstall"'); + } + if (!isset($analysis['declared_methods'][$class])) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare methods init() and run()'); + } + $methods = array('init' => 0, 'run' => 1); + foreach ($analysis['declared_methods'][$class] as $method) { + if (isset($methods[$method])) { + unset($methods[$method]); + } + } + if (count($methods)) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare methods init() and run()'); + } + } + PEAR::popErrorHandling(); + $definedparams = array(); + $tasksNamespace = $pkg->getTasksNs() . ':'; + if (!isset($xml[$tasksNamespace . 'paramgroup']) && isset($xml['paramgroup'])) { + // in order to support the older betas, which did not expect internal tags + // to also use the namespace + $tasksNamespace = ''; + } + if (isset($xml[$tasksNamespace . 'paramgroup'])) { + $params = $xml[$tasksNamespace . 'paramgroup']; + if (!is_array($params) || !isset($params[0])) { + $params = array($params); + } + foreach ($params as $param) { + if (!isset($param[$tasksNamespace . 'id'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must have ' . + 'an ' . $tasksNamespace . 'id> tag'); + } + if (isset($param[$tasksNamespace . 'name'])) { + if (!in_array($param[$tasksNamespace . 'name'], $definedparams)) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" parameter "' . $param[$tasksNamespace . 'name'] . + '" has not been previously defined'); + } + if (!isset($param[$tasksNamespace . 'conditiontype'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'conditiontype> tag containing either "=", ' . + '"!=", or "preg_match"'); + } + if (!in_array($param[$tasksNamespace . 'conditiontype'], + array('=', '!=', 'preg_match'))) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'conditiontype> tag containing either "=", ' . + '"!=", or "preg_match"'); + } + if (!isset($param[$tasksNamespace . 'value'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'value> tag containing expected parameter value'); + } + } + if (isset($param[$tasksNamespace . 'instructions'])) { + if (!is_string($param[$tasksNamespace . 'instructions'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" ' . $tasksNamespace . 'instructions> must be simple text'); + } + } + if (!isset($param[$tasksNamespace . 'param'])) { + continue; // is no longer required + } + $subparams = $param[$tasksNamespace . 'param']; + if (!is_array($subparams) || !isset($subparams[0])) { + $subparams = array($subparams); + } + foreach ($subparams as $subparam) { + if (!isset($subparam[$tasksNamespace . 'name'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter for ' . + $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . '" must have ' . + 'a ' . $tasksNamespace . 'name> tag'); + } + if (!preg_match('/[a-zA-Z0-9]+/', + $subparam[$tasksNamespace . 'name'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" is not a valid name. Must contain only alphanumeric characters'); + } + if (!isset($subparam[$tasksNamespace . 'prompt'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . 'prompt> tag'); + } + if (!isset($subparam[$tasksNamespace . 'type'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . 'type> tag'); + } + $definedparams[] = $param[$tasksNamespace . 'id'] . '::' . + $subparam[$tasksNamespace . 'name']; + } + } + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param array attributes from the tag containing this task + * @param string|null last installed version of this package, if any (useful for upgrades) + */ + function init($xml, $fileattribs, $lastversion) + { + $this->_class = str_replace('/', '_', $fileattribs['name']); + $this->_filename = $fileattribs['name']; + $this->_class = str_replace ('.php', '', $this->_class) . '_postinstall'; + $this->_params = $xml; + $this->_lastversion = $lastversion; + } + + /** + * Strip the tasks: namespace from internal params + * + * @access private + */ + function _stripNamespace($params = null) + { + if ($params === null) { + $params = array(); + if (!is_array($this->_params)) { + return; + } + foreach ($this->_params as $i => $param) { + if (is_array($param)) { + $param = $this->_stripNamespace($param); + } + $params[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param; + } + $this->_params = $params; + } else { + $newparams = array(); + foreach ($params as $i => $param) { + if (is_array($param)) { + $param = $this->_stripNamespace($param); + } + $newparams[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param; + } + return $newparams; + } + } + + /** + * Unlike other tasks, the installed file name is passed in instead of the file contents, + * because this task is handled post-installation + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file name + * @return bool|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError) + */ + function startSession($pkg, $contents) + { + if ($this->installphase != PEAR_TASK_INSTALL) { + return false; + } + // remove the tasks: namespace if present + $this->_pkg = $pkg; + $this->_stripNamespace(); + $this->logger->log(0, 'Including external post-installation script "' . + $contents . '" - any errors are in this script'); + include_once $contents; + if (class_exists($this->_class)) { + $this->logger->log(0, 'Inclusion succeeded'); + } else { + return $this->throwError('init of post-install script class "' . $this->_class + . '" failed'); + } + $this->_obj = new $this->_class; + $this->logger->log(1, 'running post-install script "' . $this->_class . '->init()"'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $res = $this->_obj->init($this->config, $pkg, $this->_lastversion); + PEAR::popErrorHandling(); + if ($res) { + $this->logger->log(0, 'init succeeded'); + } else { + return $this->throwError('init of post-install script "' . $this->_class . + '->init()" failed'); + } + $this->_contents = $contents; + return true; + } + + /** + * No longer used + * @see PEAR_PackageFile_v2::runPostinstallScripts() + * @param array an array of tasks + * @param string install or upgrade + * @access protected + * @static + */ + function run() + { + } +} +?>PEAR-1.9.0/PEAR/Task/Replace.php100664 764 764 15232 100664 11046 + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Replace.php 276394 2009-02-25 00:15:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the replace file task. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Replace extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGEANDINSTALL; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if (!isset($xml['attribs'])) { + return array(PEAR_TASK_ERROR_NOATTRIBS); + } + if (!isset($xml['attribs']['type'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'type'); + } + if (!isset($xml['attribs']['to'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'to'); + } + if (!isset($xml['attribs']['from'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'from'); + } + if ($xml['attribs']['type'] == 'pear-config') { + if (!in_array($xml['attribs']['to'], $config->getKeys())) { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + $config->getKeys()); + } + } elseif ($xml['attribs']['type'] == 'php-const') { + if (defined($xml['attribs']['to'])) { + return true; + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + array('valid PHP constant')); + } + } elseif ($xml['attribs']['type'] == 'package-info') { + if (in_array($xml['attribs']['to'], + array('name', 'summary', 'channel', 'notes', 'extends', 'description', + 'release_notes', 'license', 'release-license', 'license-uri', + 'version', 'api-version', 'state', 'api-state', 'release_date', + 'date', 'time'))) { + return true; + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + array('name', 'summary', 'channel', 'notes', 'extends', 'description', + 'release_notes', 'license', 'release-license', 'license-uri', + 'version', 'api-version', 'state', 'api-state', 'release_date', + 'date', 'time')); + } + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'type', $xml['attribs']['type'], + array('pear-config', 'package-info', 'php-const')); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + $this->_replacements = isset($xml['attribs']) ? array($xml) : $xml; + } + + /** + * Do a package.xml 1.0 replacement, with additional package-info fields available + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $subst_from = $subst_to = array(); + foreach ($this->_replacements as $a) { + $a = $a['attribs']; + $to = ''; + if ($a['type'] == 'pear-config') { + if ($this->installphase == PEAR_TASK_PACKAGE) { + return false; + } + if ($a['to'] == 'master_server') { + $chan = $this->registry->getChannel($pkg->getChannel()); + if (!PEAR::isError($chan)) { + $to = $chan->getServer(); + } else { + $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]"); + return false; + } + } else { + if ($this->config->isDefinedLayer('ftp')) { + // try the remote config file first + $to = $this->config->get($a['to'], 'ftp', $pkg->getChannel()); + if (is_null($to)) { + // then default to local + $to = $this->config->get($a['to'], null, $pkg->getChannel()); + } + } else { + $to = $this->config->get($a['to'], null, $pkg->getChannel()); + } + } + if (is_null($to)) { + $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]"); + return false; + } + } elseif ($a['type'] == 'php-const') { + if ($this->installphase == PEAR_TASK_PACKAGE) { + return false; + } + if (defined($a['to'])) { + $to = constant($a['to']); + } else { + $this->logger->log(0, "$dest: invalid php-const replacement: $a[to]"); + return false; + } + } else { + if ($t = $pkg->packageInfo($a['to'])) { + $to = $t; + } else { + $this->logger->log(0, "$dest: invalid package-info replacement: $a[to]"); + return false; + } + } + if (!is_null($to)) { + $subst_from[] = $a['from']; + $subst_to[] = $to; + } + } + $this->logger->log(3, "doing " . sizeof($subst_from) . + " substitution(s) for $dest"); + if (sizeof($subst_from)) { + $contents = str_replace($subst_from, $subst_to, $contents); + } + return $contents; + } +} +?>PEAR-1.9.0/PEAR/Task/Unixeol.php100664 764 764 4341 100664 11075 + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Unixeol.php 276394 2009-02-25 00:15:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the unix line endings file task. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Unixeol extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGE; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($xml != '') { + return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed'); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + } + + /** + * Replace all line endings with line endings customized for the current OS + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $this->logger->log(3, "replacing all line endings with \\n in $dest"); + return preg_replace("/\r\n|\n\r|\r|\n/", "\n", $contents); + } +} +?>PEAR-1.9.0/PEAR/Task/Windowseol.php100664 764 764 4335 100664 11607 + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Windowseol.php 276394 2009-02-25 00:15:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the windows line endsings file task. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Windowseol extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGE; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($xml != '') { + return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed'); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + } + + /** + * Replace all line endings with windows line endings + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $this->logger->log(3, "replacing all line endings with \\r\\n in $dest"); + return preg_replace("/\r\n|\n\r|\r|\n/", "\r\n", $contents); + } +} +?>PEAR-1.9.0/PEAR/Validator/PECL.php100664 764 764 4175 100664 11225 + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: PECL.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a5 + */ +/** + * This is the parent class for all validators + */ +require_once 'PEAR/Validate.php'; +/** + * Channel Validator for the pecl.php.net channel + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a5 + */ +class PEAR_Validator_PECL extends PEAR_Validate +{ + function validateVersion() + { + if ($this->_state == PEAR_VALIDATE_PACKAGING) { + $version = $this->_packagexml->getVersion(); + $versioncomponents = explode('.', $version); + $last = array_pop($versioncomponents); + if (substr($last, 1, 2) == 'rc') { + $this->_addFailure('version', 'Release Candidate versions must have ' . + 'upper-case RC, not lower-case rc'); + return false; + } + } + return true; + } + + function validatePackageName() + { + $ret = parent::validatePackageName(); + if ($this->_packagexml->getPackageType() == 'extsrc' || + $this->_packagexml->getPackageType() == 'zendextsrc') { + if (strtolower($this->_packagexml->getPackage()) != + strtolower($this->_packagexml->getProvidesExtension())) { + $this->_addWarning('providesextension', 'package name "' . + $this->_packagexml->getPackage() . '" is different from extension name "' . + $this->_packagexml->getProvidesExtension() . '"'); + } + } + return $ret; + } +} +?>PEAR-1.9.0/PEAR/Autoloader.php100664 764 764 14645 100664 10677 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Autoloader.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader + * @since File available since Release 0.1 + * @deprecated File deprecated in Release 1.4.0a1 + */ + +// /* vim: set expandtab tabstop=4 shiftwidth=4: */ + +if (!extension_loaded("overload")) { + // die hard without ext/overload + die("Rebuild PHP with the `overload' extension to use PEAR_Autoloader"); +} + +/** + * Include for PEAR_Error and PEAR classes + */ +require_once "PEAR.php"; + +/** + * This class is for objects where you want to separate the code for + * some methods into separate classes. This is useful if you have a + * class with not-frequently-used methods that contain lots of code + * that you would like to avoid always parsing. + * + * The PEAR_Autoloader class provides autoloading and aggregation. + * The autoloading lets you set up in which classes the separated + * methods are found. Aggregation is the technique used to import new + * methods, an instance of each class providing separated methods is + * stored and called every time the aggregated method is called. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader + * @since File available since Release 0.1 + * @deprecated File deprecated in Release 1.4.0a1 + */ +class PEAR_Autoloader extends PEAR +{ + // {{{ properties + + /** + * Map of methods and classes where they are defined + * + * @var array + * + * @access private + */ + var $_autoload_map = array(); + + /** + * Map of methods and aggregate objects + * + * @var array + * + * @access private + */ + var $_method_map = array(); + + // }}} + // {{{ addAutoload() + + /** + * Add one or more autoload entries. + * + * @param string $method which method to autoload + * + * @param string $classname (optional) which class to find the method in. + * If the $method parameter is an array, this + * parameter may be omitted (and will be ignored + * if not), and the $method parameter will be + * treated as an associative array with method + * names as keys and class names as values. + * + * @return void + * + * @access public + */ + function addAutoload($method, $classname = null) + { + if (is_array($method)) { + array_walk($method, create_function('$a,&$b', '$b = strtolower($b);')); + $this->_autoload_map = array_merge($this->_autoload_map, $method); + } else { + $this->_autoload_map[strtolower($method)] = $classname; + } + } + + // }}} + // {{{ removeAutoload() + + /** + * Remove an autoload entry. + * + * @param string $method which method to remove the autoload entry for + * + * @return bool TRUE if an entry was removed, FALSE if not + * + * @access public + */ + function removeAutoload($method) + { + $method = strtolower($method); + $ok = isset($this->_autoload_map[$method]); + unset($this->_autoload_map[$method]); + return $ok; + } + + // }}} + // {{{ addAggregateObject() + + /** + * Add an aggregate object to this object. If the specified class + * is not defined, loading it will be attempted following PEAR's + * file naming scheme. All the methods in the class will be + * aggregated, except private ones (name starting with an + * underscore) and constructors. + * + * @param string $classname what class to instantiate for the object. + * + * @return void + * + * @access public + */ + function addAggregateObject($classname) + { + $classname = strtolower($classname); + if (!class_exists($classname)) { + $include_file = preg_replace('/[^a-z0-9]/i', '_', $classname); + include_once $include_file; + } + $obj =& new $classname; + $methods = get_class_methods($classname); + foreach ($methods as $method) { + // don't import priviate methods and constructors + if ($method{0} != '_' && $method != $classname) { + $this->_method_map[$method] = $obj; + } + } + } + + // }}} + // {{{ removeAggregateObject() + + /** + * Remove an aggregate object. + * + * @param string $classname the class of the object to remove + * + * @return bool TRUE if an object was removed, FALSE if not + * + * @access public + */ + function removeAggregateObject($classname) + { + $ok = false; + $classname = strtolower($classname); + reset($this->_method_map); + while (list($method, $obj) = each($this->_method_map)) { + if (is_a($obj, $classname)) { + unset($this->_method_map[$method]); + $ok = true; + } + } + return $ok; + } + + // }}} + // {{{ __call() + + /** + * Overloaded object call handler, called each time an + * undefined/aggregated method is invoked. This method repeats + * the call in the right aggregate object and passes on the return + * value. + * + * @param string $method which method that was called + * + * @param string $args An array of the parameters passed in the + * original call + * + * @return mixed The return value from the aggregated method, or a PEAR + * error if the called method was unknown. + */ + function __call($method, $args, &$retval) + { + $method = strtolower($method); + if (empty($this->_method_map[$method]) && isset($this->_autoload_map[$method])) { + $this->addAggregateObject($this->_autoload_map[$method]); + } + if (isset($this->_method_map[$method])) { + $retval = call_user_func_array(array($this->_method_map[$method], $method), $args); + return true; + } + return false; + } + + // }}} +} + +overload("PEAR_Autoloader"); + +?> +PEAR-1.9.0/PEAR/Builder.php100664 764 764 40120 100664 10151 + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Builder.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + * + * TODO: log output parameters in PECL command line + * TODO: msdev path in configuration + */ + +/** + * Needed for extending PEAR_Builder + */ +require_once 'PEAR/Common.php'; +require_once 'PEAR/PackageFile.php'; + +/** + * Class to handle building (compiling) extensions. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since PHP 4.0.2 + * @see http://pear.php.net/manual/en/core.ppm.pear-builder.php + */ +class PEAR_Builder extends PEAR_Common +{ + var $php_api_version = 0; + var $zend_module_api_no = 0; + var $zend_extension_api_no = 0; + + var $extensions_built = array(); + + /** + * @var string Used for reporting when it is not possible to pass function + * via extra parameter, e.g. log, msdevCallback + */ + var $current_callback = null; + + // used for msdev builds + var $_lastline = null; + var $_firstline = null; + + /** + * PEAR_Builder constructor. + * + * @param object $ui user interface object (instance of PEAR_Frontend_*) + * + * @access public + */ + function PEAR_Builder(&$ui) + { + parent::PEAR_Common(); + $this->setFrontendObject($ui); + } + + /** + * Build an extension from source on windows. + * requires msdev + */ + function _build_win32($descfile, $callback = null) + { + if (is_object($descfile)) { + $pkg = $descfile; + $descfile = $pkg->getPackageFile(); + } else { + $pf = &new PEAR_PackageFile($this->config, $this->debug); + $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($pkg)) { + return $pkg; + } + } + $dir = dirname($descfile); + $old_cwd = getcwd(); + + if (!file_exists($dir) || !is_dir($dir) || !chdir($dir)) { + return $this->raiseError("could not chdir to $dir"); + } + + // packages that were in a .tar have the packagefile in this directory + $vdir = $pkg->getPackage() . '-' . $pkg->getVersion(); + if (file_exists($dir) && is_dir($vdir)) { + if (!chdir($vdir)) { + return $this->raiseError("could not chdir to " . realpath($vdir)); + } + + $dir = getcwd(); + } + + $this->log(2, "building in $dir"); + + $dsp = $pkg->getPackage().'.dsp'; + if (!file_exists("$dir/$dsp")) { + return $this->raiseError("The DSP $dsp does not exist."); + } + // XXX TODO: make release build type configurable + $command = 'msdev '.$dsp.' /MAKE "'.$pkg->getPackage(). ' - Release"'; + + $err = $this->_runCommand($command, array(&$this, 'msdevCallback')); + if (PEAR::isError($err)) { + return $err; + } + + // figure out the build platform and type + $platform = 'Win32'; + $buildtype = 'Release'; + if (preg_match('/.*?'.$pkg->getPackage().'\s-\s(\w+)\s(.*?)-+/i',$this->_firstline,$matches)) { + $platform = $matches[1]; + $buildtype = $matches[2]; + } + + if (preg_match('/(.*)?\s-\s(\d+).*?(\d+)/', $this->_lastline, $matches)) { + if ($matches[2]) { + // there were errors in the build + return $this->raiseError("There were errors during compilation."); + } + $out = $matches[1]; + } else { + return $this->raiseError("Did not understand the completion status returned from msdev.exe."); + } + + // msdev doesn't tell us the output directory :/ + // open the dsp, find /out and use that directory + $dsptext = join(file($dsp),''); + + // this regex depends on the build platform and type having been + // correctly identified above. + $regex ='/.*?!IF\s+"\$\(CFG\)"\s+==\s+("'. + $pkg->getPackage().'\s-\s'. + $platform.'\s'. + $buildtype.'").*?'. + '\/out:"(.*?)"/is'; + + if ($dsptext && preg_match($regex, $dsptext, $matches)) { + // what we get back is a relative path to the output file itself. + $outfile = realpath($matches[2]); + } else { + return $this->raiseError("Could not retrieve output information from $dsp."); + } + // realpath returns false if the file doesn't exist + if ($outfile && copy($outfile, "$dir/$out")) { + $outfile = "$dir/$out"; + } + + $built_files[] = array( + 'file' => "$outfile", + 'php_api' => $this->php_api_version, + 'zend_mod_api' => $this->zend_module_api_no, + 'zend_ext_api' => $this->zend_extension_api_no, + ); + + return $built_files; + } + // }}} + + // {{{ msdevCallback() + function msdevCallback($what, $data) + { + if (!$this->_firstline) + $this->_firstline = $data; + $this->_lastline = $data; + call_user_func($this->current_callback, $what, $data); + } + + /** + * @param string + * @param string + * @param array + * @access private + */ + function _harvestInstDir($dest_prefix, $dirname, &$built_files) + { + $d = opendir($dirname); + if (!$d) + return false; + + $ret = true; + while (($ent = readdir($d)) !== false) { + if ($ent{0} == '.') + continue; + + $full = $dirname . DIRECTORY_SEPARATOR . $ent; + if (is_dir($full)) { + if (!$this->_harvestInstDir( + $dest_prefix . DIRECTORY_SEPARATOR . $ent, + $full, $built_files)) { + $ret = false; + break; + } + } else { + $dest = $dest_prefix . DIRECTORY_SEPARATOR . $ent; + $built_files[] = array( + 'file' => $full, + 'dest' => $dest, + 'php_api' => $this->php_api_version, + 'zend_mod_api' => $this->zend_module_api_no, + 'zend_ext_api' => $this->zend_extension_api_no, + ); + } + } + closedir($d); + return $ret; + } + + /** + * Build an extension from source. Runs "phpize" in the source + * directory, but compiles in a temporary directory + * (/var/tmp/pear-build-USER/PACKAGE-VERSION). + * + * @param string|PEAR_PackageFile_v* $descfile path to XML package description file, or + * a PEAR_PackageFile object + * + * @param mixed $callback callback function used to report output, + * see PEAR_Builder::_runCommand for details + * + * @return array an array of associative arrays with built files, + * format: + * array( array( 'file' => '/path/to/ext.so', + * 'php_api' => YYYYMMDD, + * 'zend_mod_api' => YYYYMMDD, + * 'zend_ext_api' => YYYYMMDD ), + * ... ) + * + * @access public + * + * @see PEAR_Builder::_runCommand + */ + function build($descfile, $callback = null) + { + if (preg_match('/(\\/|\\\\|^)([^\\/\\\\]+)?php(.+)?$/', + $this->config->get('php_bin'), $matches)) { + if (isset($matches[2]) && strlen($matches[2]) && + trim($matches[2]) != trim($this->config->get('php_prefix'))) { + $this->log(0, 'WARNING: php_bin ' . $this->config->get('php_bin') . + ' appears to have a prefix ' . $matches[2] . ', but' . + ' config variable php_prefix does not match'); + } + if (isset($matches[3]) && strlen($matches[3]) && + trim($matches[3]) != trim($this->config->get('php_suffix'))) { + $this->log(0, 'WARNING: php_bin ' . $this->config->get('php_bin') . + ' appears to have a suffix ' . $matches[3] . ', but' . + ' config variable php_suffix does not match'); + } + } + + + $this->current_callback = $callback; + if (PEAR_OS == "Windows") { + return $this->_build_win32($descfile, $callback); + } + if (PEAR_OS != 'Unix') { + return $this->raiseError("building extensions not supported on this platform"); + } + if (is_object($descfile)) { + $pkg = $descfile; + $descfile = $pkg->getPackageFile(); + if (is_a($pkg, 'PEAR_PackageFile_v1')) { + $dir = dirname($descfile); + } else { + $dir = $pkg->_config->get('temp_dir') . '/' . $pkg->getName(); + // automatically delete at session end + $this->addTempFile($dir); + } + } else { + $pf = &new PEAR_PackageFile($this->config); + $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($pkg)) { + return $pkg; + } + $dir = dirname($descfile); + } + $old_cwd = getcwd(); + if (!file_exists($dir) || !is_dir($dir) || !chdir($dir)) { + return $this->raiseError("could not chdir to $dir"); + } + $vdir = $pkg->getPackage() . '-' . $pkg->getVersion(); + if (is_dir($vdir)) { + chdir($vdir); + } + $dir = getcwd(); + $this->log(2, "building in $dir"); + putenv('PATH=' . $this->config->get('bin_dir') . ':' . getenv('PATH')); + $err = $this->_runCommand($this->config->get('php_prefix') + . "phpize" . + $this->config->get('php_suffix'), + array(&$this, 'phpizeCallback')); + if (PEAR::isError($err)) { + return $err; + } + if (!$err) { + return $this->raiseError("`phpize' failed"); + } + + // {{{ start of interactive part + $configure_command = "$dir/configure"; + $configure_options = $pkg->getConfigureOptions(); + if ($configure_options) { + foreach ($configure_options as $o) { + $default = array_key_exists('default', $o) ? $o['default'] : null; + list($r) = $this->ui->userDialog('build', + array($o['prompt']), + array('text'), + array($default)); + if (substr($o['name'], 0, 5) == 'with-' && + ($r == 'yes' || $r == 'autodetect')) { + $configure_command .= " --$o[name]"; + } else { + $configure_command .= " --$o[name]=".trim($r); + } + } + } + // }}} end of interactive part + + // FIXME make configurable + if(!$user=getenv('USER')){ + $user='defaultuser'; + } + $build_basedir = "/var/tmp/pear-build-$user"; + $build_dir = "$build_basedir/$vdir"; + $inst_dir = "$build_basedir/install-$vdir"; + $this->log(1, "building in $build_dir"); + if (is_dir($build_dir)) { + System::rm(array('-rf', $build_dir)); + } + if (!System::mkDir(array('-p', $build_dir))) { + return $this->raiseError("could not create build dir: $build_dir"); + } + $this->addTempFile($build_dir); + if (!System::mkDir(array('-p', $inst_dir))) { + return $this->raiseError("could not create temporary install dir: $inst_dir"); + } + $this->addTempFile($inst_dir); + + if (getenv('MAKE')) { + $make_command = getenv('MAKE'); + } else { + $make_command = 'make'; + } + $to_run = array( + $configure_command, + $make_command, + "$make_command INSTALL_ROOT=\"$inst_dir\" install", + "find \"$inst_dir\" | xargs ls -dils" + ); + if (!file_exists($build_dir) || !is_dir($build_dir) || !chdir($build_dir)) { + return $this->raiseError("could not chdir to $build_dir"); + } + putenv('PHP_PEAR_VERSION=1.9.0'); + foreach ($to_run as $cmd) { + $err = $this->_runCommand($cmd, $callback); + if (PEAR::isError($err)) { + chdir($old_cwd); + return $err; + } + if (!$err) { + chdir($old_cwd); + return $this->raiseError("`$cmd' failed"); + } + } + if (!($dp = opendir("modules"))) { + chdir($old_cwd); + return $this->raiseError("no `modules' directory found"); + } + $built_files = array(); + $prefix = exec($this->config->get('php_prefix') + . "php-config" . + $this->config->get('php_suffix') . " --prefix"); + $this->_harvestInstDir($prefix, $inst_dir . DIRECTORY_SEPARATOR . $prefix, $built_files); + chdir($old_cwd); + return $built_files; + } + + /** + * Message callback function used when running the "phpize" + * program. Extracts the API numbers used. Ignores other message + * types than "cmdoutput". + * + * @param string $what the type of message + * @param mixed $data the message + * + * @return void + * + * @access public + */ + function phpizeCallback($what, $data) + { + if ($what != 'cmdoutput') { + return; + } + $this->log(1, rtrim($data)); + if (preg_match('/You should update your .aclocal.m4/', $data)) { + return; + } + $matches = array(); + if (preg_match('/^\s+(\S[^:]+):\s+(\d{8})/', $data, $matches)) { + $member = preg_replace('/[^a-z]/', '_', strtolower($matches[1])); + $apino = (int)$matches[2]; + if (isset($this->$member)) { + $this->$member = $apino; + //$msg = sprintf("%-22s : %d", $matches[1], $apino); + //$this->log(1, $msg); + } + } + } + + /** + * Run an external command, using a message callback to report + * output. The command will be run through popen and output is + * reported for every line with a "cmdoutput" message with the + * line string, including newlines, as payload. + * + * @param string $command the command to run + * + * @param mixed $callback (optional) function to use as message + * callback + * + * @return bool whether the command was successful (exit code 0 + * means success, any other means failure) + * + * @access private + */ + function _runCommand($command, $callback = null) + { + $this->log(1, "running: $command"); + $pp = popen("$command 2>&1", "r"); + if (!$pp) { + return $this->raiseError("failed to run `$command'"); + } + if ($callback && $callback[0]->debug == 1) { + $olddbg = $callback[0]->debug; + $callback[0]->debug = 2; + } + + while ($line = fgets($pp, 1024)) { + if ($callback) { + call_user_func($callback, 'cmdoutput', $line); + } else { + $this->log(2, rtrim($line)); + } + } + if ($callback && isset($olddbg)) { + $callback[0]->debug = $olddbg; + } + + $exitcode = is_resource($pp) ? pclose($pp) : -1; + return ($exitcode == 0); + } + + function log($level, $msg) + { + if ($this->current_callback) { + if ($this->debug >= $level) { + call_user_func($this->current_callback, 'output', $msg); + } + return; + } + return PEAR_Common::log($level, $msg); + } +}PEAR-1.9.0/PEAR/ChannelFile.php100664 764 764 143353 100664 10767 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: ChannelFile.php 286951 2009-08-09 14:41:22Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Needed for error handling + */ +require_once 'PEAR/ErrorStack.php'; +require_once 'PEAR/XMLParser.php'; +require_once 'PEAR/Common.php'; + +/** + * Error code if the channel.xml tag does not contain a valid version + */ +define('PEAR_CHANNELFILE_ERROR_NO_VERSION', 1); +/** + * Error code if the channel.xml tag version is not supported (version 1.0 is the only supported version, + * currently + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_VERSION', 2); + +/** + * Error code if parsing is attempted with no xml extension + */ +define('PEAR_CHANNELFILE_ERROR_NO_XML_EXT', 3); + +/** + * Error code if creating the xml parser resource fails + */ +define('PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER', 4); + +/** + * Error code used for all sax xml parsing errors + */ +define('PEAR_CHANNELFILE_ERROR_PARSER_ERROR', 5); + +/**#@+ + * Validation errors + */ +/** + * Error code when channel name is missing + */ +define('PEAR_CHANNELFILE_ERROR_NO_NAME', 6); +/** + * Error code when channel name is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_NAME', 7); +/** + * Error code when channel summary is missing + */ +define('PEAR_CHANNELFILE_ERROR_NO_SUMMARY', 8); +/** + * Error code when channel summary is multi-line + */ +define('PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY', 9); +/** + * Error code when channel server is missing for protocol + */ +define('PEAR_CHANNELFILE_ERROR_NO_HOST', 10); +/** + * Error code when channel server is invalid for protocol + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_HOST', 11); +/** + * Error code when a mirror name is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_MIRROR', 21); +/** + * Error code when a mirror type is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE', 22); +/** + * Error code when an attempt is made to generate xml, but the parsed content is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID', 23); +/** + * Error code when an empty package name validate regex is passed in + */ +define('PEAR_CHANNELFILE_ERROR_EMPTY_REGEX', 24); +/** + * Error code when a tag has no version + */ +define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION', 25); +/** + * Error code when a tag has no name + */ +define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME', 26); +/** + * Error code when a tag has no name + */ +define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME', 27); +/** + * Error code when a tag has no version attribute + */ +define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION', 28); +/** + * Error code when a mirror does not exist but is called for in one of the set* + * methods. + */ +define('PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND', 32); +/** + * Error code when a server port is not numeric + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_PORT', 33); +/** + * Error code when contains no version attribute + */ +define('PEAR_CHANNELFILE_ERROR_NO_STATICVERSION', 34); +/** + * Error code when contains no type attribute in a protocol definition + */ +define('PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE', 35); +/** + * Error code when a mirror is defined and the channel.xml represents the __uri pseudo-channel + */ +define('PEAR_CHANNELFILE_URI_CANT_MIRROR', 36); +/** + * Error code when ssl attribute is present and is not "yes" + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_SSL', 37); +/**#@-*/ + +/** + * Mirror types allowed. Currently only internet servers are recognized. + */ +$GLOBALS['_PEAR_CHANNELS_MIRROR_TYPES'] = array('server'); + + +/** + * The Channel handling class + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_ChannelFile +{ + /** + * @access private + * @var PEAR_ErrorStack + * @access private + */ + var $_stack; + + /** + * Supported channel.xml versions, for parsing + * @var array + * @access private + */ + var $_supportedVersions = array('1.0'); + + /** + * Parsed channel information + * @var array + * @access private + */ + var $_channelInfo; + + /** + * index into the subchannels array, used for parsing xml + * @var int + * @access private + */ + var $_subchannelIndex; + + /** + * index into the mirrors array, used for parsing xml + * @var int + * @access private + */ + var $_mirrorIndex; + + /** + * Flag used to determine the validity of parsed content + * @var boolean + * @access private + */ + var $_isValid = false; + + function PEAR_ChannelFile() + { + $this->_stack = &new PEAR_ErrorStack('PEAR_ChannelFile'); + $this->_stack->setErrorMessageTemplate($this->_getErrorMessage()); + $this->_isValid = false; + } + + /** + * @return array + * @access protected + */ + function _getErrorMessage() + { + return + array( + PEAR_CHANNELFILE_ERROR_INVALID_VERSION => + 'While parsing channel.xml, an invalid version number "%version% was passed in, expecting one of %versions%', + PEAR_CHANNELFILE_ERROR_NO_VERSION => + 'No version number found in tag', + PEAR_CHANNELFILE_ERROR_NO_XML_EXT => + '%error%', + PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER => + 'Unable to create XML parser', + PEAR_CHANNELFILE_ERROR_PARSER_ERROR => + '%error%', + PEAR_CHANNELFILE_ERROR_NO_NAME => + 'Missing channel name', + PEAR_CHANNELFILE_ERROR_INVALID_NAME => + 'Invalid channel %tag% "%name%"', + PEAR_CHANNELFILE_ERROR_NO_SUMMARY => + 'Missing channel summary', + PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY => + 'Channel summary should be on one line, but is multi-line', + PEAR_CHANNELFILE_ERROR_NO_HOST => + 'Missing channel server for %type% server', + PEAR_CHANNELFILE_ERROR_INVALID_HOST => + 'Server name "%server%" is invalid for %type% server', + PEAR_CHANNELFILE_ERROR_INVALID_MIRROR => + 'Invalid mirror name "%name%", mirror type %type%', + PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE => + 'Invalid mirror type "%type%"', + PEAR_CHANNELFILE_ERROR_INVALID => + 'Cannot generate xml, contents are invalid', + PEAR_CHANNELFILE_ERROR_EMPTY_REGEX => + 'packagenameregex cannot be empty', + PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION => + '%parent% %protocol% function has no version', + PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME => + '%parent% %protocol% function has no name', + PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE => + '%parent% rest baseurl has no type', + PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME => + 'Validation package has no name in tag', + PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION => + 'Validation package "%package%" has no version', + PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND => + 'Mirror "%mirror%" does not exist', + PEAR_CHANNELFILE_ERROR_INVALID_PORT => + 'Port "%port%" must be numeric', + PEAR_CHANNELFILE_ERROR_NO_STATICVERSION => + ' tag must contain version attribute', + PEAR_CHANNELFILE_URI_CANT_MIRROR => + 'The __uri pseudo-channel cannot have mirrors', + PEAR_CHANNELFILE_ERROR_INVALID_SSL => + '%server% has invalid ssl attribute "%ssl%" can only be yes or not present', + ); + } + + /** + * @param string contents of package.xml file + * @return bool success of parsing + */ + function fromXmlString($data) + { + if (preg_match('/_supportedVersions)) { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_INVALID_VERSION, 'error', + array('version' => $channelversion[1])); + return false; + } + $parser = new PEAR_XMLParser; + $result = $parser->parse($data); + if ($result !== true) { + if ($result->getCode() == 1) { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_XML_EXT, 'error', + array('error' => $result->getMessage())); + } else { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER, 'error'); + } + return false; + } + $this->_channelInfo = $parser->getData(); + return true; + } else { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_VERSION, 'error', array('xml' => $data)); + return false; + } + } + + /** + * @return array + */ + function toArray() + { + if (!$this->_isValid && !$this->validate()) { + return false; + } + return $this->_channelInfo; + } + + /** + * @param array + * @static + * @return PEAR_ChannelFile|false false if invalid + */ + function &fromArray($data, $compatibility = false, $stackClass = 'PEAR_ErrorStack') + { + $a = new PEAR_ChannelFile($compatibility, $stackClass); + $a->_fromArray($data); + if (!$a->validate()) { + $a = false; + return $a; + } + return $a; + } + + /** + * Unlike {@link fromArray()} this does not do any validation + * @param array + * @static + * @return PEAR_ChannelFile + */ + function &fromArrayWithErrors($data, $compatibility = false, + $stackClass = 'PEAR_ErrorStack') + { + $a = new PEAR_ChannelFile($compatibility, $stackClass); + $a->_fromArray($data); + return $a; + } + + /** + * @param array + * @access private + */ + function _fromArray($data) + { + $this->_channelInfo = $data; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getErrors($purge = false) + { + return $this->_stack->getErrors($purge); + } + + /** + * Unindent given string (?) + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } + } + return $data; + } + + /** + * Parse a channel.xml file. Expects the name of + * a channel xml file as input. + * + * @param string $descfile name of channel xml file + * @return bool success of parsing + */ + function fromXmlFile($descfile) + { + if (!file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) || + (!$fp = fopen($descfile, 'r'))) { + require_once 'PEAR.php'; + return PEAR::raiseError("Unable to open $descfile"); + } + + // read the whole thing so we only get one cdata callback + // for each block of cdata + fclose($fp); + $data = file_get_contents($descfile); + return $this->fromXmlString($data); + } + + /** + * Parse channel information from different sources + * + * This method is able to extract information about a channel + * from an .xml file or a string + * + * @access public + * @param string Filename of the source or the source itself + * @return bool + */ + function fromAny($info) + { + if (is_string($info) && file_exists($info) && strlen($info) < 255) { + $tmp = substr($info, -4); + if ($tmp == '.xml') { + $info = $this->fromXmlFile($info); + } else { + $fp = fopen($info, "r"); + $test = fread($fp, 5); + fclose($fp); + if ($test == "fromXmlFile($info); + } + } + if (PEAR::isError($info)) { + require_once 'PEAR.php'; + return PEAR::raiseError($info); + } + } + if (is_string($info)) { + $info = $this->fromXmlString($info); + } + return $info; + } + + /** + * Return an XML document based on previous parsing and modifications + * + * @return string XML data + * + * @access public + */ + function toXml() + { + if (!$this->_isValid && !$this->validate()) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID); + return false; + } + if (!isset($this->_channelInfo['attribs']['version'])) { + $this->_channelInfo['attribs']['version'] = '1.0'; + } + $channelInfo = $this->_channelInfo; + $ret = "\n"; + $ret .= " + $channelInfo[name] + " . htmlspecialchars($channelInfo['summary'])." +"; + if (isset($channelInfo['suggestedalias'])) { + $ret .= ' ' . $channelInfo['suggestedalias'] . "\n"; + } + if (isset($channelInfo['validatepackage'])) { + $ret .= ' ' . + htmlspecialchars($channelInfo['validatepackage']['_content']) . + "\n"; + } + $ret .= " \n"; + $ret .= ' _makeRestXml($channelInfo['servers']['primary']['rest'], ' '); + } + $ret .= " \n"; + if (isset($channelInfo['servers']['mirror'])) { + $ret .= $this->_makeMirrorsXml($channelInfo); + } + $ret .= " \n"; + $ret .= ""; + return str_replace("\r", "\n", str_replace("\r\n", "\n", $ret)); + } + + /** + * Generate the tag + * @access private + */ + function _makeRestXml($info, $indent) + { + $ret = $indent . "\n"; + if (isset($info['baseurl']) && !isset($info['baseurl'][0])) { + $info['baseurl'] = array($info['baseurl']); + } + + if (isset($info['baseurl'])) { + foreach ($info['baseurl'] as $url) { + $ret .= "$indent \n"; + } + } + $ret .= $indent . "\n"; + return $ret; + } + + /** + * Generate the tag + * @access private + */ + function _makeMirrorsXml($channelInfo) + { + $ret = ""; + if (!isset($channelInfo['servers']['mirror'][0])) { + $channelInfo['servers']['mirror'] = array($channelInfo['servers']['mirror']); + } + foreach ($channelInfo['servers']['mirror'] as $mirror) { + $ret .= ' _makeRestXml($mirror['rest'], ' '); + } + $ret .= " \n"; + } else { + $ret .= "/>\n"; + } + } + return $ret; + } + + /** + * Generate the tag + * @access private + */ + function _makeFunctionsXml($functions, $indent, $rest = false) + { + $ret = ''; + if (!isset($functions[0])) { + $functions = array($functions); + } + foreach ($functions as $function) { + $ret .= "$indent\n"; + } + return $ret; + } + + /** + * Validation error. Also marks the object contents as invalid + * @param error code + * @param array error information + * @access private + */ + function _validateError($code, $params = array()) + { + $this->_stack->push($code, 'error', $params); + $this->_isValid = false; + } + + /** + * Validation warning. Does not mark the object contents invalid. + * @param error code + * @param array error information + * @access private + */ + function _validateWarning($code, $params = array()) + { + $this->_stack->push($code, 'warning', $params); + } + + /** + * Validate parsed file. + * + * @access public + * @return boolean + */ + function validate() + { + $this->_isValid = true; + $info = $this->_channelInfo; + if (empty($info['name'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_NAME); + } elseif (!$this->validChannelServer($info['name'])) { + if ($info['name'] != '__uri') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, array('tag' => 'name', + 'name' => $info['name'])); + } + } + if (empty($info['summary'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY); + } elseif (strpos(trim($info['summary']), "\n") !== false) { + $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $info['summary'])); + } + if (isset($info['suggestedalias'])) { + if (!$this->validChannelServer($info['suggestedalias'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'suggestedalias', 'name' =>$info['suggestedalias'])); + } + } + if (isset($info['localalias'])) { + if (!$this->validChannelServer($info['localalias'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'localalias', 'name' =>$info['localalias'])); + } + } + if (isset($info['validatepackage'])) { + if (!isset($info['validatepackage']['_content'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME); + } + if (!isset($info['validatepackage']['attribs']['version'])) { + $content = isset($info['validatepackage']['_content']) ? + $info['validatepackage']['_content'] : + null; + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION, + array('package' => $content)); + } + } + + if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['port']) && + !is_numeric($info['servers']['primary']['attribs']['port'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_PORT, + array('port' => $info['servers']['primary']['attribs']['port'])); + } + + if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['ssl']) && + $info['servers']['primary']['attribs']['ssl'] != 'yes') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL, + array('ssl' => $info['servers']['primary']['attribs']['ssl'], + 'server' => $info['name'])); + } + + if (isset($info['servers']['primary']['rest']) && + isset($info['servers']['primary']['rest']['baseurl'])) { + $this->_validateFunctions('rest', $info['servers']['primary']['rest']['baseurl']); + } + if (isset($info['servers']['mirror'])) { + if ($this->_channelInfo['name'] == '__uri') { + $this->_validateError(PEAR_CHANNELFILE_URI_CANT_MIRROR); + } + if (!isset($info['servers']['mirror'][0])) { + $info['servers']['mirror'] = array($info['servers']['mirror']); + } + foreach ($info['servers']['mirror'] as $mirror) { + if (!isset($mirror['attribs']['host'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_HOST, + array('type' => 'mirror')); + } elseif (!$this->validChannelServer($mirror['attribs']['host'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_HOST, + array('server' => $mirror['attribs']['host'], 'type' => 'mirror')); + } + if (isset($mirror['attribs']['ssl']) && $mirror['attribs']['ssl'] != 'yes') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL, + array('ssl' => $info['ssl'], 'server' => $mirror['attribs']['host'])); + } + if (isset($mirror['rest'])) { + $this->_validateFunctions('rest', $mirror['rest']['baseurl'], + $mirror['attribs']['host']); + } + } + } + return $this->_isValid; + } + + /** + * @param string rest - protocol name this function applies to + * @param array the functions + * @param string the name of the parent element (mirror name, for instance) + */ + function _validateFunctions($protocol, $functions, $parent = '') + { + if (!isset($functions[0])) { + $functions = array($functions); + } + + foreach ($functions as $function) { + if (!isset($function['_content']) || empty($function['_content'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME, + array('parent' => $parent, 'protocol' => $protocol)); + } + + if ($protocol == 'rest') { + if (!isset($function['attribs']['type']) || + empty($function['attribs']['type'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE, + array('parent' => $parent, 'protocol' => $protocol)); + } + } else { + if (!isset($function['attribs']['version']) || + empty($function['attribs']['version'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION, + array('parent' => $parent, 'protocol' => $protocol)); + } + } + } + } + + /** + * Test whether a string contains a valid channel server. + * @param string $ver the package version to test + * @return bool + */ + function validChannelServer($server) + { + if ($server == '__uri') { + return true; + } + return (bool) preg_match(PEAR_CHANNELS_SERVER_PREG, $server); + } + + /** + * @return string|false + */ + function getName() + { + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + + return false; + } + + /** + * @return string|false + */ + function getServer() + { + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + + return false; + } + + /** + * @return int|80 port number to connect to + */ + function getPort($mirror = false) + { + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir['attribs']['port'])) { + return $mir['attribs']['port']; + } + + if ($this->getSSL($mirror)) { + return 443; + } + + return 80; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary']['attribs']['port'])) { + return $this->_channelInfo['servers']['primary']['attribs']['port']; + } + + if ($this->getSSL()) { + return 443; + } + + return 80; + } + + /** + * @return bool Determines whether secure sockets layer (SSL) is used to connect to this channel + */ + function getSSL($mirror = false) + { + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir['attribs']['ssl'])) { + return true; + } + + return false; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) { + return true; + } + + return false; + } + + /** + * @return string|false + */ + function getSummary() + { + if (isset($this->_channelInfo['summary'])) { + return $this->_channelInfo['summary']; + } + + return false; + } + + /** + * @param string protocol type + * @param string Mirror name + * @return array|false + */ + function getFunctions($protocol, $mirror = false) + { + if ($this->getName() == '__uri') { + return false; + } + + $function = $protocol == 'rest' ? 'baseurl' : 'function'; + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir[$protocol][$function])) { + return $mir[$protocol][$function]; + } + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary'][$protocol][$function])) { + return $this->_channelInfo['servers']['primary'][$protocol][$function]; + } + + return false; + } + + /** + * @param string Protocol type + * @param string Function name (null to return the + * first protocol of the type requested) + * @param string Mirror name, if any + * @return array + */ + function getFunction($type, $name = null, $mirror = false) + { + $protocols = $this->getFunctions($type, $mirror); + if (!$protocols) { + return false; + } + + foreach ($protocols as $protocol) { + if ($name === null) { + return $protocol; + } + + if ($protocol['_content'] != $name) { + continue; + } + + return $protocol; + } + + return false; + } + + /** + * @param string protocol type + * @param string protocol name + * @param string version + * @param string mirror name + * @return boolean + */ + function supports($type, $name = null, $mirror = false, $version = '1.0') + { + $protocols = $this->getFunctions($type, $mirror); + if (!$protocols) { + return false; + } + + foreach ($protocols as $protocol) { + if ($protocol['attribs']['version'] != $version) { + continue; + } + + if ($name === null) { + return true; + } + + if ($protocol['_content'] != $name) { + continue; + } + + return true; + } + + return false; + } + + /** + * Determines whether a channel supports Representational State Transfer (REST) protocols + * for retrieving channel information + * @param string + * @return bool + */ + function supportsREST($mirror = false) + { + if ($mirror == $this->_channelInfo['name']) { + $mirror = false; + } + + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + return isset($mir['rest']); + } + + return false; + } + + return isset($this->_channelInfo['servers']['primary']['rest']); + } + + /** + * Get the URL to access a base resource. + * + * Hyperlinks in the returned xml will be used to retrieve the proper information + * needed. This allows extreme extensibility and flexibility in implementation + * @param string Resource Type to retrieve + */ + function getBaseURL($resourceType, $mirror = false) + { + if ($mirror == $this->_channelInfo['name']) { + $mirror = false; + } + + if ($mirror) { + $mir = $this->getMirror($mirror); + if (!$mir) { + return false; + } + + $rest = $mir['rest']; + } else { + $rest = $this->_channelInfo['servers']['primary']['rest']; + } + + if (!isset($rest['baseurl'][0])) { + $rest['baseurl'] = array($rest['baseurl']); + } + + foreach ($rest['baseurl'] as $baseurl) { + if (strtolower($baseurl['attribs']['type']) == strtolower($resourceType)) { + return $baseurl['_content']; + } + } + + return false; + } + + /** + * Since REST does not implement RPC, provide this as a logical wrapper around + * resetFunctions for REST + * @param string|false mirror name, if any + */ + function resetREST($mirror = false) + { + return $this->resetFunctions('rest', $mirror); + } + + /** + * Empty all protocol definitions + * @param string protocol type + * @param string|false mirror name, if any + */ + function resetFunctions($type, $mirror = false) + { + if ($mirror) { + if (isset($this->_channelInfo['servers']['mirror'])) { + $mirrors = $this->_channelInfo['servers']['mirror']; + if (!isset($mirrors[0])) { + $mirrors = array($mirrors); + } + + foreach ($mirrors as $i => $mir) { + if ($mir['attribs']['host'] == $mirror) { + if (isset($this->_channelInfo['servers']['mirror'][$i][$type])) { + unset($this->_channelInfo['servers']['mirror'][$i][$type]); + } + + return true; + } + } + + return false; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary'][$type])) { + unset($this->_channelInfo['servers']['primary'][$type]); + } + + return true; + } + + /** + * Set a channel's protocols to the protocols supported by pearweb + */ + function setDefaultPEARProtocols($version = '1.0', $mirror = false) + { + switch ($version) { + case '1.0' : + $this->resetREST($mirror); + + if (!isset($this->_channelInfo['servers'])) { + $this->_channelInfo['servers'] = array('primary' => + array('rest' => array())); + } elseif (!isset($this->_channelInfo['servers']['primary'])) { + $this->_channelInfo['servers']['primary'] = array('rest' => array()); + } + + return true; + break; + default : + return false; + break; + } + } + + /** + * @return array + */ + function getMirrors() + { + if (isset($this->_channelInfo['servers']['mirror'])) { + $mirrors = $this->_channelInfo['servers']['mirror']; + if (!isset($mirrors[0])) { + $mirrors = array($mirrors); + } + + return $mirrors; + } + + return array(); + } + + /** + * Get the unserialized XML representing a mirror + * @return array|false + */ + function getMirror($server) + { + foreach ($this->getMirrors() as $mirror) { + if ($mirror['attribs']['host'] == $server) { + return $mirror; + } + } + + return false; + } + + /** + * @param string + * @return string|false + * @error PEAR_CHANNELFILE_ERROR_NO_NAME + * @error PEAR_CHANNELFILE_ERROR_INVALID_NAME + */ + function setName($name) + { + return $this->setServer($name); + } + + /** + * Set the socket number (port) that is used to connect to this channel + * @param integer + * @param string|false name of the mirror server, or false for the primary + */ + function setPort($port, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $this->_channelInfo['servers']['mirror'][$i]['attribs']['port'] = $port; + return true; + } + } + + return false; + } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $this->_channelInfo['servers']['mirror']['attribs']['port'] = $port; + $this->_isValid = false; + return true; + } + } + + $this->_channelInfo['servers']['primary']['attribs']['port'] = $port; + $this->_isValid = false; + return true; + } + + /** + * Set the socket number (port) that is used to connect to this channel + * @param bool Determines whether to turn on SSL support or turn it off + * @param string|false name of the mirror server, or false for the primary + */ + function setSSL($ssl = true, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + if (!$ssl) { + if (isset($this->_channelInfo['servers']['mirror'][$i] + ['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl']); + } + } else { + $this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl'] = 'yes'; + } + + return true; + } + } + + return false; + } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + if (!$ssl) { + if (isset($this->_channelInfo['servers']['mirror']['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['mirror']['attribs']['ssl']); + } + } else { + $this->_channelInfo['servers']['mirror']['attribs']['ssl'] = 'yes'; + } + + $this->_isValid = false; + return true; + } + } + + if ($ssl) { + $this->_channelInfo['servers']['primary']['attribs']['ssl'] = 'yes'; + } else { + if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['primary']['attribs']['ssl']); + } + } + + $this->_isValid = false; + return true; + } + + /** + * @param string + * @return string|false + * @error PEAR_CHANNELFILE_ERROR_NO_SERVER + * @error PEAR_CHANNELFILE_ERROR_INVALID_SERVER + */ + function setServer($server, $mirror = false) + { + if (empty($server)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SERVER); + return false; + } elseif (!$this->validChannelServer($server)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'name', 'name' => $server)); + return false; + } + + if ($mirror) { + $found = false; + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $found = true; + break; + } + } + + if (!$found) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $this->_channelInfo['mirror'][$i]['attribs']['host'] = $server; + return true; + } + + $this->_channelInfo['name'] = $server; + return true; + } + + /** + * @param string + * @return boolean success + * @error PEAR_CHANNELFILE_ERROR_NO_SUMMARY + * @warning PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY + */ + function setSummary($summary) + { + if (empty($summary)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY); + return false; + } elseif (strpos(trim($summary), "\n") !== false) { + $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $summary)); + } + + $this->_channelInfo['summary'] = $summary; + return true; + } + + /** + * @param string + * @param boolean determines whether the alias is in channel.xml or local + * @return boolean success + */ + function setAlias($alias, $local = false) + { + if (!$this->validChannelServer($alias)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'suggestedalias', 'name' => $alias)); + return false; + } + + if ($local) { + $this->_channelInfo['localalias'] = $alias; + } else { + $this->_channelInfo['suggestedalias'] = $alias; + } + + return true; + } + + /** + * @return string + */ + function getAlias() + { + if (isset($this->_channelInfo['localalias'])) { + return $this->_channelInfo['localalias']; + } + if (isset($this->_channelInfo['suggestedalias'])) { + return $this->_channelInfo['suggestedalias']; + } + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + return ''; + } + + /** + * Set the package validation object if it differs from PEAR's default + * The class must be includeable via changing _ in the classname to path separator, + * but no checking of this is made. + * @param string|false pass in false to reset to the default packagename regex + * @return boolean success + */ + function setValidationPackage($validateclass, $version) + { + if (empty($validateclass)) { + unset($this->_channelInfo['validatepackage']); + } + $this->_channelInfo['validatepackage'] = array('_content' => $validateclass); + $this->_channelInfo['validatepackage']['attribs'] = array('version' => $version); + } + + /** + * Add a protocol to the provides section + * @param string protocol type + * @param string protocol version + * @param string protocol name, if any + * @param string mirror name, if this is a mirror's protocol + * @return bool + */ + function addFunction($type, $version, $name = '', $mirror = false) + { + if ($mirror) { + return $this->addMirrorFunction($mirror, $type, $version, $name); + } + + $set = array('attribs' => array('version' => $version), '_content' => $name); + if (!isset($this->_channelInfo['servers']['primary'][$type]['function'])) { + if (!isset($this->_channelInfo['servers'])) { + $this->_channelInfo['servers'] = array('primary' => + array($type => array())); + } elseif (!isset($this->_channelInfo['servers']['primary'])) { + $this->_channelInfo['servers']['primary'] = array($type => array()); + } + + $this->_channelInfo['servers']['primary'][$type]['function'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($this->_channelInfo['servers']['primary'][$type]['function'][0])) { + $this->_channelInfo['servers']['primary'][$type]['function'] = array( + $this->_channelInfo['servers']['primary'][$type]['function']); + } + + $this->_channelInfo['servers']['primary'][$type]['function'][] = $set; + return true; + } + /** + * Add a protocol to a mirror's provides section + * @param string mirror name (server) + * @param string protocol type + * @param string protocol version + * @param string protocol name, if any + */ + function addMirrorFunction($mirror, $type, $version, $name = '') + { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $setmirror = false; + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $setmirror = &$this->_channelInfo['servers']['mirror'][$i]; + break; + } + } + } else { + if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $setmirror = &$this->_channelInfo['servers']['mirror']; + } + } + + if (!$setmirror) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $set = array('attribs' => array('version' => $version), '_content' => $name); + if (!isset($setmirror[$type]['function'])) { + $setmirror[$type]['function'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($setmirror[$type]['function'][0])) { + $setmirror[$type]['function'] = array($setmirror[$type]['function']); + } + + $setmirror[$type]['function'][] = $set; + $this->_isValid = false; + return true; + } + + /** + * @param string Resource Type this url links to + * @param string URL + * @param string|false mirror name, if this is not a primary server REST base URL + */ + function setBaseURL($resourceType, $url, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $setmirror = false; + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $setmirror = &$this->_channelInfo['servers']['mirror'][$i]; + break; + } + } + } else { + if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $setmirror = &$this->_channelInfo['servers']['mirror']; + } + } + } else { + $setmirror = &$this->_channelInfo['servers']['primary']; + } + + $set = array('attribs' => array('type' => $resourceType), '_content' => $url); + if (!isset($setmirror['rest'])) { + $setmirror['rest'] = array(); + } + + if (!isset($setmirror['rest']['baseurl'])) { + $setmirror['rest']['baseurl'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($setmirror['rest']['baseurl'][0])) { + $setmirror['rest']['baseurl'] = array($setmirror['rest']['baseurl']); + } + + foreach ($setmirror['rest']['baseurl'] as $i => $url) { + if ($url['attribs']['type'] == $resourceType) { + $this->_isValid = false; + $setmirror['rest']['baseurl'][$i] = $set; + return true; + } + } + + $setmirror['rest']['baseurl'][] = $set; + $this->_isValid = false; + return true; + } + + /** + * @param string mirror server + * @param int mirror http port + * @return boolean + */ + function addMirror($server, $port = null) + { + if ($this->_channelInfo['name'] == '__uri') { + return false; // the __uri channel cannot have mirrors by definition + } + + $set = array('attribs' => array('host' => $server)); + if (is_numeric($port)) { + $set['attribs']['port'] = $port; + } + + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_channelInfo['servers']['mirror'] = $set; + return true; + } + + if (!isset($this->_channelInfo['servers']['mirror'][0])) { + $this->_channelInfo['servers']['mirror'] = + array($this->_channelInfo['servers']['mirror']); + } + + $this->_channelInfo['servers']['mirror'][] = $set; + return true; + } + + /** + * Retrieve the name of the validation package for this channel + * @return string|false + */ + function getValidationPackage() + { + if (!$this->_isValid && !$this->validate()) { + return false; + } + + if (!isset($this->_channelInfo['validatepackage'])) { + return array('attribs' => array('version' => 'default'), + '_content' => 'PEAR_Validate'); + } + + return $this->_channelInfo['validatepackage']; + } + + /** + * Retrieve the object that can be used for custom validation + * @param string|false the name of the package to validate. If the package is + * the channel validation package, PEAR_Validate is returned + * @return PEAR_Validate|false false is returned if the validation package + * cannot be located + */ + function &getValidationObject($package = false) + { + if (!class_exists('PEAR_Validate')) { + require_once 'PEAR/Validate.php'; + } + + if (!$this->_isValid) { + if (!$this->validate()) { + $a = false; + return $a; + } + } + + if (isset($this->_channelInfo['validatepackage'])) { + if ($package == $this->_channelInfo['validatepackage']) { + // channel validation packages are always validated by PEAR_Validate + $val = &new PEAR_Validate; + return $val; + } + + if (!class_exists(str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']))) { + if ($this->isIncludeable(str_replace('_', '/', + $this->_channelInfo['validatepackage']['_content']) . '.php')) { + include_once str_replace('_', '/', + $this->_channelInfo['validatepackage']['_content']) . '.php'; + $vclass = str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']); + $val = &new $vclass; + } else { + $a = false; + return $a; + } + } else { + $vclass = str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']); + $val = &new $vclass; + } + } else { + $val = &new PEAR_Validate; + } + + return $val; + } + + function isIncludeable($path) + { + $possibilities = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($possibilities as $dir) { + if (file_exists($dir . DIRECTORY_SEPARATOR . $path) + && is_readable($dir . DIRECTORY_SEPARATOR . $path)) { + return true; + } + } + + return false; + } + + /** + * This function is used by the channel updater and retrieves a value set by + * the registry, or the current time if it has not been set + * @return string + */ + function lastModified() + { + if (isset($this->_channelInfo['_lastmodified'])) { + return $this->_channelInfo['_lastmodified']; + } + + return time(); + } +}PEAR-1.9.0/PEAR/Command.php100664 764 764 30633 100664 10151 + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Command.php 286494 2009-07-29 06:57:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * Needed for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Frontend.php'; +require_once 'PEAR/XMLParser.php'; + +/** + * List of commands and what classes they are implemented in. + * @var array command => implementing class + */ +$GLOBALS['_PEAR_Command_commandlist'] = array(); + +/** + * List of commands and their descriptions + * @var array command => description + */ +$GLOBALS['_PEAR_Command_commanddesc'] = array(); + +/** + * List of shortcuts to common commands. + * @var array shortcut => command + */ +$GLOBALS['_PEAR_Command_shortcuts'] = array(); + +/** + * Array of command objects + * @var array class => object + */ +$GLOBALS['_PEAR_Command_objects'] = array(); + +/** + * PEAR command class, a simple factory class for administrative + * commands. + * + * How to implement command classes: + * + * - The class must be called PEAR_Command_Nnn, installed in the + * "PEAR/Common" subdir, with a method called getCommands() that + * returns an array of the commands implemented by the class (see + * PEAR/Command/Install.php for an example). + * + * - The class must implement a run() function that is called with three + * params: + * + * (string) command name + * (array) assoc array with options, freely defined by each + * command, for example: + * array('force' => true) + * (array) list of the other parameters + * + * The run() function returns a PEAR_CommandResponse object. Use + * these methods to get information: + * + * int getStatus() Returns PEAR_COMMAND_(SUCCESS|FAILURE|PARTIAL) + * *_PARTIAL means that you need to issue at least + * one more command to complete the operation + * (used for example for validation steps). + * + * string getMessage() Returns a message for the user. Remember, + * no HTML or other interface-specific markup. + * + * If something unexpected happens, run() returns a PEAR error. + * + * - DON'T OUTPUT ANYTHING! Return text for output instead. + * + * - DON'T USE HTML! The text you return will be used from both Gtk, + * web and command-line interfaces, so for now, keep everything to + * plain text. + * + * - DON'T USE EXIT OR DIE! Always use pear errors. From static + * classes do PEAR::raiseError(), from other classes do + * $this->raiseError(). + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command +{ + // {{{ factory() + + /** + * Get the right object for executing a command. + * + * @param string $command The name of the command + * @param object $config Instance of PEAR_Config object + * + * @return object the command object or a PEAR error + * + * @access public + * @static + */ + function &factory($command, &$config) + { + if (empty($GLOBALS['_PEAR_Command_commandlist'])) { + PEAR_Command::registerCommands(); + } + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } + if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { + $a = PEAR::raiseError("unknown command `$command'"); + return $a; + } + $class = $GLOBALS['_PEAR_Command_commandlist'][$command]; + if (!class_exists($class)) { + require_once $GLOBALS['_PEAR_Command_objects'][$class]; + } + if (!class_exists($class)) { + $a = PEAR::raiseError("unknown command `$command'"); + return $a; + } + $ui =& PEAR_Command::getFrontendObject(); + $obj = &new $class($ui, $config); + return $obj; + } + + // }}} + // {{{ & getObject() + function &getObject($command) + { + $class = $GLOBALS['_PEAR_Command_commandlist'][$command]; + if (!class_exists($class)) { + require_once $GLOBALS['_PEAR_Command_objects'][$class]; + } + if (!class_exists($class)) { + return PEAR::raiseError("unknown command `$command'"); + } + $ui =& PEAR_Command::getFrontendObject(); + $config = &PEAR_Config::singleton(); + $obj = &new $class($ui, $config); + return $obj; + } + + // }}} + // {{{ & getFrontendObject() + + /** + * Get instance of frontend object. + * + * @return object|PEAR_Error + * @static + */ + function &getFrontendObject() + { + $a = &PEAR_Frontend::singleton(); + return $a; + } + + // }}} + // {{{ & setFrontendClass() + + /** + * Load current frontend class. + * + * @param string $uiclass Name of class implementing the frontend + * + * @return object the frontend object, or a PEAR error + * @static + */ + function &setFrontendClass($uiclass) + { + $a = &PEAR_Frontend::setFrontendClass($uiclass); + return $a; + } + + // }}} + // {{{ setFrontendType() + + /** + * Set current frontend. + * + * @param string $uitype Name of the frontend type (for example "CLI") + * + * @return object the frontend object, or a PEAR error + * @static + */ + function setFrontendType($uitype) + { + $uiclass = 'PEAR_Frontend_' . $uitype; + return PEAR_Command::setFrontendClass($uiclass); + } + + // }}} + // {{{ registerCommands() + + /** + * Scan through the Command directory looking for classes + * and see what commands they implement. + * + * @param bool (optional) if FALSE (default), the new list of + * commands should replace the current one. If TRUE, + * new entries will be merged with old. + * + * @param string (optional) where (what directory) to look for + * classes, defaults to the Command subdirectory of + * the directory from where this file (__FILE__) is + * included. + * + * @return bool TRUE on success, a PEAR error on failure + * + * @access public + * @static + */ + function registerCommands($merge = false, $dir = null) + { + $parser = new PEAR_XMLParser; + if ($dir === null) { + $dir = dirname(__FILE__) . '/Command'; + } + if (!is_dir($dir)) { + return PEAR::raiseError("registerCommands: opendir($dir) '$dir' does not exist or is not a directory"); + } + $dp = @opendir($dir); + if (empty($dp)) { + return PEAR::raiseError("registerCommands: opendir($dir) failed"); + } + if (!$merge) { + $GLOBALS['_PEAR_Command_commandlist'] = array(); + } + + while ($file = readdir($dp)) { + if ($file{0} == '.' || substr($file, -4) != '.xml') { + continue; + } + + $f = substr($file, 0, -4); + $class = "PEAR_Command_" . $f; + // List of commands + if (empty($GLOBALS['_PEAR_Command_objects'][$class])) { + $GLOBALS['_PEAR_Command_objects'][$class] = "$dir/" . $f . '.php'; + } + + $parser->parse(file_get_contents("$dir/$file")); + $implements = $parser->getData(); + foreach ($implements as $command => $desc) { + if ($command == 'attribs') { + continue; + } + + if (isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { + return PEAR::raiseError('Command "' . $command . '" already registered in ' . + 'class "' . $GLOBALS['_PEAR_Command_commandlist'][$command] . '"'); + } + + $GLOBALS['_PEAR_Command_commandlist'][$command] = $class; + $GLOBALS['_PEAR_Command_commanddesc'][$command] = $desc['summary']; + if (isset($desc['shortcut'])) { + $shortcut = $desc['shortcut']; + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$shortcut])) { + return PEAR::raiseError('Command shortcut "' . $shortcut . '" already ' . + 'registered to command "' . $command . '" in class "' . + $GLOBALS['_PEAR_Command_commandlist'][$command] . '"'); + } + $GLOBALS['_PEAR_Command_shortcuts'][$shortcut] = $command; + } + + if (isset($desc['options']) && $desc['options']) { + foreach ($desc['options'] as $oname => $option) { + if (isset($option['shortopt']) && strlen($option['shortopt']) > 1) { + return PEAR::raiseError('Option "' . $oname . '" short option "' . + $option['shortopt'] . '" must be ' . + 'only 1 character in Command "' . $command . '" in class "' . + $class . '"'); + } + } + } + } + } + + ksort($GLOBALS['_PEAR_Command_shortcuts']); + ksort($GLOBALS['_PEAR_Command_commandlist']); + @closedir($dp); + return true; + } + + // }}} + // {{{ getCommands() + + /** + * Get the list of currently supported commands, and what + * classes implement them. + * + * @return array command => implementing class + * + * @access public + * @static + */ + function getCommands() + { + if (empty($GLOBALS['_PEAR_Command_commandlist'])) { + PEAR_Command::registerCommands(); + } + return $GLOBALS['_PEAR_Command_commandlist']; + } + + // }}} + // {{{ getShortcuts() + + /** + * Get the list of command shortcuts. + * + * @return array shortcut => command + * + * @access public + * @static + */ + function getShortcuts() + { + if (empty($GLOBALS['_PEAR_Command_shortcuts'])) { + PEAR_Command::registerCommands(); + } + return $GLOBALS['_PEAR_Command_shortcuts']; + } + + // }}} + // {{{ getGetoptArgs() + + /** + * Compiles arguments for getopt. + * + * @param string $command command to get optstring for + * @param string $short_args (reference) short getopt format + * @param array $long_args (reference) long getopt format + * + * @return void + * + * @access public + * @static + */ + function getGetoptArgs($command, &$short_args, &$long_args) + { + if (empty($GLOBALS['_PEAR_Command_commandlist'])) { + PEAR_Command::registerCommands(); + } + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } + if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { + return null; + } + $obj = &PEAR_Command::getObject($command); + return $obj->getGetoptArgs($command, $short_args, $long_args); + } + + // }}} + // {{{ getDescription() + + /** + * Get description for a command. + * + * @param string $command Name of the command + * + * @return string command description + * + * @access public + * @static + */ + function getDescription($command) + { + if (!isset($GLOBALS['_PEAR_Command_commanddesc'][$command])) { + return null; + } + return $GLOBALS['_PEAR_Command_commanddesc'][$command]; + } + + // }}} + // {{{ getHelp() + + /** + * Get help for command. + * + * @param string $command Name of the command to return help for + * + * @access public + * @static + */ + function getHelp($command) + { + $cmds = PEAR_Command::getCommands(); + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } + if (isset($cmds[$command])) { + $obj = &PEAR_Command::getObject($command); + return $obj->getHelp($command); + } + return false; + } + // }}} +}PEAR-1.9.0/PEAR/Common.php100664 764 764 62255 100664 10030 + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 282969 2009-06-28 23:09:27Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1.0 + * @deprecated File deprecated since Release 1.4.0a1 + */ + +/** + * Include error handling + */ +require_once 'PEAR.php'; + +/** + * PEAR_Common error when an invalid PHP file is passed to PEAR_Common::analyzeSourceCode() + */ +define('PEAR_COMMON_ERROR_INVALIDPHP', 1); +define('_PEAR_COMMON_PACKAGE_NAME_PREG', '[A-Za-z][a-zA-Z0-9_]+'); +define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/'); + +// this should allow: 1, 1.0, 1.0RC1, 1.0dev, 1.0dev123234234234, 1.0a1, 1.0b1, 1.0pl1 +define('_PEAR_COMMON_PACKAGE_VERSION_PREG', '\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?'); +define('PEAR_COMMON_PACKAGE_VERSION_PREG', '/^' . _PEAR_COMMON_PACKAGE_VERSION_PREG . '\\z/i'); + +// XXX far from perfect :-) +define('_PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '(' . _PEAR_COMMON_PACKAGE_NAME_PREG . + ')(-([.0-9a-zA-Z]+))?'); +define('PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_PACKAGE_DOWNLOAD_PREG . + '\\z/'); + +define('_PEAR_CHANNELS_NAME_PREG', '[A-Za-z][a-zA-Z0-9\.]+'); +define('PEAR_CHANNELS_NAME_PREG', '/^' . _PEAR_CHANNELS_NAME_PREG . '\\z/'); + +// this should allow any dns or IP address, plus a path - NO UNDERSCORES ALLOWED +define('_PEAR_CHANNELS_SERVER_PREG', '[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(\/[a-zA-Z0-9\-]+)*'); +define('PEAR_CHANNELS_SERVER_PREG', '/^' . _PEAR_CHANNELS_SERVER_PREG . '\\z/i'); + +define('_PEAR_CHANNELS_PACKAGE_PREG', '(' ._PEAR_CHANNELS_SERVER_PREG . ')\/(' + . _PEAR_COMMON_PACKAGE_NAME_PREG . ')'); +define('PEAR_CHANNELS_PACKAGE_PREG', '/^' . _PEAR_CHANNELS_PACKAGE_PREG . '\\z/i'); + +define('_PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '(' . _PEAR_CHANNELS_NAME_PREG . ')::(' + . _PEAR_COMMON_PACKAGE_NAME_PREG . ')(-([.0-9a-zA-Z]+))?'); +define('PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_CHANNEL_DOWNLOAD_PREG . '\\z/'); + +/** + * List of temporary files and directories registered by + * PEAR_Common::addTempFile(). + * @var array + */ +$GLOBALS['_PEAR_Common_tempfiles'] = array(); + +/** + * Valid maintainer roles + * @var array + */ +$GLOBALS['_PEAR_Common_maintainer_roles'] = array('lead','developer','contributor','helper'); + +/** + * Valid release states + * @var array + */ +$GLOBALS['_PEAR_Common_release_states'] = array('alpha','beta','stable','snapshot','devel'); + +/** + * Valid dependency types + * @var array + */ +$GLOBALS['_PEAR_Common_dependency_types'] = array('pkg','ext','php','prog','ldlib','rtlib','os','websrv','sapi'); + +/** + * Valid dependency relations + * @var array + */ +$GLOBALS['_PEAR_Common_dependency_relations'] = array('has','eq','lt','le','gt','ge','not', 'ne'); + +/** + * Valid file roles + * @var array + */ +$GLOBALS['_PEAR_Common_file_roles'] = array('php','ext','test','doc','data','src','script'); + +/** + * Valid replacement types + * @var array + */ +$GLOBALS['_PEAR_Common_replacement_types'] = array('php-const', 'pear-config', 'package-info'); + +/** + * Valid "provide" types + * @var array + */ +$GLOBALS['_PEAR_Common_provide_types'] = array('ext', 'prog', 'class', 'function', 'feature', 'api'); + +/** + * Valid "provide" types + * @var array + */ +$GLOBALS['_PEAR_Common_script_phases'] = array('pre-install', 'post-install', 'pre-uninstall', 'post-uninstall', 'pre-build', 'post-build', 'pre-configure', 'post-configure', 'pre-setup', 'post-setup'); + +/** + * Class providing common functionality for PEAR administration classes. + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + * @deprecated This class will disappear, and its components will be spread + * into smaller classes, like the AT&T breakup, as of Release 1.4.0a1 + */ +class PEAR_Common extends PEAR +{ + /** + * User Interface object (PEAR_Frontend_* class). If null, + * the log() method uses print. + * @var object + */ + var $ui = null; + + /** + * Configuration object (PEAR_Config). + * @var PEAR_Config + */ + var $config = null; + + /** stack of elements, gives some sort of XML context */ + var $element_stack = array(); + + /** name of currently parsed XML element */ + var $current_element; + + /** array of attributes of the currently parsed XML element */ + var $current_attributes = array(); + + /** assoc with information about a package */ + var $pkginfo = array(); + + var $current_path = null; + + /** + * Flag variable used to mark a valid package file + * @var boolean + * @access private + */ + var $_validPackageFile; + + /** + * PEAR_Common constructor + * + * @access public + */ + function PEAR_Common() + { + parent::PEAR(); + $this->config = &PEAR_Config::singleton(); + $this->debug = $this->config->get('verbose'); + } + + /** + * PEAR_Common destructor + * + * @access private + */ + function _PEAR_Common() + { + // doesn't work due to bug #14744 + //$tempfiles = $this->_tempfiles; + $tempfiles =& $GLOBALS['_PEAR_Common_tempfiles']; + while ($file = array_shift($tempfiles)) { + if (@is_dir($file)) { + if (!class_exists('System')) { + require_once 'System.php'; + } + + System::rm(array('-rf', $file)); + } elseif (file_exists($file)) { + unlink($file); + } + } + } + + /** + * Register a temporary file or directory. When the destructor is + * executed, all registered temporary files and directories are + * removed. + * + * @param string $file name of file or directory + * + * @return void + * + * @access public + */ + function addTempFile($file) + { + if (!class_exists('PEAR_Frontend')) { + require_once 'PEAR/Frontend.php'; + } + PEAR_Frontend::addTempFile($file); + } + + /** + * Wrapper to System::mkDir(), creates a directory as well as + * any necessary parent directories. + * + * @param string $dir directory name + * + * @return bool TRUE on success, or a PEAR error + * + * @access public + */ + function mkDirHier($dir) + { + // Only used in Installer - move it there ? + $this->log(2, "+ create dir $dir"); + if (!class_exists('System')) { + require_once 'System.php'; + } + return System::mkDir(array('-p', $dir)); + } + + /** + * Logging method. + * + * @param int $level log level (0 is quiet, higher is noisier) + * @param string $msg message to write to the log + * + * @return void + * + * @access public + * @static + */ + function log($level, $msg, $append_crlf = true) + { + if ($this->debug >= $level) { + if (!class_exists('PEAR_Frontend')) { + require_once 'PEAR/Frontend.php'; + } + + $ui = &PEAR_Frontend::singleton(); + if (is_a($ui, 'PEAR_Frontend')) { + $ui->log($msg, $append_crlf); + } else { + print "$msg\n"; + } + } + } + + /** + * Create and register a temporary directory. + * + * @param string $tmpdir (optional) Directory to use as tmpdir. + * Will use system defaults (for example + * /tmp or c:\windows\temp) if not specified + * + * @return string name of created directory + * + * @access public + */ + function mkTempDir($tmpdir = '') + { + $topt = $tmpdir ? array('-t', $tmpdir) : array(); + $topt = array_merge($topt, array('-d', 'pear')); + if (!class_exists('System')) { + require_once 'System.php'; + } + + if (!$tmpdir = System::mktemp($topt)) { + return false; + } + + $this->addTempFile($tmpdir); + return $tmpdir; + } + + /** + * Set object that represents the frontend to be used. + * + * @param object Reference of the frontend object + * @return void + * @access public + */ + function setFrontendObject(&$ui) + { + $this->ui = &$ui; + } + + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + if ($include) { + $i--; + } + return array_slice($states, $i + 1); + } + + /** + * Get the valid roles for a PEAR package maintainer + * + * @return array + * @static + */ + function getUserRoles() + { + return $GLOBALS['_PEAR_Common_maintainer_roles']; + } + + /** + * Get the valid package release states of packages + * + * @return array + * @static + */ + function getReleaseStates() + { + return $GLOBALS['_PEAR_Common_release_states']; + } + + /** + * Get the implemented dependency types (php, ext, pkg etc.) + * + * @return array + * @static + */ + function getDependencyTypes() + { + return $GLOBALS['_PEAR_Common_dependency_types']; + } + + /** + * Get the implemented dependency relations (has, lt, ge etc.) + * + * @return array + * @static + */ + function getDependencyRelations() + { + return $GLOBALS['_PEAR_Common_dependency_relations']; + } + + /** + * Get the implemented file roles + * + * @return array + * @static + */ + function getFileRoles() + { + return $GLOBALS['_PEAR_Common_file_roles']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getReplacementTypes() + { + return $GLOBALS['_PEAR_Common_replacement_types']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getProvideTypes() + { + return $GLOBALS['_PEAR_Common_provide_types']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getScriptPhases() + { + return $GLOBALS['_PEAR_Common_script_phases']; + } + + /** + * Test whether a string contains a valid package name. + * + * @param string $name the package name to test + * + * @return bool + * + * @access public + */ + function validPackageName($name) + { + return (bool)preg_match(PEAR_COMMON_PACKAGE_NAME_PREG, $name); + } + + /** + * Test whether a string contains a valid package version. + * + * @param string $ver the package version to test + * + * @return bool + * + * @access public + */ + function validPackageVersion($ver) + { + return (bool)preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver); + } + + /** + * @param string $path relative or absolute include path + * @return boolean + * @static + */ + function isIncludeable($path) + { + if (file_exists($path) && is_readable($path)) { + return true; + } + + $ipath = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($ipath as $include) { + $test = realpath($include . DIRECTORY_SEPARATOR . $path); + if (file_exists($test) && is_readable($test)) { + return true; + } + } + + return false; + } + + function _postProcessChecks($pf) + { + if (!PEAR::isError($pf)) { + return $this->_postProcessValidPackagexml($pf); + } + + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + $e = $this->raiseError($error['message'], $error['code'], null, null, $error); + } + } + + return $pf; + } + + /** + * Returns information about a package file. Expects the name of + * a gzipped tar file as input. + * + * @param string $file name of .tgz file + * + * @return array array with package information + * + * @access public + * @deprecated use PEAR_PackageFile->fromTgzFile() instead + * + */ + function infoFromTgzFile($file) + { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromTgzFile($file, PEAR_VALIDATE_NORMAL); + return $this->_postProcessChecks($pf); + } + + /** + * Returns information about a package file. Expects the name of + * a package xml file as input. + * + * @param string $descfile name of package xml file + * + * @return array array with package information + * + * @access public + * @deprecated use PEAR_PackageFile->fromPackageFile() instead + * + */ + function infoFromDescriptionFile($descfile) + { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); + return $this->_postProcessChecks($pf); + } + + /** + * Returns information about a package file. Expects the contents + * of a package xml file as input. + * + * @param string $data contents of package.xml file + * + * @return array array with package information + * + * @access public + * @deprecated use PEAR_PackageFile->fromXmlstring() instead + * + */ + function infoFromString($data) + { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromXmlString($data, PEAR_VALIDATE_NORMAL, false); + return $this->_postProcessChecks($pf); + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return array + */ + function _postProcessValidPackagexml(&$pf) + { + if (!is_a($pf, 'PEAR_PackageFile_v2')) { + $this->pkginfo = $pf->toArray(); + return $this->pkginfo; + } + + // sort of make this into a package.xml 1.0-style array + // changelog is not converted to old format. + $arr = $pf->toArray(true); + $arr = array_merge($arr, $arr['old']); + unset($arr['old'], $arr['xsdversion'], $arr['contents'], $arr['compatible'], + $arr['channel'], $arr['uri'], $arr['dependencies'], $arr['phprelease'], + $arr['extsrcrelease'], $arr['zendextsrcrelease'], $arr['extbinrelease'], + $arr['zendextbinrelease'], $arr['bundle'], $arr['lead'], $arr['developer'], + $arr['helper'], $arr['contributor']); + $arr['filelist'] = $pf->getFilelist(); + $this->pkginfo = $arr; + return $arr; + } + + /** + * Returns package information from different sources + * + * This method is able to extract information about a package + * from a .tgz archive or from a XML package definition file. + * + * @access public + * @param string Filename of the source ('package.xml', '.tgz') + * @return string + * @deprecated use PEAR_PackageFile->fromAnyFile() instead + */ + function infoFromAny($info) + { + if (is_string($info) && file_exists($info)) { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($pf)) { + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + $e = $this->raiseError($error['message'], $error['code'], null, null, $error); + } + } + + return $pf; + } + + return $this->_postProcessValidPackagexml($pf); + } + + return $info; + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @param array $pkginfo package info + * + * @return string XML data + * + * @access public + * @deprecated use a PEAR_PackageFile_v* object's generator instead + */ + function xmlFromInfo($pkginfo) + { + $config = &PEAR_Config::singleton(); + $packagefile = &new PEAR_PackageFile($config); + $pf = &$packagefile->fromArray($pkginfo); + $gen = &$pf->getDefaultGenerator(); + return $gen->toXml(PEAR_VALIDATE_PACKAGING); + } + + /** + * Validate XML package definition file. + * + * @param string $info Filename of the package archive or of the + * package definition file + * @param array $errors Array that will contain the errors + * @param array $warnings Array that will contain the warnings + * @param string $dir_prefix (optional) directory where source files + * may be found, or empty if they are not available + * @access public + * @return boolean + * @deprecated use the validation of PEAR_PackageFile objects + */ + function validatePackageInfo($info, &$errors, &$warnings, $dir_prefix = '') + { + $config = &PEAR_Config::singleton(); + $packagefile = &new PEAR_PackageFile($config); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (strpos($info, 'fromXmlString($info, PEAR_VALIDATE_NORMAL, ''); + } else { + $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL); + } + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + if ($error['level'] == 'error') { + $errors[] = $error['message']; + } else { + $warnings[] = $error['message']; + } + } + } + + return false; + } + + return true; + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access public + * + */ + function buildProvidesArray($srcinfo) + { + $file = basename($srcinfo['source_file']); + $pn = ''; + if (isset($this->_packageName)) { + $pn = $this->_packageName; + } + + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($this->pkginfo['provides'][$key])) { + continue; + } + + $this->pkginfo['provides'][$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $this->pkginfo['provides'][$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($this->pkginfo['provides'][$key])) { + continue; + } + + $this->pkginfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($this->pkginfo['provides'][$key])) { + continue; + } + + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + + $this->pkginfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @return mixed + * @access public + */ + function analyzeSourceCode($file) + { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'PEAR/PackageFile/v2/Validator.php'; + } + + $a = new PEAR_PackageFile_v2_Validator; + return $a->analyzeSourceCode($file); + } + + function detectDependencies($any, $status_callback = null) + { + if (!function_exists("token_get_all")) { + return false; + } + + if (PEAR::isError($info = $this->infoFromAny($any))) { + return $this->raiseError($info); + } + + if (!is_array($info)) { + return false; + } + + $deps = array(); + $used_c = $decl_c = $decl_f = $decl_m = array(); + foreach ($info['filelist'] as $file => $fa) { + $tmp = $this->analyzeSourceCode($file); + $used_c = @array_merge($used_c, $tmp['used_classes']); + $decl_c = @array_merge($decl_c, $tmp['declared_classes']); + $decl_f = @array_merge($decl_f, $tmp['declared_functions']); + $decl_m = @array_merge($decl_m, $tmp['declared_methods']); + $inheri = @array_merge($inheri, $tmp['inheritance']); + } + + $used_c = array_unique($used_c); + $decl_c = array_unique($decl_c); + $undecl_c = array_diff($used_c, $decl_c); + + return array('used_classes' => $used_c, + 'declared_classes' => $decl_c, + 'declared_methods' => $decl_m, + 'declared_functions' => $decl_f, + 'undeclared_classes' => $undecl_c, + 'inheritance' => $inheri, + ); + } + + /** + * Download a file through HTTP. Considers suggested file name in + * Content-disposition: header and can run a callback function for + * different events. The callback will be called with two + * parameters: the callback type, and parameters. The implemented + * callback types are: + * + * 'setup' called at the very beginning, parameter is a UI object + * that should be used for all output + * 'message' the parameter is a string with an informational message + * 'saveas' may be used to save with a different file name, the + * parameter is the filename that is about to be used. + * If a 'saveas' callback returns a non-empty string, + * that file name will be used as the filename instead. + * Note that $save_dir will not be affected by this, only + * the basename of the file. + * 'start' download is starting, parameter is number of bytes + * that are expected, or -1 if unknown + * 'bytesread' parameter is the number of bytes read so far + * 'done' download is complete, parameter is the total number + * of bytes read + * 'connfailed' if the TCP connection fails, this callback is called + * with array(host,port,errno,errmsg) + * 'writefailed' if writing to disk fails, this callback is called + * with array(destfile,errmsg) + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param object $ui PEAR_Frontend_* instance + * @param object $config PEAR_Config instance + * @param string $save_dir (optional) directory to save file in + * @param mixed $callback (optional) function/method to call for status + * updates + * + * @return string Returns the full path of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). + * + * @access public + * @deprecated in favor of PEAR_Downloader::downloadHttp() + */ + function downloadHttp($url, &$ui, $save_dir = '.', $callback = null) + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + return PEAR_Downloader::downloadHttp($url, $ui, $save_dir, $callback); + } +} + +require_once 'PEAR/Config.php'; +require_once 'PEAR/PackageFile.php';PEAR-1.9.0/PEAR/Config.php100664 764 764 204452 100664 10022 + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Config.php 286480 2009-07-29 02:50:02Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * Required for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Registry.php'; +require_once 'PEAR/Installer/Role.php'; +require_once 'System.php'; + +/** + * Last created PEAR_Config instance. + * @var object + */ +$GLOBALS['_PEAR_Config_instance'] = null; +if (!defined('PEAR_INSTALL_DIR') || !PEAR_INSTALL_DIR) { + $PEAR_INSTALL_DIR = PHP_LIBDIR . DIRECTORY_SEPARATOR . 'pear'; +} else { + $PEAR_INSTALL_DIR = PEAR_INSTALL_DIR; +} + +// Below we define constants with default values for all configuration +// parameters except username/password. All of them can have their +// defaults set through environment variables. The reason we use the +// PHP_ prefix is for some security, PHP protects environment +// variables starting with PHP_*. + +// default channel and preferred mirror is based on whether we are invoked through +// the "pear" or the "pecl" command +if (!defined('PEAR_RUNTYPE')) { + define('PEAR_RUNTYPE', 'pear'); +} + +if (PEAR_RUNTYPE == 'pear') { + define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pear.php.net'); +} else { + define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pecl.php.net'); +} + +if (getenv('PHP_PEAR_SYSCONF_DIR')) { + define('PEAR_CONFIG_SYSCONFDIR', getenv('PHP_PEAR_SYSCONF_DIR')); +} elseif (getenv('SystemRoot')) { + define('PEAR_CONFIG_SYSCONFDIR', getenv('SystemRoot')); +} else { + define('PEAR_CONFIG_SYSCONFDIR', PHP_SYSCONFDIR); +} + +// Default for master_server +if (getenv('PHP_PEAR_MASTER_SERVER')) { + define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', getenv('PHP_PEAR_MASTER_SERVER')); +} else { + define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', 'pear.php.net'); +} + +// Default for http_proxy +if (getenv('PHP_PEAR_HTTP_PROXY')) { + define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('PHP_PEAR_HTTP_PROXY')); +} elseif (getenv('http_proxy')) { + define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('http_proxy')); +} else { + define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', ''); +} + +// Default for php_dir +if (getenv('PHP_PEAR_INSTALL_DIR')) { + define('PEAR_CONFIG_DEFAULT_PHP_DIR', getenv('PHP_PEAR_INSTALL_DIR')); +} else { + if (@file_exists($PEAR_INSTALL_DIR) && is_dir($PEAR_INSTALL_DIR)) { + define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR); + } else { + define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR); + } +} + +// Default for ext_dir +if (getenv('PHP_PEAR_EXTENSION_DIR')) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', getenv('PHP_PEAR_EXTENSION_DIR')); +} else { + if (ini_get('extension_dir')) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', ini_get('extension_dir')); + } elseif (defined('PEAR_EXTENSION_DIR') && + file_exists(PEAR_EXTENSION_DIR) && is_dir(PEAR_EXTENSION_DIR)) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', PEAR_EXTENSION_DIR); + } elseif (defined('PHP_EXTENSION_DIR')) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', PHP_EXTENSION_DIR); + } else { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', '.'); + } +} + +// Default for doc_dir +if (getenv('PHP_PEAR_DOC_DIR')) { + define('PEAR_CONFIG_DEFAULT_DOC_DIR', getenv('PHP_PEAR_DOC_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_DOC_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'docs'); +} + +// Default for bin_dir +if (getenv('PHP_PEAR_BIN_DIR')) { + define('PEAR_CONFIG_DEFAULT_BIN_DIR', getenv('PHP_PEAR_BIN_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_BIN_DIR', PHP_BINDIR); +} + +// Default for data_dir +if (getenv('PHP_PEAR_DATA_DIR')) { + define('PEAR_CONFIG_DEFAULT_DATA_DIR', getenv('PHP_PEAR_DATA_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_DATA_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'data'); +} + +// Default for cfg_dir +if (getenv('PHP_PEAR_CFG_DIR')) { + define('PEAR_CONFIG_DEFAULT_CFG_DIR', getenv('PHP_PEAR_CFG_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_CFG_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'cfg'); +} + +// Default for www_dir +if (getenv('PHP_PEAR_WWW_DIR')) { + define('PEAR_CONFIG_DEFAULT_WWW_DIR', getenv('PHP_PEAR_WWW_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_WWW_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'www'); +} + +// Default for test_dir +if (getenv('PHP_PEAR_TEST_DIR')) { + define('PEAR_CONFIG_DEFAULT_TEST_DIR', getenv('PHP_PEAR_TEST_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_TEST_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'tests'); +} + +// Default for temp_dir +if (getenv('PHP_PEAR_TEMP_DIR')) { + define('PEAR_CONFIG_DEFAULT_TEMP_DIR', getenv('PHP_PEAR_TEMP_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_TEMP_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'temp'); +} + +// Default for cache_dir +if (getenv('PHP_PEAR_CACHE_DIR')) { + define('PEAR_CONFIG_DEFAULT_CACHE_DIR', getenv('PHP_PEAR_CACHE_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_CACHE_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'cache'); +} + +// Default for download_dir +if (getenv('PHP_PEAR_DOWNLOAD_DIR')) { + define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR', getenv('PHP_PEAR_DOWNLOAD_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'download'); +} + +// Default for php_bin +if (getenv('PHP_PEAR_PHP_BIN')) { + define('PEAR_CONFIG_DEFAULT_PHP_BIN', getenv('PHP_PEAR_PHP_BIN')); +} else { + define('PEAR_CONFIG_DEFAULT_PHP_BIN', PEAR_CONFIG_DEFAULT_BIN_DIR. + DIRECTORY_SEPARATOR.'php'.(OS_WINDOWS ? '.exe' : '')); +} + +// Default for verbose +if (getenv('PHP_PEAR_VERBOSE')) { + define('PEAR_CONFIG_DEFAULT_VERBOSE', getenv('PHP_PEAR_VERBOSE')); +} else { + define('PEAR_CONFIG_DEFAULT_VERBOSE', 1); +} + +// Default for preferred_state +if (getenv('PHP_PEAR_PREFERRED_STATE')) { + define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', getenv('PHP_PEAR_PREFERRED_STATE')); +} else { + define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', 'stable'); +} + +// Default for umask +if (getenv('PHP_PEAR_UMASK')) { + define('PEAR_CONFIG_DEFAULT_UMASK', getenv('PHP_PEAR_UMASK')); +} else { + define('PEAR_CONFIG_DEFAULT_UMASK', decoct(umask())); +} + +// Default for cache_ttl +if (getenv('PHP_PEAR_CACHE_TTL')) { + define('PEAR_CONFIG_DEFAULT_CACHE_TTL', getenv('PHP_PEAR_CACHE_TTL')); +} else { + define('PEAR_CONFIG_DEFAULT_CACHE_TTL', 3600); +} + +// Default for sig_type +if (getenv('PHP_PEAR_SIG_TYPE')) { + define('PEAR_CONFIG_DEFAULT_SIG_TYPE', getenv('PHP_PEAR_SIG_TYPE')); +} else { + define('PEAR_CONFIG_DEFAULT_SIG_TYPE', 'gpg'); +} + +// Default for sig_bin +if (getenv('PHP_PEAR_SIG_BIN')) { + define('PEAR_CONFIG_DEFAULT_SIG_BIN', getenv('PHP_PEAR_SIG_BIN')); +} else { + define('PEAR_CONFIG_DEFAULT_SIG_BIN', + System::which( + 'gpg', OS_WINDOWS ? 'c:\gnupg\gpg.exe' : '/usr/local/bin/gpg')); +} + +// Default for sig_keydir +if (getenv('PHP_PEAR_SIG_KEYDIR')) { + define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR', getenv('PHP_PEAR_SIG_KEYDIR')); +} else { + define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR', + PEAR_CONFIG_SYSCONFDIR . DIRECTORY_SEPARATOR . 'pearkeys'); +} + +/** + * This is a class for storing configuration data, keeping track of + * which are system-defined, user-defined or defaulted. + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Config extends PEAR +{ + /** + * Array of config files used. + * + * @var array layer => config file + */ + var $files = array( + 'system' => '', + 'user' => '', + ); + + var $layers = array(); + + /** + * Configuration data, two-dimensional array where the first + * dimension is the config layer ('user', 'system' and 'default'), + * and the second dimension is keyname => value. + * + * The order in the first dimension is important! Earlier + * layers will shadow later ones when a config value is + * requested (if a 'user' value exists, it will be returned first, + * then 'system' and finally 'default'). + * + * @var array layer => array(keyname => value, ...) + */ + var $configuration = array( + 'user' => array(), + 'system' => array(), + 'default' => array(), + ); + + /** + * Configuration values that can be set for a channel + * + * All other configuration values can only have a global value + * @var array + * @access private + */ + var $_channelConfigInfo = array( + 'php_dir', 'ext_dir', 'doc_dir', 'bin_dir', 'data_dir', 'cfg_dir', + 'test_dir', 'www_dir', 'php_bin', 'php_prefix', 'php_suffix', 'username', + 'password', 'verbose', 'preferred_state', 'umask', 'preferred_mirror', 'php_ini' + ); + + /** + * Channels that can be accessed + * @see setChannels() + * @var array + * @access private + */ + var $_channels = array('pear.php.net', 'pecl.php.net', '__uri'); + + /** + * This variable is used to control the directory values returned + * @see setInstallRoot(); + * @var string|false + * @access private + */ + var $_installRoot = false; + + /** + * If requested, this will always refer to the registry + * contained in php_dir + * @var PEAR_Registry + */ + var $_registry = array(); + + /** + * @var array + * @access private + */ + var $_regInitialized = array(); + + /** + * @var bool + * @access private + */ + var $_noRegistry = false; + + /** + * amount of errors found while parsing config + * @var integer + * @access private + */ + var $_errorsFound = 0; + var $_lastError = null; + + /** + * Information about the configuration data. Stores the type, + * default value and a documentation string for each configuration + * value. + * + * @var array layer => array(infotype => value, ...) + */ + var $configuration_info = array( + // Channels/Internet Access + 'default_channel' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_CHANNEL, + 'doc' => 'the default channel to use for all non explicit commands', + 'prompt' => 'Default Channel', + 'group' => 'Internet Access', + ), + 'preferred_mirror' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_CHANNEL, + 'doc' => 'the default server or mirror to use for channel actions', + 'prompt' => 'Default Channel Mirror', + 'group' => 'Internet Access', + ), + 'remote_config' => array( + 'type' => 'password', + 'default' => '', + 'doc' => 'ftp url of remote configuration file to use for synchronized install', + 'prompt' => 'Remote Configuration File', + 'group' => 'Internet Access', + ), + 'auto_discover' => array( + 'type' => 'integer', + 'default' => 0, + 'doc' => 'whether to automatically discover new channels', + 'prompt' => 'Auto-discover new Channels', + 'group' => 'Internet Access', + ), + // Internet Access + 'master_server' => array( + 'type' => 'string', + 'default' => 'pear.php.net', + 'doc' => 'name of the main PEAR server [NOT USED IN THIS VERSION]', + 'prompt' => 'PEAR server [DEPRECATED]', + 'group' => 'Internet Access', + ), + 'http_proxy' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_HTTP_PROXY, + 'doc' => 'HTTP proxy (host:port) to use when downloading packages', + 'prompt' => 'HTTP Proxy Server Address', + 'group' => 'Internet Access', + ), + // File Locations + 'php_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_PHP_DIR, + 'doc' => 'directory where .php files are installed', + 'prompt' => 'PEAR directory', + 'group' => 'File Locations', + ), + 'ext_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_EXT_DIR, + 'doc' => 'directory where loadable extensions are installed', + 'prompt' => 'PHP extension directory', + 'group' => 'File Locations', + ), + 'doc_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_DOC_DIR, + 'doc' => 'directory where documentation is installed', + 'prompt' => 'PEAR documentation directory', + 'group' => 'File Locations', + ), + 'bin_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_BIN_DIR, + 'doc' => 'directory where executables are installed', + 'prompt' => 'PEAR executables directory', + 'group' => 'File Locations', + ), + 'data_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_DATA_DIR, + 'doc' => 'directory where data files are installed', + 'prompt' => 'PEAR data directory', + 'group' => 'File Locations (Advanced)', + ), + 'cfg_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_CFG_DIR, + 'doc' => 'directory where modifiable configuration files are installed', + 'prompt' => 'PEAR configuration file directory', + 'group' => 'File Locations (Advanced)', + ), + 'www_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_WWW_DIR, + 'doc' => 'directory where www frontend files (html/js) are installed', + 'prompt' => 'PEAR www files directory', + 'group' => 'File Locations (Advanced)', + ), + 'test_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_TEST_DIR, + 'doc' => 'directory where regression tests are installed', + 'prompt' => 'PEAR test directory', + 'group' => 'File Locations (Advanced)', + ), + 'cache_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_CACHE_DIR, + 'doc' => 'directory which is used for web service cache', + 'prompt' => 'PEAR Installer cache directory', + 'group' => 'File Locations (Advanced)', + ), + 'temp_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_TEMP_DIR, + 'doc' => 'directory which is used for all temp files', + 'prompt' => 'PEAR Installer temp directory', + 'group' => 'File Locations (Advanced)', + ), + 'download_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR, + 'doc' => 'directory which is used for all downloaded files', + 'prompt' => 'PEAR Installer download directory', + 'group' => 'File Locations (Advanced)', + ), + 'php_bin' => array( + 'type' => 'file', + 'default' => PEAR_CONFIG_DEFAULT_PHP_BIN, + 'doc' => 'PHP CLI/CGI binary for executing scripts', + 'prompt' => 'PHP CLI/CGI binary', + 'group' => 'File Locations (Advanced)', + ), + 'php_prefix' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '--program-prefix for php_bin\'s ./configure, used for pecl installs', + 'prompt' => '--program-prefix passed to PHP\'s ./configure', + 'group' => 'File Locations (Advanced)', + ), + 'php_suffix' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '--program-suffix for php_bin\'s ./configure, used for pecl installs', + 'prompt' => '--program-suffix passed to PHP\'s ./configure', + 'group' => 'File Locations (Advanced)', + ), + 'php_ini' => array( + 'type' => 'file', + 'default' => '', + 'doc' => 'location of php.ini in which to enable PECL extensions on install', + 'prompt' => 'php.ini location', + 'group' => 'File Locations (Advanced)', + ), + // Maintainers + 'username' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '(maintainers) your PEAR account name', + 'prompt' => 'PEAR username (for maintainers)', + 'group' => 'Maintainers', + ), + 'password' => array( + 'type' => 'password', + 'default' => '', + 'doc' => '(maintainers) your PEAR account password', + 'prompt' => 'PEAR password (for maintainers)', + 'group' => 'Maintainers', + ), + // Advanced + 'verbose' => array( + 'type' => 'integer', + 'default' => PEAR_CONFIG_DEFAULT_VERBOSE, + 'doc' => 'verbosity level +0: really quiet +1: somewhat quiet +2: verbose +3: debug', + 'prompt' => 'Debug Log Level', + 'group' => 'Advanced', + ), + 'preferred_state' => array( + 'type' => 'set', + 'default' => PEAR_CONFIG_DEFAULT_PREFERRED_STATE, + 'doc' => 'the installer will prefer releases with this state when installing packages without a version or state specified', + 'valid_set' => array( + 'stable', 'beta', 'alpha', 'devel', 'snapshot'), + 'prompt' => 'Preferred Package State', + 'group' => 'Advanced', + ), + 'umask' => array( + 'type' => 'mask', + 'default' => PEAR_CONFIG_DEFAULT_UMASK, + 'doc' => 'umask used when creating files (Unix-like systems only)', + 'prompt' => 'Unix file mask', + 'group' => 'Advanced', + ), + 'cache_ttl' => array( + 'type' => 'integer', + 'default' => PEAR_CONFIG_DEFAULT_CACHE_TTL, + 'doc' => 'amount of secs where the local cache is used and not updated', + 'prompt' => 'Cache TimeToLive', + 'group' => 'Advanced', + ), + 'sig_type' => array( + 'type' => 'set', + 'default' => PEAR_CONFIG_DEFAULT_SIG_TYPE, + 'doc' => 'which package signature mechanism to use', + 'valid_set' => array('gpg'), + 'prompt' => 'Package Signature Type', + 'group' => 'Maintainers', + ), + 'sig_bin' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_SIG_BIN, + 'doc' => 'which package signature mechanism to use', + 'prompt' => 'Signature Handling Program', + 'group' => 'Maintainers', + ), + 'sig_keyid' => array( + 'type' => 'string', + 'default' => '', + 'doc' => 'which key to use for signing with', + 'prompt' => 'Signature Key Id', + 'group' => 'Maintainers', + ), + 'sig_keydir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_SIG_KEYDIR, + 'doc' => 'directory where signature keys are located', + 'prompt' => 'Signature Key Directory', + 'group' => 'Maintainers', + ), + // __channels is reserved - used for channel-specific configuration + ); + + /** + * Constructor. + * + * @param string file to read user-defined options from + * @param string file to read system-wide defaults from + * @param bool determines whether a registry object "follows" + * the value of php_dir (is automatically created + * and moved when php_dir is changed) + * @param bool if true, fails if configuration files cannot be loaded + * + * @access public + * + * @see PEAR_Config::singleton + */ + function PEAR_Config($user_file = '', $system_file = '', $ftp_file = false, + $strict = true) + { + $this->PEAR(); + PEAR_Installer_Role::initializeConfig($this); + $sl = DIRECTORY_SEPARATOR; + if (empty($user_file)) { + if (OS_WINDOWS) { + $user_file = PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini'; + } else { + $user_file = getenv('HOME') . $sl . '.pearrc'; + } + } + + if (empty($system_file)) { + $system_file = PEAR_CONFIG_SYSCONFDIR . $sl; + if (OS_WINDOWS) { + $system_file .= 'pearsys.ini'; + } else { + $system_file .= 'pear.conf'; + } + } + + $this->layers = array_keys($this->configuration); + $this->files['user'] = $user_file; + $this->files['system'] = $system_file; + if ($user_file && file_exists($user_file)) { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->readConfigFile($user_file, 'user', $strict); + $this->popErrorHandling(); + if ($this->_errorsFound > 0) { + return; + } + } + + if ($system_file && @file_exists($system_file)) { + $this->mergeConfigFile($system_file, false, 'system', $strict); + if ($this->_errorsFound > 0) { + return; + } + + } + + if (!$ftp_file) { + $ftp_file = $this->get('remote_config'); + } + + if ($ftp_file && defined('PEAR_REMOTEINSTALL_OK')) { + $this->readFTPConfigFile($ftp_file); + } + + foreach ($this->configuration_info as $key => $info) { + $this->configuration['default'][$key] = $info['default']; + } + + $this->_registry['default'] = &new PEAR_Registry($this->configuration['default']['php_dir']); + $this->_registry['default']->setConfig($this, false); + $this->_regInitialized['default'] = false; + //$GLOBALS['_PEAR_Config_instance'] = &$this; + } + + /** + * Return the default locations of user and system configuration files + * @static + */ + function getDefaultConfigFiles() + { + $sl = DIRECTORY_SEPARATOR; + if (OS_WINDOWS) { + return array( + 'user' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini', + 'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pearsys.ini' + ); + } + + return array( + 'user' => getenv('HOME') . $sl . '.pearrc', + 'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.conf' + ); + } + + /** + * Static singleton method. If you want to keep only one instance + * of this class in use, this method will give you a reference to + * the last created PEAR_Config object if one exists, or create a + * new object. + * + * @param string (optional) file to read user-defined options from + * @param string (optional) file to read system-wide defaults from + * + * @return object an existing or new PEAR_Config instance + * + * @access public + * + * @see PEAR_Config::PEAR_Config + */ + function &singleton($user_file = '', $system_file = '', $strict = true) + { + if (is_object($GLOBALS['_PEAR_Config_instance'])) { + return $GLOBALS['_PEAR_Config_instance']; + } + + $t_conf = &new PEAR_Config($user_file, $system_file, false, $strict); + if ($t_conf->_errorsFound > 0) { + return $t_conf->lastError; + } + + $GLOBALS['_PEAR_Config_instance'] = &$t_conf; + return $GLOBALS['_PEAR_Config_instance']; + } + + /** + * Determine whether any configuration files have been detected, and whether a + * registry object can be retrieved from this configuration. + * @return bool + * @since PEAR 1.4.0a1 + */ + function validConfiguration() + { + if ($this->isDefinedLayer('user') || $this->isDefinedLayer('system')) { + return true; + } + + return false; + } + + /** + * Reads configuration data from a file. All existing values in + * the config layer are discarded and replaced with data from the + * file. + * @param string file to read from, if NULL or not specified, the + * last-used file for the same layer (second param) is used + * @param string config layer to insert data into ('user' or 'system') + * @return bool TRUE on success or a PEAR error on failure + */ + function readConfigFile($file = null, $layer = 'user', $strict = true) + { + if (empty($this->files[$layer])) { + return $this->raiseError("unknown config layer `$layer'"); + } + + if ($file === null) { + $file = $this->files[$layer]; + } + + $data = $this->_readConfigDataFrom($file); + if (PEAR::isError($data)) { + if (!$strict) { + return true; + } + + $this->_errorsFound++; + $this->lastError = $data; + + return $data; + } + + $this->files[$layer] = $file; + $this->_decodeInput($data); + $this->configuration[$layer] = $data; + $this->_setupChannels(); + if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + } + return true; + } + + /** + * @param string url to the remote config file, like ftp://www.example.com/pear/config.ini + * @return true|PEAR_Error + */ + function readFTPConfigFile($path) + { + do { // poor man's try + if (!class_exists('PEAR_FTP')) { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + if (PEAR_Common::isIncludeable('PEAR/FTP.php')) { + require_once 'PEAR/FTP.php'; + } + } + + if (!class_exists('PEAR_FTP')) { + return PEAR::raiseError('PEAR_RemoteInstaller must be installed to use remote config'); + } + + $this->_ftp = &new PEAR_FTP; + $this->_ftp->pushErrorHandling(PEAR_ERROR_RETURN); + $e = $this->_ftp->init($path); + if (PEAR::isError($e)) { + $this->_ftp->popErrorHandling(); + return $e; + } + + $tmp = System::mktemp('-d'); + PEAR_Common::addTempFile($tmp); + $e = $this->_ftp->get(basename($path), $tmp . DIRECTORY_SEPARATOR . + 'pear.ini', false, FTP_BINARY); + if (PEAR::isError($e)) { + $this->_ftp->popErrorHandling(); + return $e; + } + + PEAR_Common::addTempFile($tmp . DIRECTORY_SEPARATOR . 'pear.ini'); + $this->_ftp->disconnect(); + $this->_ftp->popErrorHandling(); + $this->files['ftp'] = $tmp . DIRECTORY_SEPARATOR . 'pear.ini'; + $e = $this->readConfigFile(null, 'ftp'); + if (PEAR::isError($e)) { + return $e; + } + + $fail = array(); + foreach ($this->configuration_info as $key => $val) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + // any directory configs must be set for this to work + if (!isset($this->configuration['ftp'][$key])) { + $fail[] = $key; + } + } + } + + if (!count($fail)) { + return true; + } + + $fail = '"' . implode('", "', $fail) . '"'; + unset($this->files['ftp']); + unset($this->configuration['ftp']); + return PEAR::raiseError('ERROR: Ftp configuration file must set all ' . + 'directory configuration variables. These variables were not set: ' . + $fail); + } while (false); // poor man's catch + unset($this->files['ftp']); + return PEAR::raiseError('no remote host specified'); + } + + /** + * Reads the existing configurations and creates the _channels array from it + */ + function _setupChannels() + { + $set = array_flip(array_values($this->_channels)); + foreach ($this->configuration as $layer => $data) { + $i = 1000; + if (isset($data['__channels']) && is_array($data['__channels'])) { + foreach ($data['__channels'] as $channel => $info) { + $set[$channel] = $i++; + } + } + } + $this->_channels = array_values(array_flip($set)); + $this->setChannels($this->_channels); + } + + function deleteChannel($channel) + { + $ch = strtolower($channel); + foreach ($this->configuration as $layer => $data) { + if (isset($data['__channels']) && isset($data['__channels'][$ch])) { + unset($this->configuration[$layer]['__channels'][$ch]); + } + } + + $this->_channels = array_flip($this->_channels); + unset($this->_channels[$ch]); + $this->_channels = array_flip($this->_channels); + } + + /** + * Merges data into a config layer from a file. Does the same + * thing as readConfigFile, except it does not replace all + * existing values in the config layer. + * @param string file to read from + * @param bool whether to overwrite existing data (default TRUE) + * @param string config layer to insert data into ('user' or 'system') + * @param string if true, errors are returned if file opening fails + * @return bool TRUE on success or a PEAR error on failure + */ + function mergeConfigFile($file, $override = true, $layer = 'user', $strict = true) + { + if (empty($this->files[$layer])) { + return $this->raiseError("unknown config layer `$layer'"); + } + + if ($file === null) { + $file = $this->files[$layer]; + } + + $data = $this->_readConfigDataFrom($file); + if (PEAR::isError($data)) { + if (!$strict) { + return true; + } + + $this->_errorsFound++; + $this->lastError = $data; + + return $data; + } + + $this->_decodeInput($data); + if ($override) { + $this->configuration[$layer] = + PEAR_Config::arrayMergeRecursive($this->configuration[$layer], $data); + } else { + $this->configuration[$layer] = + PEAR_Config::arrayMergeRecursive($data, $this->configuration[$layer]); + } + + $this->_setupChannels(); + if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + } + return true; + } + + /** + * @param array + * @param array + * @return array + * @static + */ + function arrayMergeRecursive($arr2, $arr1) + { + $ret = array(); + foreach ($arr2 as $key => $data) { + if (!isset($arr1[$key])) { + $ret[$key] = $data; + unset($arr1[$key]); + continue; + } + if (is_array($data)) { + if (!is_array($arr1[$key])) { + $ret[$key] = $arr1[$key]; + unset($arr1[$key]); + continue; + } + $ret[$key] = PEAR_Config::arrayMergeRecursive($arr1[$key], $arr2[$key]); + unset($arr1[$key]); + } + } + + return array_merge($ret, $arr1); + } + + /** + * Writes data into a config layer from a file. + * + * @param string|null file to read from, or null for default + * @param string config layer to insert data into ('user' or + * 'system') + * @param string|null data to write to config file or null for internal data [DEPRECATED] + * @return bool TRUE on success or a PEAR error on failure + */ + function writeConfigFile($file = null, $layer = 'user', $data = null) + { + $this->_lazyChannelSetup($layer); + if ($layer == 'both' || $layer == 'all') { + foreach ($this->files as $type => $file) { + $err = $this->writeConfigFile($file, $type, $data); + if (PEAR::isError($err)) { + return $err; + } + } + return true; + } + + if (empty($this->files[$layer])) { + return $this->raiseError("unknown config file type `$layer'"); + } + + if ($file === null) { + $file = $this->files[$layer]; + } + + $data = ($data === null) ? $this->configuration[$layer] : $data; + $this->_encodeOutput($data); + $opt = array('-p', dirname($file)); + if (!@System::mkDir($opt)) { + return $this->raiseError("could not create directory: " . dirname($file)); + } + + if (file_exists($file) && is_file($file) && !is_writeable($file)) { + return $this->raiseError("no write access to $file!"); + } + + $fp = @fopen($file, "w"); + if (!$fp) { + return $this->raiseError("PEAR_Config::writeConfigFile fopen('$file','w') failed ($php_errormsg)"); + } + + $contents = "#PEAR_Config 0.9\n" . serialize($data); + if (!@fwrite($fp, $contents)) { + return $this->raiseError("PEAR_Config::writeConfigFile: fwrite failed ($php_errormsg)"); + } + return true; + } + + /** + * Reads configuration data from a file and returns the parsed data + * in an array. + * + * @param string file to read from + * @return array configuration data or a PEAR error on failure + * @access private + */ + function _readConfigDataFrom($file) + { + $fp = false; + if (file_exists($file)) { + $fp = @fopen($file, "r"); + } + + if (!$fp) { + return $this->raiseError("PEAR_Config::readConfigFile fopen('$file','r') failed"); + } + + $size = filesize($file); + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + fclose($fp); + $contents = file_get_contents($file); + if (empty($contents)) { + return $this->raiseError('Configuration file "' . $file . '" is empty'); + } + + set_magic_quotes_runtime($rt); + + $version = false; + if (preg_match('/^#PEAR_Config\s+(\S+)\s+/si', $contents, $matches)) { + $version = $matches[1]; + $contents = substr($contents, strlen($matches[0])); + } else { + // Museum config file + if (substr($contents,0,2) == 'a:') { + $version = '0.1'; + } + } + + if ($version && version_compare("$version", '1', '<')) { + // no '@', it is possible that unserialize + // raises a notice but it seems to block IO to + // STDOUT if a '@' is used and a notice is raise + $data = unserialize($contents); + + if (!is_array($data) && !$data) { + if ($contents == serialize(false)) { + $data = array(); + } else { + $err = $this->raiseError("PEAR_Config: bad data in $file"); + return $err; + } + } + if (!is_array($data)) { + if (strlen(trim($contents)) > 0) { + $error = "PEAR_Config: bad data in $file"; + $err = $this->raiseError($error); + return $err; + } + + $data = array(); + } + // add parsing of newer formats here... + } else { + $err = $this->raiseError("$file: unknown version `$version'"); + return $err; + } + + return $data; + } + + /** + * Gets the file used for storing the config for a layer + * + * @param string $layer 'user' or 'system' + */ + function getConfFile($layer) + { + return $this->files[$layer]; + } + + /** + * @param string Configuration class name, used for detecting duplicate calls + * @param array information on a role as parsed from its xml file + * @return true|PEAR_Error + * @access private + */ + function _addConfigVars($class, $vars) + { + static $called = array(); + if (isset($called[$class])) { + return; + } + + $called[$class] = 1; + if (count($vars) > 3) { + return $this->raiseError('Roles can only define 3 new config variables or less'); + } + + foreach ($vars as $name => $var) { + if (!is_array($var)) { + return $this->raiseError('Configuration information must be an array'); + } + + if (!isset($var['type'])) { + return $this->raiseError('Configuration information must contain a type'); + } elseif (!in_array($var['type'], + array('string', 'mask', 'password', 'directory', 'file', 'set'))) { + return $this->raiseError( + 'Configuration type must be one of directory, file, string, ' . + 'mask, set, or password'); + } + if (!isset($var['default'])) { + return $this->raiseError( + 'Configuration information must contain a default value ("default" index)'); + } + + if (is_array($var['default'])) { + $real_default = ''; + foreach ($var['default'] as $config_var => $val) { + if (strpos($config_var, 'text') === 0) { + $real_default .= $val; + } elseif (strpos($config_var, 'constant') === 0) { + if (!defined($val)) { + return $this->raiseError( + 'Unknown constant "' . $val . '" requested in ' . + 'default value for configuration variable "' . + $name . '"'); + } + + $real_default .= constant($val); + } elseif (isset($this->configuration_info[$config_var])) { + $real_default .= + $this->configuration_info[$config_var]['default']; + } else { + return $this->raiseError( + 'Unknown request for "' . $config_var . '" value in ' . + 'default value for configuration variable "' . + $name . '"'); + } + } + $var['default'] = $real_default; + } + + if ($var['type'] == 'integer') { + $var['default'] = (integer) $var['default']; + } + + if (!isset($var['doc'])) { + return $this->raiseError( + 'Configuration information must contain a summary ("doc" index)'); + } + + if (!isset($var['prompt'])) { + return $this->raiseError( + 'Configuration information must contain a simple prompt ("prompt" index)'); + } + + if (!isset($var['group'])) { + return $this->raiseError( + 'Configuration information must contain a simple group ("group" index)'); + } + + if (isset($this->configuration_info[$name])) { + return $this->raiseError('Configuration variable "' . $name . + '" already exists'); + } + + $this->configuration_info[$name] = $var; + // fix bug #7351: setting custom config variable in a channel fails + $this->_channelConfigInfo[] = $name; + } + + return true; + } + + /** + * Encodes/scrambles configuration data before writing to files. + * Currently, 'password' values will be base64-encoded as to avoid + * that people spot cleartext passwords by accident. + * + * @param array (reference) array to encode values in + * @return bool TRUE on success + * @access private + */ + function _encodeOutput(&$data) + { + foreach ($data as $key => $value) { + if ($key == '__channels') { + foreach ($data['__channels'] as $channel => $blah) { + $this->_encodeOutput($data['__channels'][$channel]); + } + } + + if (!isset($this->configuration_info[$key])) { + continue; + } + + $type = $this->configuration_info[$key]['type']; + switch ($type) { + // we base64-encode passwords so they are at least + // not shown in plain by accident + case 'password': { + $data[$key] = base64_encode($data[$key]); + break; + } + case 'mask': { + $data[$key] = octdec($data[$key]); + break; + } + } + } + + return true; + } + + /** + * Decodes/unscrambles configuration data after reading from files. + * + * @param array (reference) array to encode values in + * @return bool TRUE on success + * @access private + * + * @see PEAR_Config::_encodeOutput + */ + function _decodeInput(&$data) + { + if (!is_array($data)) { + return true; + } + + foreach ($data as $key => $value) { + if ($key == '__channels') { + foreach ($data['__channels'] as $channel => $blah) { + $this->_decodeInput($data['__channels'][$channel]); + } + } + + if (!isset($this->configuration_info[$key])) { + continue; + } + + $type = $this->configuration_info[$key]['type']; + switch ($type) { + case 'password': { + $data[$key] = base64_decode($data[$key]); + break; + } + case 'mask': { + $data[$key] = decoct($data[$key]); + break; + } + } + } + + return true; + } + + /** + * Retrieve the default channel. + * + * On startup, channels are not initialized, so if the default channel is not + * pear.php.net, then initialize the config. + * @param string registry layer + * @return string|false + */ + function getDefaultChannel($layer = null) + { + $ret = false; + if ($layer === null) { + foreach ($this->layers as $layer) { + if (isset($this->configuration[$layer]['default_channel'])) { + $ret = $this->configuration[$layer]['default_channel']; + break; + } + } + } elseif (isset($this->configuration[$layer]['default_channel'])) { + $ret = $this->configuration[$layer]['default_channel']; + } + + if ($ret == 'pear.php.net' && defined('PEAR_RUNTYPE') && PEAR_RUNTYPE == 'pecl') { + $ret = 'pecl.php.net'; + } + + if ($ret) { + if ($ret != 'pear.php.net') { + $this->_lazyChannelSetup(); + } + + return $ret; + } + + return PEAR_CONFIG_DEFAULT_CHANNEL; + } + + /** + * Returns a configuration value, prioritizing layers as per the + * layers property. + * + * @param string config key + * @return mixed the config value, or NULL if not found + * @access public + */ + function get($key, $layer = null, $channel = false) + { + if (!isset($this->configuration_info[$key])) { + return null; + } + + if ($key == '__channels') { + return null; + } + + if ($key == 'default_channel') { + return $this->getDefaultChannel($layer); + } + + if (!$channel) { + $channel = $this->getDefaultChannel(); + } elseif ($channel != 'pear.php.net') { + $this->_lazyChannelSetup(); + } + $channel = strtolower($channel); + + $test = (in_array($key, $this->_channelConfigInfo)) ? + $this->_getChannelValue($key, $layer, $channel) : + null; + if ($test !== null) { + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + return $test; + } + + if ($layer === null) { + foreach ($this->layers as $layer) { + if (isset($this->configuration[$layer][$key])) { + $test = $this->configuration[$layer][$key]; + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + + if ($key == 'preferred_mirror') { + $reg = &$this->getRegistry(); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($test) && $chan->getName() != $test) { + return $channel; // mirror does not exist + } + } + } + return $test; + } + } + } elseif (isset($this->configuration[$layer][$key])) { + $test = $this->configuration[$layer][$key]; + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + + if ($key == 'preferred_mirror') { + $reg = &$this->getRegistry(); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($test) && $chan->getName() != $test) { + return $channel; // mirror does not exist + } + } + } + + return $test; + } + + return null; + } + + /** + * Returns a channel-specific configuration value, prioritizing layers as per the + * layers property. + * + * @param string config key + * @return mixed the config value, or NULL if not found + * @access private + */ + function _getChannelValue($key, $layer, $channel) + { + if ($key == '__channels' || $channel == 'pear.php.net') { + return null; + } + + $ret = null; + if ($layer === null) { + foreach ($this->layers as $ilayer) { + if (isset($this->configuration[$ilayer]['__channels'][$channel][$key])) { + $ret = $this->configuration[$ilayer]['__channels'][$channel][$key]; + break; + } + } + } elseif (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + $ret = $this->configuration[$layer]['__channels'][$channel][$key]; + } + + if ($key != 'preferred_mirror') { + return $ret; + } + + + if ($ret !== null) { + $reg = &$this->getRegistry($layer); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($ret) && $chan->getName() != $ret) { + return $channel; // mirror does not exist + } + } + + return $ret; + } + + if ($channel != $this->getDefaultChannel($layer)) { + return $channel; // we must use the channel name as the preferred mirror + // if the user has not chosen an alternate + } + + return $this->getDefaultChannel($layer); + } + + /** + * Set a config value in a specific layer (defaults to 'user'). + * Enforces the types defined in the configuration_info array. An + * integer config variable will be cast to int, and a set config + * variable will be validated against its legal values. + * + * @param string config key + * @param string config value + * @param string (optional) config layer + * @param string channel to set this value for, or null for global value + * @return bool TRUE on success, FALSE on failure + */ + function set($key, $value, $layer = 'user', $channel = false) + { + if ($key == '__channels') { + return false; + } + + if (!isset($this->configuration[$layer])) { + return false; + } + + if ($key == 'default_channel') { + // can only set this value globally + $channel = 'pear.php.net'; + if ($value != 'pear.php.net') { + $this->_lazyChannelSetup($layer); + } + } + + if ($key == 'preferred_mirror') { + if ($channel == '__uri') { + return false; // can't set the __uri pseudo-channel's mirror + } + + $reg = &$this->getRegistry($layer); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel ? $channel : 'pear.php.net'); + if (PEAR::isError($chan)) { + return false; + } + + if (!$chan->getMirror($value) && $chan->getName() != $value) { + return false; // mirror does not exist + } + } + } + + if (!isset($this->configuration_info[$key])) { + return false; + } + + extract($this->configuration_info[$key]); + switch ($type) { + case 'integer': + $value = (int)$value; + break; + case 'set': { + // If a valid_set is specified, require the value to + // be in the set. If there is no valid_set, accept + // any value. + if ($valid_set) { + reset($valid_set); + if ((key($valid_set) === 0 && !in_array($value, $valid_set)) || + (key($valid_set) !== 0 && empty($valid_set[$value]))) + { + return false; + } + } + break; + } + } + + if (!$channel) { + $channel = $this->get('default_channel', null, 'pear.php.net'); + } + + if (!in_array($channel, $this->_channels)) { + $this->_lazyChannelSetup($layer); + $reg = &$this->getRegistry($layer); + if ($reg) { + $channel = $reg->channelName($channel); + } + + if (!in_array($channel, $this->_channels)) { + return false; + } + } + + if ($channel != 'pear.php.net') { + if (in_array($key, $this->_channelConfigInfo)) { + $this->configuration[$layer]['__channels'][$channel][$key] = $value; + return true; + } + + return false; + } + + if ($key == 'default_channel') { + if (!isset($reg)) { + $reg = &$this->getRegistry($layer); + if (!$reg) { + $reg = &$this->getRegistry(); + } + } + + if ($reg) { + $value = $reg->channelName($value); + } + + if (!$value) { + return false; + } + } + + $this->configuration[$layer][$key] = $value; + if ($key == 'php_dir' && !$this->_noRegistry) { + if (!isset($this->_registry[$layer]) || + $value != $this->_registry[$layer]->install_dir) { + $this->_registry[$layer] = &new PEAR_Registry($value); + $this->_regInitialized[$layer] = false; + $this->_registry[$layer]->setConfig($this, false); + } + } + + return true; + } + + function _lazyChannelSetup($uselayer = false) + { + if ($this->_noRegistry) { + return; + } + + $merge = false; + foreach ($this->_registry as $layer => $p) { + if ($uselayer && $uselayer != $layer) { + continue; + } + + if (!$this->_regInitialized[$layer]) { + if ($layer == 'default' && isset($this->_registry['user']) || + isset($this->_registry['system'])) { + // only use the default registry if there are no alternatives + continue; + } + + if (!is_object($this->_registry[$layer])) { + if ($phpdir = $this->get('php_dir', $layer, 'pear.php.net')) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + return; + } + } + + $this->setChannels($this->_registry[$layer]->listChannels(), $merge); + $this->_regInitialized[$layer] = true; + $merge = true; + } + } + } + + /** + * Set the list of channels. + * + * This should be set via a call to {@link PEAR_Registry::listChannels()} + * @param array + * @param bool + * @return bool success of operation + */ + function setChannels($channels, $merge = false) + { + if (!is_array($channels)) { + return false; + } + + if ($merge) { + $this->_channels = array_merge($this->_channels, $channels); + } else { + $this->_channels = $channels; + } + + foreach ($channels as $channel) { + $channel = strtolower($channel); + if ($channel == 'pear.php.net') { + continue; + } + + foreach ($this->layers as $layer) { + if (!isset($this->configuration[$layer]['__channels'])) { + $this->configuration[$layer]['__channels'] = array(); + } + if (!isset($this->configuration[$layer]['__channels'][$channel]) + || !is_array($this->configuration[$layer]['__channels'][$channel])) { + $this->configuration[$layer]['__channels'][$channel] = array(); + } + } + } + + return true; + } + + /** + * Get the type of a config value. + * + * @param string config key + * + * @return string type, one of "string", "integer", "file", + * "directory", "set" or "password". + * + * @access public + * + */ + function getType($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['type']; + } + return false; + } + + /** + * Get the documentation for a config value. + * + * @param string config key + * @return string documentation string + * + * @access public + * + */ + function getDocs($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['doc']; + } + + return false; + } + + /** + * Get the short documentation for a config value. + * + * @param string config key + * @return string short documentation string + * + * @access public + * + */ + function getPrompt($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['prompt']; + } + + return false; + } + + /** + * Get the parameter group for a config key. + * + * @param string config key + * @return string parameter group + * + * @access public + * + */ + function getGroup($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['group']; + } + + return false; + } + + /** + * Get the list of parameter groups. + * + * @return array list of parameter groups + * + * @access public + * + */ + function getGroups() + { + $tmp = array(); + foreach ($this->configuration_info as $key => $info) { + $tmp[$info['group']] = 1; + } + + return array_keys($tmp); + } + + /** + * Get the list of the parameters in a group. + * + * @param string $group parameter group + * @return array list of parameters in $group + * + * @access public + * + */ + function getGroupKeys($group) + { + $keys = array(); + foreach ($this->configuration_info as $key => $info) { + if ($info['group'] == $group) { + $keys[] = $key; + } + } + + return $keys; + } + + /** + * Get the list of allowed set values for a config value. Returns + * NULL for config values that are not sets. + * + * @param string config key + * @return array enumerated array of set values, or NULL if the + * config key is unknown or not a set + * + * @access public + * + */ + function getSetValues($key) + { + if (isset($this->configuration_info[$key]) && + isset($this->configuration_info[$key]['type']) && + $this->configuration_info[$key]['type'] == 'set') + { + $valid_set = $this->configuration_info[$key]['valid_set']; + reset($valid_set); + if (key($valid_set) === 0) { + return $valid_set; + } + + return array_keys($valid_set); + } + + return null; + } + + /** + * Get all the current config keys. + * + * @return array simple array of config keys + * + * @access public + */ + function getKeys() + { + $keys = array(); + foreach ($this->layers as $layer) { + $test = $this->configuration[$layer]; + if (isset($test['__channels'])) { + foreach ($test['__channels'] as $channel => $configs) { + $keys = array_merge($keys, $configs); + } + } + + unset($test['__channels']); + $keys = array_merge($keys, $test); + + } + return array_keys($keys); + } + + /** + * Remove the a config key from a specific config layer. + * + * @param string config key + * @param string (optional) config layer + * @param string (optional) channel (defaults to default channel) + * @return bool TRUE on success, FALSE on failure + * + * @access public + */ + function remove($key, $layer = 'user', $channel = null) + { + if ($channel === null) { + $channel = $this->getDefaultChannel(); + } + + if ($channel !== 'pear.php.net') { + if (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + unset($this->configuration[$layer]['__channels'][$channel][$key]); + return true; + } + } + + if (isset($this->configuration[$layer][$key])) { + unset($this->configuration[$layer][$key]); + return true; + } + + return false; + } + + /** + * Temporarily remove an entire config layer. USE WITH CARE! + * + * @param string config key + * @param string (optional) config layer + * @return bool TRUE on success, FALSE on failure + * + * @access public + */ + function removeLayer($layer) + { + if (isset($this->configuration[$layer])) { + $this->configuration[$layer] = array(); + return true; + } + + return false; + } + + /** + * Stores configuration data in a layer. + * + * @param string config layer to store + * @return bool TRUE on success, or PEAR error on failure + * + * @access public + */ + function store($layer = 'user', $data = null) + { + return $this->writeConfigFile(null, $layer, $data); + } + + /** + * Tells what config layer that gets to define a key. + * + * @param string config key + * @param boolean return the defining channel + * + * @return string|array the config layer, or an empty string if not found. + * + * if $returnchannel, the return is an array array('layer' => layername, + * 'channel' => channelname), or an empty string if not found + * + * @access public + */ + function definedBy($key, $returnchannel = false) + { + foreach ($this->layers as $layer) { + $channel = $this->getDefaultChannel(); + if ($channel !== 'pear.php.net') { + if (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + if ($returnchannel) { + return array('layer' => $layer, 'channel' => $channel); + } + return $layer; + } + } + + if (isset($this->configuration[$layer][$key])) { + if ($returnchannel) { + return array('layer' => $layer, 'channel' => 'pear.php.net'); + } + return $layer; + } + } + + return ''; + } + + /** + * Tells whether a given key exists as a config value. + * + * @param string config key + * @return bool whether exists in this object + * + * @access public + */ + function isDefined($key) + { + foreach ($this->layers as $layer) { + if (isset($this->configuration[$layer][$key])) { + return true; + } + } + + return false; + } + + /** + * Tells whether a given config layer exists. + * + * @param string config layer + * @return bool whether exists in this object + * + * @access public + */ + function isDefinedLayer($layer) + { + return isset($this->configuration[$layer]); + } + + /** + * Returns the layers defined (except the 'default' one) + * + * @return array of the defined layers + */ + function getLayers() + { + $cf = $this->configuration; + unset($cf['default']); + return array_keys($cf); + } + + function apiVersion() + { + return '1.1'; + } + + /** + * @return PEAR_Registry + */ + function &getRegistry($use = null) + { + $layer = $use === null ? 'user' : $use; + if (isset($this->_registry[$layer])) { + return $this->_registry[$layer]; + } elseif ($use === null && isset($this->_registry['system'])) { + return $this->_registry['system']; + } elseif ($use === null && isset($this->_registry['default'])) { + return $this->_registry['default']; + } elseif ($use) { + $a = false; + return $a; + } + + // only go here if null was passed in + echo "CRITICAL ERROR: Registry could not be initialized from any value"; + exit(1); + } + + /** + * This is to allow customization like the use of installroot + * @param PEAR_Registry + * @return bool + */ + function setRegistry(&$reg, $layer = 'user') + { + if ($this->_noRegistry) { + return false; + } + + if (!in_array($layer, array('user', 'system'))) { + return false; + } + + $this->_registry[$layer] = &$reg; + if (is_object($reg)) { + $this->_registry[$layer]->setConfig($this, false); + } + + return true; + } + + function noRegistry() + { + $this->_noRegistry = true; + } + + /** + * @return PEAR_REST + */ + function &getREST($version, $options = array()) + { + $version = str_replace('.', '', $version); + if (!class_exists($class = 'PEAR_REST_' . $version)) { + require_once 'PEAR/REST/' . $version . '.php'; + } + + $remote = &new $class($this, $options); + return $remote; + } + + /** + * The ftp server is set in {@link readFTPConfigFile()}. It exists only if a + * remote configuration file has been specified + * @return PEAR_FTP|false + */ + function &getFTP() + { + if (isset($this->_ftp)) { + return $this->_ftp; + } + + $a = false; + return $a; + } + + function _prependPath($path, $prepend) + { + if (strlen($prepend) > 0) { + if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) { + if (preg_match('/^[a-z]:/i', $prepend)) { + $prepend = substr($prepend, 2); + } elseif ($prepend{0} != '\\') { + $prepend = "\\$prepend"; + } + $path = substr($path, 0, 2) . $prepend . substr($path, 2); + } else { + $path = $prepend . $path; + } + } + return $path; + } + + /** + * @param string|false installation directory to prepend to all _dir variables, or false to + * disable + */ + function setInstallRoot($root) + { + if (substr($root, -1) == DIRECTORY_SEPARATOR) { + $root = substr($root, 0, -1); + } + $old = $this->_installRoot; + $this->_installRoot = $root; + if (($old != $root) && !$this->_noRegistry) { + foreach (array_keys($this->_registry) as $layer) { + if ($layer == 'ftp' || !isset($this->_registry[$layer])) { + continue; + } + $this->_registry[$layer] = + &new PEAR_Registry($this->get('php_dir', $layer, 'pear.php.net')); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } + } + } +} +PEAR-1.9.0/PEAR/DependencyDB.php100664 764 764 57256 100664 11071 + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: DependencyDB.php 286686 2009-08-02 17:38:57Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Needed for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Config.php'; + +$GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] = array(); +/** + * Track dependency relationships between installed packages + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Tomas V.V.Cox + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_DependencyDB +{ + // {{{ properties + + /** + * This is initialized by {@link setConfig()} + * @var PEAR_Config + * @access private + */ + var $_config; + /** + * This is initialized by {@link setConfig()} + * @var PEAR_Registry + * @access private + */ + var $_registry; + /** + * Filename of the dependency DB (usually .depdb) + * @var string + * @access private + */ + var $_depdb = false; + /** + * File name of the lockfile (usually .depdblock) + * @var string + * @access private + */ + var $_lockfile = false; + /** + * Open file resource for locking the lockfile + * @var resource|false + * @access private + */ + var $_lockFp = false; + /** + * API version of this class, used to validate a file on-disk + * @var string + * @access private + */ + var $_version = '1.0'; + /** + * Cached dependency database file + * @var array|null + * @access private + */ + var $_cache; + + // }}} + // {{{ & singleton() + + /** + * Get a raw dependency database. Calls setConfig() and assertDepsDB() + * @param PEAR_Config + * @param string|false full path to the dependency database, or false to use default + * @return PEAR_DependencyDB|PEAR_Error + * @static + */ + function &singleton(&$config, $depdb = false) + { + $phpdir = $config->get('php_dir', null, 'pear.php.net'); + if (!isset($GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir])) { + $a = new PEAR_DependencyDB; + $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir] = &$a; + $a->setConfig($config, $depdb); + $e = $a->assertDepsDB(); + if (PEAR::isError($e)) { + return $e; + } + } + + return $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir]; + } + + /** + * Set up the registry/location of dependency DB + * @param PEAR_Config|false + * @param string|false full path to the dependency database, or false to use default + */ + function setConfig(&$config, $depdb = false) + { + if (!$config) { + $this->_config = &PEAR_Config::singleton(); + } else { + $this->_config = &$config; + } + + $this->_registry = &$this->_config->getRegistry(); + if (!$depdb) { + $this->_depdb = $this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb'; + } else { + $this->_depdb = $depdb; + } + + $this->_lockfile = dirname($this->_depdb) . DIRECTORY_SEPARATOR . '.depdblock'; + } + // }}} + + function hasWriteAccess() + { + if (!file_exists($this->_depdb)) { + $dir = $this->_depdb; + while ($dir && $dir != '.') { + $dir = dirname($dir); // cd .. + if ($dir != '.' && file_exists($dir)) { + if (is_writeable($dir)) { + return true; + } + + return false; + } + } + + return false; + } + + return is_writeable($this->_depdb); + } + + // {{{ assertDepsDB() + + /** + * Create the dependency database, if it doesn't exist. Error if the database is + * newer than the code reading it. + * @return void|PEAR_Error + */ + function assertDepsDB() + { + if (!is_file($this->_depdb)) { + $this->rebuildDB(); + return; + } + + $depdb = $this->_getDepDB(); + // Datatype format has been changed, rebuild the Deps DB + if ($depdb['_version'] < $this->_version) { + $this->rebuildDB(); + } + + if ($depdb['_version']{0} > $this->_version{0}) { + return PEAR::raiseError('Dependency database is version ' . + $depdb['_version'] . ', and we are version ' . + $this->_version . ', cannot continue'); + } + } + + /** + * Get a list of installed packages that depend on this package + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + function getDependentPackages(&$pkg) + { + $data = $this->_getDepDB(); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + if (isset($data['packages'][$channel][$package])) { + return $data['packages'][$channel][$package]; + } + + return false; + } + + /** + * Get a list of the actual dependencies of installed packages that depend on + * a package. + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + function getDependentPackageDependencies(&$pkg) + { + $data = $this->_getDepDB(); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + $depend = $this->getDependentPackages($pkg); + if (!$depend) { + return false; + } + + $dependencies = array(); + foreach ($depend as $info) { + $temp = $this->getDependencies($info); + foreach ($temp as $dep) { + if ( + isset($dep['dep'], $dep['dep']['channel'], $dep['dep']['name']) && + strtolower($dep['dep']['channel']) == $channel && + strtolower($dep['dep']['name']) == $package + ) { + if (!isset($dependencies[$info['channel']])) { + $dependencies[$info['channel']] = array(); + } + + if (!isset($dependencies[$info['channel']][$info['package']])) { + $dependencies[$info['channel']][$info['package']] = array(); + } + $dependencies[$info['channel']][$info['package']][] = $dep; + } + } + } + + return $dependencies; + } + + /** + * Get a list of dependencies of this installed package + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + function getDependencies(&$pkg) + { + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + $data = $this->_getDepDB(); + if (isset($data['dependencies'][$channel][$package])) { + return $data['dependencies'][$channel][$package]; + } + + return false; + } + + /** + * Determine whether $parent depends on $child, near or deep + * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 + * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 + */ + function dependsOn($parent, $child) + { + $c = array(); + $this->_getDepDB(); + return $this->_dependsOn($parent, $child, $c); + } + + function _dependsOn($parent, $child, &$checked) + { + if (is_object($parent)) { + $channel = strtolower($parent->getChannel()); + $package = strtolower($parent->getPackage()); + } else { + $channel = strtolower($parent['channel']); + $package = strtolower($parent['package']); + } + + if (is_object($child)) { + $depchannel = strtolower($child->getChannel()); + $deppackage = strtolower($child->getPackage()); + } else { + $depchannel = strtolower($child['channel']); + $deppackage = strtolower($child['package']); + } + + if (isset($checked[$channel][$package][$depchannel][$deppackage])) { + return false; // avoid endless recursion + } + + $checked[$channel][$package][$depchannel][$deppackage] = true; + if (!isset($this->_cache['dependencies'][$channel][$package])) { + return false; + } + + foreach ($this->_cache['dependencies'][$channel][$package] as $info) { + if (isset($info['dep']['uri'])) { + if (is_object($child)) { + if ($info['dep']['uri'] == $child->getURI()) { + return true; + } + } elseif (isset($child['uri'])) { + if ($info['dep']['uri'] == $child['uri']) { + return true; + } + } + return false; + } + + if (strtolower($info['dep']['channel']) == $depchannel && + strtolower($info['dep']['name']) == $deppackage) { + return true; + } + } + + foreach ($this->_cache['dependencies'][$channel][$package] as $info) { + if (isset($info['dep']['uri'])) { + if ($this->_dependsOn(array( + 'uri' => $info['dep']['uri'], + 'package' => $info['dep']['name']), $child, $checked)) { + return true; + } + } else { + if ($this->_dependsOn(array( + 'channel' => $info['dep']['channel'], + 'package' => $info['dep']['name']), $child, $checked)) { + return true; + } + } + } + + return false; + } + + /** + * Register dependencies of a package that is being installed or upgraded + * @param PEAR_PackageFile_v2|PEAR_PackageFile_v2 + */ + function installPackage(&$package) + { + $data = $this->_getDepDB(); + unset($this->_cache); + $this->_setPackageDeps($data, $package); + $this->_writeDepDB($data); + } + + /** + * Remove dependencies of a package that is being uninstalled, or upgraded. + * + * Upgraded packages first uninstall, then install + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array If an array, then it must have + * indices 'channel' and 'package' + */ + function uninstallPackage(&$pkg) + { + $data = $this->_getDepDB(); + unset($this->_cache); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + if (!isset($data['dependencies'][$channel][$package])) { + return true; + } + + foreach ($data['dependencies'][$channel][$package] as $dep) { + $found = false; + $depchannel = isset($dep['dep']['uri']) ? '__uri' : strtolower($dep['dep']['channel']); + $depname = strtolower($dep['dep']['name']); + if (isset($data['packages'][$depchannel][$depname])) { + foreach ($data['packages'][$depchannel][$depname] as $i => $info) { + if ($info['channel'] == $channel && $info['package'] == $package) { + $found = true; + break; + } + } + } + + if ($found) { + unset($data['packages'][$depchannel][$depname][$i]); + if (!count($data['packages'][$depchannel][$depname])) { + unset($data['packages'][$depchannel][$depname]); + if (!count($data['packages'][$depchannel])) { + unset($data['packages'][$depchannel]); + } + } else { + $data['packages'][$depchannel][$depname] = + array_values($data['packages'][$depchannel][$depname]); + } + } + } + + unset($data['dependencies'][$channel][$package]); + if (!count($data['dependencies'][$channel])) { + unset($data['dependencies'][$channel]); + } + + if (!count($data['dependencies'])) { + unset($data['dependencies']); + } + + if (!count($data['packages'])) { + unset($data['packages']); + } + + $this->_writeDepDB($data); + } + + /** + * Rebuild the dependency DB by reading registry entries. + * @return true|PEAR_Error + */ + function rebuildDB() + { + $depdb = array('_version' => $this->_version); + if (!$this->hasWriteAccess()) { + // allow startup for read-only with older Registry + return $depdb; + } + + $packages = $this->_registry->listAllPackages(); + if (PEAR::isError($packages)) { + return $packages; + } + + foreach ($packages as $channel => $ps) { + foreach ($ps as $package) { + $package = $this->_registry->getPackage($package, $channel); + if (PEAR::isError($package)) { + return $package; + } + $this->_setPackageDeps($depdb, $package); + } + } + + $error = $this->_writeDepDB($depdb); + if (PEAR::isError($error)) { + return $error; + } + + $this->_cache = $depdb; + return true; + } + + /** + * Register usage of the dependency DB to prevent race conditions + * @param int one of the LOCK_* constants + * @return true|PEAR_Error + * @access private + */ + function _lock($mode = LOCK_EX) + { + if (stristr(php_uname(), 'Windows 9')) { + return true; + } + + if ($mode != LOCK_UN && is_resource($this->_lockFp)) { + // XXX does not check type of lock (LOCK_SH/LOCK_EX) + return true; + } + + $open_mode = 'w'; + // XXX People reported problems with LOCK_SH and 'w' + if ($mode === LOCK_SH) { + if (!file_exists($this->_lockfile)) { + touch($this->_lockfile); + } elseif (!is_file($this->_lockfile)) { + return PEAR::raiseError('could not create Dependency lock file, ' . + 'it exists and is not a regular file'); + } + $open_mode = 'r'; + } + + if (!is_resource($this->_lockFp)) { + $this->_lockFp = @fopen($this->_lockfile, $open_mode); + } + + if (!is_resource($this->_lockFp)) { + return PEAR::raiseError("could not create Dependency lock file" . + (isset($php_errormsg) ? ": " . $php_errormsg : "")); + } + + if (!(int)flock($this->_lockFp, $mode)) { + switch ($mode) { + case LOCK_SH: $str = 'shared'; break; + case LOCK_EX: $str = 'exclusive'; break; + case LOCK_UN: $str = 'unlock'; break; + default: $str = 'unknown'; break; + } + + return PEAR::raiseError("could not acquire $str lock ($this->_lockfile)"); + } + + return true; + } + + /** + * Release usage of dependency DB + * @return true|PEAR_Error + * @access private + */ + function _unlock() + { + $ret = $this->_lock(LOCK_UN); + if (is_resource($this->_lockFp)) { + fclose($this->_lockFp); + } + $this->_lockFp = null; + return $ret; + } + + /** + * Load the dependency database from disk, or return the cache + * @return array|PEAR_Error + */ + function _getDepDB() + { + if (!$this->hasWriteAccess()) { + return array('_version' => $this->_version); + } + + if (isset($this->_cache)) { + return $this->_cache; + } + + if (!$fp = fopen($this->_depdb, 'r')) { + $err = PEAR::raiseError("Could not open dependencies file `".$this->_depdb."'"); + return $err; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + fclose($fp); + $data = unserialize(file_get_contents($this->_depdb)); + set_magic_quotes_runtime($rt); + $this->_cache = $data; + return $data; + } + + /** + * Write out the dependency database to disk + * @param array the database + * @return true|PEAR_Error + * @access private + */ + function _writeDepDB(&$deps) + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + if (!$fp = fopen($this->_depdb, 'wb')) { + $this->_unlock(); + return PEAR::raiseError("Could not open dependencies file `".$this->_depdb."' for writing"); + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + fwrite($fp, serialize($deps)); + set_magic_quotes_runtime($rt); + fclose($fp); + $this->_unlock(); + $this->_cache = $deps; + return true; + } + + /** + * Register all dependencies from a package in the dependencies database, in essence + * "installing" the package's dependency information + * @param array the database + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @access private + */ + function _setPackageDeps(&$data, &$pkg) + { + $pkg->setConfig($this->_config); + if ($pkg->getPackagexmlVersion() == '1.0') { + $gen = &$pkg->getDefaultGenerator(); + $deps = $gen->dependenciesToV2(); + } else { + $deps = $pkg->getDeps(true); + } + + if (!$deps) { + return; + } + + if (!is_array($data)) { + $data = array(); + } + + if (!isset($data['dependencies'])) { + $data['dependencies'] = array(); + } + + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + + if (!isset($data['dependencies'][$channel])) { + $data['dependencies'][$channel] = array(); + } + + $data['dependencies'][$channel][$package] = array(); + if (isset($deps['required']['package'])) { + if (!isset($deps['required']['package'][0])) { + $deps['required']['package'] = array($deps['required']['package']); + } + + foreach ($deps['required']['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'required'); + } + } + + if (isset($deps['optional']['package'])) { + if (!isset($deps['optional']['package'][0])) { + $deps['optional']['package'] = array($deps['optional']['package']); + } + + foreach ($deps['optional']['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional'); + } + } + + if (isset($deps['required']['subpackage'])) { + if (!isset($deps['required']['subpackage'][0])) { + $deps['required']['subpackage'] = array($deps['required']['subpackage']); + } + + foreach ($deps['required']['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'required'); + } + } + + if (isset($deps['optional']['subpackage'])) { + if (!isset($deps['optional']['subpackage'][0])) { + $deps['optional']['subpackage'] = array($deps['optional']['subpackage']); + } + + foreach ($deps['optional']['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional'); + } + } + + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + foreach ($deps['group'] as $group) { + if (isset($group['package'])) { + if (!isset($group['package'][0])) { + $group['package'] = array($group['package']); + } + + foreach ($group['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional', + $group['attribs']['name']); + } + } + + if (isset($group['subpackage'])) { + if (!isset($group['subpackage'][0])) { + $group['subpackage'] = array($group['subpackage']); + } + + foreach ($group['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional', + $group['attribs']['name']); + } + } + } + } + + if ($data['dependencies'][$channel][$package] == array()) { + unset($data['dependencies'][$channel][$package]); + if (!count($data['dependencies'][$channel])) { + unset($data['dependencies'][$channel]); + } + } + } + + /** + * @param array the database + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param array the specific dependency + * @param required|optional whether this is a required or an optional dep + * @param string|false dependency group this dependency is from, or false for ordinary dep + */ + function _registerDep(&$data, &$pkg, $dep, $type, $group = false) + { + $info = array( + 'dep' => $dep, + 'type' => $type, + 'group' => $group + ); + + $dep = array_map('strtolower', $dep); + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (!isset($data['dependencies'])) { + $data['dependencies'] = array(); + } + + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + + if (!isset($data['dependencies'][$channel])) { + $data['dependencies'][$channel] = array(); + } + + if (!isset($data['dependencies'][$channel][$package])) { + $data['dependencies'][$channel][$package] = array(); + } + + $data['dependencies'][$channel][$package][] = $info; + if (isset($data['packages'][$depchannel][$dep['name']])) { + $found = false; + foreach ($data['packages'][$depchannel][$dep['name']] as $i => $p) { + if ($p['channel'] == $channel && $p['package'] == $package) { + $found = true; + break; + } + } + } else { + if (!isset($data['packages'])) { + $data['packages'] = array(); + } + + if (!isset($data['packages'][$depchannel])) { + $data['packages'][$depchannel] = array(); + } + + if (!isset($data['packages'][$depchannel][$dep['name']])) { + $data['packages'][$depchannel][$dep['name']] = array(); + } + + $found = false; + } + + if (!$found) { + $data['packages'][$depchannel][$dep['name']][] = array( + 'channel' => $channel, + 'package' => $package + ); + } + } +}PEAR-1.9.0/PEAR/Dependency2.php100664 764 764 142517 100664 10760 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Dependency2.php 286494 2009-07-29 06:57:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Required for the PEAR_VALIDATE_* constants + */ +require_once 'PEAR/Validate.php'; + +/** + * Dependency check for PEAR packages + * + * This class handles both version 1.0 and 2.0 dependencies + * WARNING: *any* changes to this class must be duplicated in the + * test_PEAR_Dependency2 class found in tests/PEAR_Dependency2/setup.php.inc, + * or unit tests will not actually validate the changes + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Dependency2 +{ + /** + * One of the PEAR_VALIDATE_* states + * @see PEAR_VALIDATE_NORMAL + * @var integer + */ + var $_state; + + /** + * Command-line options to install/upgrade/uninstall commands + * @param array + */ + var $_options; + + /** + * @var OS_Guess + */ + var $_os; + + /** + * @var PEAR_Registry + */ + var $_registry; + + /** + * @var PEAR_Config + */ + var $_config; + + /** + * @var PEAR_DependencyDB + */ + var $_dependencydb; + + /** + * Output of PEAR_Registry::parsedPackageName() + * @var array + */ + var $_currentPackage; + + /** + * @param PEAR_Config + * @param array installation options + * @param array format of PEAR_Registry::parsedPackageName() + * @param int installation state (one of PEAR_VALIDATE_*) + */ + function PEAR_Dependency2(&$config, $installoptions, $package, + $state = PEAR_VALIDATE_INSTALLING) + { + $this->_config = &$config; + if (!class_exists('PEAR_DependencyDB')) { + require_once 'PEAR/DependencyDB.php'; + } + + if (isset($installoptions['packagingroot'])) { + // make sure depdb is in the right location + $config->setInstallRoot($installoptions['packagingroot']); + } + + $this->_registry = &$config->getRegistry(); + $this->_dependencydb = &PEAR_DependencyDB::singleton($config); + if (isset($installoptions['packagingroot'])) { + $config->setInstallRoot(false); + } + + $this->_options = $installoptions; + $this->_state = $state; + if (!class_exists('OS_Guess')) { + require_once 'OS/Guess.php'; + } + + $this->_os = new OS_Guess; + $this->_currentPackage = $package; + } + + function _getExtraString($dep) + { + $extra = ' ('; + if (isset($dep['uri'])) { + return ''; + } + + if (isset($dep['recommended'])) { + $extra .= 'recommended version ' . $dep['recommended']; + } else { + if (isset($dep['min'])) { + $extra .= 'version >= ' . $dep['min']; + } + + if (isset($dep['max'])) { + if ($extra != ' (') { + $extra .= ', '; + } + $extra .= 'version <= ' . $dep['max']; + } + + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if ($extra != ' (') { + $extra .= ', '; + } + + $extra .= 'excluded versions: '; + foreach ($dep['exclude'] as $i => $exclude) { + if ($i) { + $extra .= ', '; + } + $extra .= $exclude; + } + } + } + + $extra .= ')'; + if ($extra == ' ()') { + $extra = ''; + } + + return $extra; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getPHP_OS() + { + return PHP_OS; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getsysname() + { + return $this->_os->getSysname(); + } + + /** + * Specify a dependency on an OS. Use arch for detailed os/processor information + * + * There are two generic OS dependencies that will be the most common, unix and windows. + * Other options are linux, freebsd, darwin (OS X), sunos, irix, hpux, aix + */ + function validateOsDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + if ($dep['name'] == '*') { + return true; + } + + $not = isset($dep['conflicts']) ? true : false; + switch (strtolower($dep['name'])) { + case 'windows' : + if ($not) { + if (strtolower(substr($this->getPHP_OS(), 0, 3)) == 'win') { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Cannot install %s on Windows"); + } + + return $this->warning("warning: Cannot install %s on Windows"); + } + } else { + if (strtolower(substr($this->getPHP_OS(), 0, 3)) != 'win') { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Can only install %s on Windows"); + } + + return $this->warning("warning: Can only install %s on Windows"); + } + } + break; + case 'unix' : + $unices = array('linux', 'freebsd', 'darwin', 'sunos', 'irix', 'hpux', 'aix'); + if ($not) { + if (in_array($this->getSysname(), $unices)) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Cannot install %s on any Unix system"); + } + + return $this->warning( "warning: Cannot install %s on any Unix system"); + } + } else { + if (!in_array($this->getSysname(), $unices)) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Can only install %s on a Unix system"); + } + + return $this->warning("warning: Can only install %s on a Unix system"); + } + } + break; + default : + if ($not) { + if (strtolower($dep['name']) == strtolower($this->getSysname())) { + if (!isset($this->_options['nodeps']) && + !isset($this->_options['force'])) { + return $this->raiseError('Cannot install %s on ' . $dep['name'] . + ' operating system'); + } + + return $this->warning('warning: Cannot install %s on ' . + $dep['name'] . ' operating system'); + } + } else { + if (strtolower($dep['name']) != strtolower($this->getSysname())) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('Cannot install %s on ' . + $this->getSysname() . + ' operating system, can only install on ' . $dep['name']); + } + + return $this->warning('warning: Cannot install %s on ' . + $this->getSysname() . + ' operating system, can only install on ' . $dep['name']); + } + } + } + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function matchSignature($pattern) + { + return $this->_os->matchSignature($pattern); + } + + /** + * Specify a complex dependency on an OS/processor/kernel version, + * Use OS for simple operating system dependency. + * + * This is the only dependency that accepts an eregable pattern. The pattern + * will be matched against the php_uname() output parsed by OS_Guess + */ + function validateArchDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING) { + return true; + } + + $not = isset($dep['conflicts']) ? true : false; + if (!$this->matchSignature($dep['pattern'])) { + if (!$not) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s Architecture dependency failed, does not ' . + 'match "' . $dep['pattern'] . '"'); + } + + return $this->warning('warning: %s Architecture dependency failed, does ' . + 'not match "' . $dep['pattern'] . '"'); + } + + return true; + } + + if ($not) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s Architecture dependency failed, required "' . + $dep['pattern'] . '"'); + } + + return $this->warning('warning: %s Architecture dependency failed, ' . + 'required "' . $dep['pattern'] . '"'); + } + + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function extension_loaded($name) + { + return extension_loaded($name); + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function phpversion($name = null) + { + if ($name !== null) { + return phpversion($name); + } + + return phpversion(); + } + + function validateExtensionDependency($dep, $required = true) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + $loaded = $this->extension_loaded($dep['name']); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (!isset($dep['min']) && !isset($dep['max']) && + !isset($dep['recommended']) && !isset($dep['exclude']) + ) { + if ($loaded) { + if (isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return true; + } + + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('warning: %s requires PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('%s can optionally use PHP extension "' . + $dep['name'] . '"' . $extra); + } + + if (!$loaded) { + if (isset($dep['conflicts'])) { + return true; + } + + if (!$required) { + return $this->warning('%s can optionally use PHP extension "' . + $dep['name'] . '"' . $extra); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . $dep['name'] . + '"' . $extra); + } + + return $this->warning('warning: %s requires PHP extension "' . $dep['name'] . + '"' . $extra); + } + + $version = (string) $this->phpversion($dep['name']); + if (empty($version)) { + $version = '0'; + } + + $fail = false; + if (isset($dep['min']) && !version_compare($version, $dep['min'], '>=')) { + $fail = true; + } + + if (isset($dep['max']) && !version_compare($version, $dep['max'], '<=')) { + $fail = true; + } + + if ($fail && !isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . $dep['name'] . + '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP extension "' . $dep['name'] . + '"' . $extra . ', installed version is ' . $version); + } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail && isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + if (isset($dep['exclude'])) { + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==')) { + if (isset($dep['conflicts'])) { + continue; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PHP extension "' . + $dep['name'] . '" version ' . + $exclude); + } + + return $this->warning('warning: %s is not compatible with PHP extension "' . + $dep['name'] . '" version ' . + $exclude); + } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + } + } + + if (isset($dep['recommended'])) { + if (version_compare($version, $dep['recommended'], '==')) { + return true; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s dependency: PHP extension ' . $dep['name'] . + ' version "' . $version . '"' . + ' is not the recommended version "' . $dep['recommended'] . + '", but may be compatible, use --force to install'); + } + + return $this->warning('warning: %s dependency: PHP extension ' . + $dep['name'] . ' version "' . $version . '"' . + ' is not the recommended version "' . $dep['recommended'].'"'); + } + + return true; + } + + function validatePhpDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + $version = $this->phpversion(); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (isset($dep['min'])) { + if (!version_compare($version, $dep['min'], '>=')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP' . + $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP' . + $extra . ', installed version is ' . $version); + } + } + + if (isset($dep['max'])) { + if (!version_compare($version, $dep['max'], '<=')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP' . + $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP' . + $extra . ', installed version is ' . $version); + } + } + + if (isset($dep['exclude'])) { + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PHP version ' . + $exclude); + } + + return $this->warning( + 'warning: %s is not compatible with PHP version ' . + $exclude); + } + } + } + + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getPEARVersion() + { + return '1.9.0'; + } + + function validatePearinstallerDependency($dep) + { + $pearversion = $this->getPEARVersion(); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (version_compare($pearversion, $dep['min'], '<')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + return $this->warning('warning: %s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + if (isset($dep['max'])) { + if (version_compare($pearversion, $dep['max'], '>')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + return $this->warning('warning: %s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + } + + if (isset($dep['exclude'])) { + if (!isset($dep['exclude'][0])) { + $dep['exclude'] = array($dep['exclude']); + } + + foreach ($dep['exclude'] as $exclude) { + if (version_compare($exclude, $pearversion, '==')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PEAR Installer ' . + 'version ' . $exclude); + } + + return $this->warning('warning: %s is not compatible with PEAR ' . + 'Installer version ' . $exclude); + } + } + } + + return true; + } + + function validateSubpackageDependency($dep, $required, $params) + { + return $this->validatePackageDependency($dep, $required, $params); + } + + /** + * @param array dependency information (2.0 format) + * @param boolean whether this is a required dependency + * @param array a list of downloaded packages to be installed, if any + * @param boolean if true, then deps on pear.php.net that fail will also check + * against pecl.php.net packages to accomodate extensions that have + * moved to pecl.php.net from pear.php.net + */ + function validatePackageDependency($dep, $required, $params, $depv1 = false) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + if (isset($dep['providesextension'])) { + if ($this->extension_loaded($dep['providesextension'])) { + $save = $dep; + $subdep = $dep; + $subdep['name'] = $subdep['providesextension']; + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->validateExtensionDependency($subdep, $required); + PEAR::popErrorHandling(); + if (!PEAR::isError($ret)) { + return true; + } + } + } + + if ($this->_state == PEAR_VALIDATE_INSTALLING) { + return $this->_validatePackageInstall($dep, $required, $depv1); + } + + if ($this->_state == PEAR_VALIDATE_DOWNLOADING) { + return $this->_validatePackageDownload($dep, $required, $params, $depv1); + } + } + + function _validatePackageDownload($dep, $required, $params, $depv1 = false) + { + $dep['package'] = $dep['name']; + if (isset($dep['uri'])) { + $dep['channel'] = '__uri'; + } + + $depname = $this->_registry->parsedPackageNameToString($dep, true); + $found = false; + foreach ($params as $param) { + if ($param->isEqual( + array('package' => $dep['name'], + 'channel' => $dep['channel']))) { + $found = true; + break; + } + + if ($depv1 && $dep['channel'] == 'pear.php.net') { + if ($param->isEqual( + array('package' => $dep['name'], + 'channel' => 'pecl.php.net'))) { + $found = true; + break; + } + } + } + + if (!$found && isset($dep['providesextension'])) { + foreach ($params as $param) { + if ($param->isExtension($dep['providesextension'])) { + $found = true; + break; + } + } + } + + if ($found) { + $version = $param->getVersion(); + $installed = false; + $downloaded = true; + } else { + if ($this->_registry->packageExists($dep['name'], $dep['channel'])) { + $installed = true; + $downloaded = false; + $version = $this->_registry->packageinfo($dep['name'], 'version', + $dep['channel']); + } else { + if ($dep['channel'] == 'pecl.php.net' && $this->_registry->packageExists($dep['name'], + 'pear.php.net')) { + $installed = true; + $downloaded = false; + $version = $this->_registry->packageinfo($dep['name'], 'version', + 'pear.php.net'); + } else { + $version = 'not installed or downloaded'; + $installed = false; + $downloaded = false; + } + } + } + + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude']) && !is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if (!isset($dep['min']) && !isset($dep['max']) && + !isset($dep['recommended']) && !isset($dep['exclude']) + ) { + if ($installed || $downloaded) { + $installed = $installed ? 'installed' : 'downloaded'; + if (isset($dep['conflicts'])) { + $rest = ''; + if ($version) { + $rest = ", $installed version is " . $version; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra . $rest); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . $extra . $rest); + } + + return true; + } + + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('%s can optionally use package "' . $depname . '"' . $extra); + } + + if (!$installed && !$downloaded) { + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('%s can optionally use package "' . $depname . '"' . $extra); + } + + $fail = false; + if (isset($dep['min']) && version_compare($version, $dep['min'], '<')) { + $fail = true; + } + + if (isset($dep['max']) && version_compare($version, $dep['max'], '>')) { + $fail = true; + } + + if ($fail && !isset($dep['conflicts'])) { + $installed = $installed ? 'installed' : 'downloaded'; + $dep['package'] = $dep['name']; + $dep = $this->_registry->parsedPackageNameToString($dep, true); + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail && + isset($dep['conflicts']) && !isset($dep['exclude'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra . + ", $installed version is " . $version); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + if (isset($dep['exclude'])) { + $installed = $installed ? 'installed' : 'downloaded'; + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==') && !isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && + !isset($this->_options['force']) + ) { + return $this->raiseError('%s is not compatible with ' . + $installed . ' package "' . + $depname . '" version ' . + $exclude); + } + + return $this->warning('warning: %s is not compatible with ' . + $installed . ' package "' . + $depname . '" version ' . + $exclude); + } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + } + } + + if (isset($dep['recommended'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (version_compare($version, $dep['recommended'], '==')) { + return true; + } + + if (!$found && $installed) { + $param = $this->_registry->getPackage($dep['name'], $dep['channel']); + } + + if ($param) { + $found = false; + foreach ($params as $parent) { + if ($parent->isEqual($this->_currentPackage)) { + $found = true; + break; + } + } + + if ($found) { + if ($param->isCompatible($parent)) { + return true; + } + } else { // this is for validPackage() calls + $parent = $this->_registry->getPackage($this->_currentPackage['package'], + $this->_currentPackage['channel']); + if ($parent !== null && $param->isCompatible($parent)) { + return true; + } + } + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force']) && + !isset($this->_options['loose']) + ) { + return $this->raiseError('%s dependency package "' . $depname . + '" ' . $installed . ' version ' . $version . + ' is not the recommended version ' . $dep['recommended'] . + ', but may be compatible, use --force to install'); + } + + return $this->warning('warning: %s dependency package "' . $depname . + '" ' . $installed . ' version ' . $version . + ' is not the recommended version ' . $dep['recommended']); + } + + return true; + } + + function _validatePackageInstall($dep, $required, $depv1 = false) + { + return $this->_validatePackageDownload($dep, $required, array(), $depv1); + } + + /** + * Verify that uninstalling packages passed in to command line is OK. + * + * @param PEAR_Installer $dl + * @return PEAR_Error|true + */ + function validatePackageUninstall(&$dl) + { + if (PEAR::isError($this->_dependencydb)) { + return $this->_dependencydb; + } + + $params = array(); + // construct an array of "downloaded" packages to fool the package dependency checker + // into using these to validate uninstalls of circular dependencies + $downloaded = &$dl->getUninstallPackages(); + foreach ($downloaded as $i => $pf) { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'PEAR/Downloader/Package.php'; + } + $dp = &new PEAR_Downloader_Package($dl); + $dp->setPackageFile($downloaded[$i]); + $params[$i] = &$dp; + } + + // check cache + $memyselfandI = strtolower($this->_currentPackage['channel']) . '/' . + strtolower($this->_currentPackage['package']); + if (isset($dl->___uninstall_package_cache)) { + $badpackages = $dl->___uninstall_package_cache; + if (isset($badpackages[$memyselfandI]['warnings'])) { + foreach ($badpackages[$memyselfandI]['warnings'] as $warning) { + $dl->log(0, $warning[0]); + } + } + + if (isset($badpackages[$memyselfandI]['errors'])) { + foreach ($badpackages[$memyselfandI]['errors'] as $error) { + if (is_array($error)) { + $dl->log(0, $error[0]); + } else { + $dl->log(0, $error->getMessage()); + } + } + + if (isset($this->_options['nodeps']) || isset($this->_options['force'])) { + return $this->warning( + 'warning: %s should not be uninstalled, other installed packages depend ' . + 'on this package'); + } + + return $this->raiseError( + '%s cannot be uninstalled, other installed packages depend on this package'); + } + + return true; + } + + // first, list the immediate parents of each package to be uninstalled + $perpackagelist = array(); + $allparents = array(); + foreach ($params as $i => $param) { + $a = array( + 'channel' => strtolower($param->getChannel()), + 'package' => strtolower($param->getPackage()) + ); + + $deps = $this->_dependencydb->getDependentPackages($a); + if ($deps) { + foreach ($deps as $d) { + $pardeps = $this->_dependencydb->getDependencies($d); + foreach ($pardeps as $dep) { + if (strtolower($dep['dep']['channel']) == $a['channel'] && + strtolower($dep['dep']['name']) == $a['package']) { + if (!isset($perpackagelist[$a['channel'] . '/' . $a['package']])) { + $perpackagelist[$a['channel'] . '/' . $a['package']] = array(); + } + $perpackagelist[$a['channel'] . '/' . $a['package']][] + = array($d['channel'] . '/' . $d['package'], $dep); + if (!isset($allparents[$d['channel'] . '/' . $d['package']])) { + $allparents[$d['channel'] . '/' . $d['package']] = array(); + } + if (!isset($allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']])) { + $allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']] = array(); + } + $allparents[$d['channel'] . '/' . $d['package']] + [$a['channel'] . '/' . $a['package']][] + = array($d, $dep); + } + } + } + } + } + + // next, remove any packages from the parents list that are not installed + $remove = array(); + foreach ($allparents as $parent => $d1) { + foreach ($d1 as $d) { + if ($this->_registry->packageExists($d[0][0]['package'], $d[0][0]['channel'])) { + continue; + } + $remove[$parent] = true; + } + } + + // next remove any packages from the parents list that are not passed in for + // uninstallation + foreach ($allparents as $parent => $d1) { + foreach ($d1 as $d) { + foreach ($params as $param) { + if (strtolower($param->getChannel()) == $d[0][0]['channel'] && + strtolower($param->getPackage()) == $d[0][0]['package']) { + // found it + continue 3; + } + } + $remove[$parent] = true; + } + } + + // remove all packages whose dependencies fail + // save which ones failed for error reporting + $badchildren = array(); + do { + $fail = false; + foreach ($remove as $package => $unused) { + if (!isset($allparents[$package])) { + continue; + } + + foreach ($allparents[$package] as $kid => $d1) { + foreach ($d1 as $depinfo) { + if ($depinfo[1]['type'] != 'optional') { + if (isset($badchildren[$kid])) { + continue; + } + $badchildren[$kid] = true; + $remove[$kid] = true; + $fail = true; + continue 2; + } + } + } + if ($fail) { + // start over, we removed some children + continue 2; + } + } + } while ($fail); + + // next, construct the list of packages that can't be uninstalled + $badpackages = array(); + $save = $this->_currentPackage; + foreach ($perpackagelist as $package => $packagedeps) { + foreach ($packagedeps as $parent) { + if (!isset($remove[$parent[0]])) { + continue; + } + + $packagename = $this->_registry->parsePackageName($parent[0]); + $packagename['channel'] = $this->_registry->channelAlias($packagename['channel']); + $pa = $this->_registry->getPackage($packagename['package'], $packagename['channel']); + $packagename['package'] = $pa->getPackage(); + $this->_currentPackage = $packagename; + // parent is not present in uninstall list, make sure we can actually + // uninstall it (parent dep is optional) + $parentname['channel'] = $this->_registry->channelAlias($parent[1]['dep']['channel']); + $pa = $this->_registry->getPackage($parent[1]['dep']['name'], $parent[1]['dep']['channel']); + $parentname['package'] = $pa->getPackage(); + $parent[1]['dep']['package'] = $parentname['package']; + $parent[1]['dep']['channel'] = $parentname['channel']; + if ($parent[1]['type'] == 'optional') { + $test = $this->_validatePackageUninstall($parent[1]['dep'], false, $dl); + if ($test !== true) { + $badpackages[$package]['warnings'][] = $test; + } + } else { + $test = $this->_validatePackageUninstall($parent[1]['dep'], true, $dl); + if ($test !== true) { + $badpackages[$package]['errors'][] = $test; + } + } + } + } + + $this->_currentPackage = $save; + $dl->___uninstall_package_cache = $badpackages; + if (isset($badpackages[$memyselfandI])) { + if (isset($badpackages[$memyselfandI]['warnings'])) { + foreach ($badpackages[$memyselfandI]['warnings'] as $warning) { + $dl->log(0, $warning[0]); + } + } + + if (isset($badpackages[$memyselfandI]['errors'])) { + foreach ($badpackages[$memyselfandI]['errors'] as $error) { + if (is_array($error)) { + $dl->log(0, $error[0]); + } else { + $dl->log(0, $error->getMessage()); + } + } + + if (isset($this->_options['nodeps']) || isset($this->_options['force'])) { + return $this->warning( + 'warning: %s should not be uninstalled, other installed packages depend ' . + 'on this package'); + } + + return $this->raiseError( + '%s cannot be uninstalled, other installed packages depend on this package'); + } + } + + return true; + } + + function _validatePackageUninstall($dep, $required, $dl) + { + $depname = $this->_registry->parsedPackageNameToString($dep, true); + $version = $this->_registry->packageinfo($dep['package'], 'version', $dep['channel']); + if (!$version) { + return true; + } + + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude']) && !is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if (isset($dep['conflicts'])) { + return true; // uninstall OK - these packages conflict (probably installed with --force) + } + + if (!isset($dep['min']) && !isset($dep['max'])) { + if (!$required) { + return $this->warning('"' . $depname . '" can be optionally used by ' . + 'installed package %s' . $extra); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('"' . $depname . '" is required by ' . + 'installed package %s' . $extra); + } + + return $this->warning('warning: "' . $depname . '" is required by ' . + 'installed package %s' . $extra); + } + + $fail = false; + if (isset($dep['min']) && version_compare($version, $dep['min'], '>=')) { + $fail = true; + } + + if (isset($dep['max']) && version_compare($version, $dep['max'], '<=')) { + $fail = true; + } + + // we re-use this variable, preserve the original value + $saverequired = $required; + if (!$required) { + return $this->warning($depname . $extra . ' can be optionally used by installed package' . + ' "%s"'); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError($depname . $extra . ' is required by installed package' . + ' "%s"'); + } + + return $this->raiseError('warning: ' . $depname . $extra . + ' is required by installed package "%s"'); + } + + /** + * validate a downloaded package against installed packages + * + * As of PEAR 1.4.3, this will only validate + * + * @param array|PEAR_Downloader_Package|PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * $pkg package identifier (either + * array('package' => blah, 'channel' => blah) or an array with + * index 'info' referencing an object) + * @param PEAR_Downloader $dl + * @param array $params full list of packages to install + * @return true|PEAR_Error + */ + function validatePackage($pkg, &$dl, $params = array()) + { + if (is_array($pkg) && isset($pkg['info'])) { + $deps = $this->_dependencydb->getDependentPackageDependencies($pkg['info']); + } else { + $deps = $this->_dependencydb->getDependentPackageDependencies($pkg); + } + + $fail = false; + if ($deps) { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'PEAR/Downloader/Package.php'; + } + + $dp = &new PEAR_Downloader_Package($dl); + if (is_object($pkg)) { + $dp->setPackageFile($pkg); + } else { + $dp->setDownloadURL($pkg); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($deps as $channel => $info) { + foreach ($info as $package => $ds) { + foreach ($params as $packd) { + if (strtolower($packd->getPackage()) == strtolower($package) && + $packd->getChannel() == $channel) { + $dl->log(3, 'skipping installed package check of "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $channel, 'package' => $package), + true) . + '", version "' . $packd->getVersion() . '" will be ' . + 'downloaded and installed'); + continue 2; // jump to next package + } + } + + foreach ($ds as $d) { + $checker = &new PEAR_Dependency2($this->_config, $this->_options, + array('channel' => $channel, 'package' => $package), $this->_state); + $dep = $d['dep']; + $required = $d['type'] == 'required'; + $ret = $checker->_validatePackageDownload($dep, $required, array(&$dp)); + if (is_array($ret)) { + $dl->log(0, $ret[0]); + } elseif (PEAR::isError($ret)) { + $dl->log(0, $ret->getMessage()); + $fail = true; + } + } + } + } + PEAR::popErrorHandling(); + } + + if ($fail) { + return $this->raiseError( + '%s cannot be installed, conflicts with installed packages'); + } + + return true; + } + + /** + * validate a package.xml 1.0 dependency + */ + function validateDependency1($dep, $params = array()) + { + if (!isset($dep['optional'])) { + $dep['optional'] = 'no'; + } + + list($newdep, $type) = $this->normalizeDep($dep); + if (!$newdep) { + return $this->raiseError("Invalid Dependency"); + } + + if (method_exists($this, "validate{$type}Dependency")) { + return $this->{"validate{$type}Dependency"}($newdep, $dep['optional'] == 'no', + $params, true); + } + } + + /** + * Convert a 1.0 dep into a 2.0 dep + */ + function normalizeDep($dep) + { + $types = array( + 'pkg' => 'Package', + 'ext' => 'Extension', + 'os' => 'Os', + 'php' => 'Php' + ); + + if (!isset($types[$dep['type']])) { + return array(false, false); + } + + $type = $types[$dep['type']]; + + $newdep = array(); + switch ($type) { + case 'Package' : + $newdep['channel'] = 'pear.php.net'; + case 'Extension' : + case 'Os' : + $newdep['name'] = $dep['name']; + break; + } + + $dep['rel'] = PEAR_Dependency2::signOperator($dep['rel']); + switch ($dep['rel']) { + case 'has' : + return array($newdep, $type); + break; + case 'not' : + $newdep['conflicts'] = true; + break; + case '>=' : + case '>' : + $newdep['min'] = $dep['version']; + if ($dep['rel'] == '>') { + $newdep['exclude'] = $dep['version']; + } + break; + case '<=' : + case '<' : + $newdep['max'] = $dep['version']; + if ($dep['rel'] == '<') { + $newdep['exclude'] = $dep['version']; + } + break; + case 'ne' : + case '!=' : + $newdep['min'] = '0'; + $newdep['max'] = '100000'; + $newdep['exclude'] = $dep['version']; + break; + case '==' : + $newdep['min'] = $dep['version']; + $newdep['max'] = $dep['version']; + break; + } + if ($type == 'Php') { + if (!isset($newdep['min'])) { + $newdep['min'] = '4.4.0'; + } + + if (!isset($newdep['max'])) { + $newdep['max'] = '6.0.0'; + } + } + return array($newdep, $type); + } + + /** + * Converts text comparing operators to them sign equivalents + * + * Example: 'ge' to '>=' + * + * @access public + * @param string Operator + * @return string Sign equivalent + */ + function signOperator($operator) + { + switch($operator) { + case 'lt': return '<'; + case 'le': return '<='; + case 'gt': return '>'; + case 'ge': return '>='; + case 'eq': return '=='; + case 'ne': return '!='; + default: + return $operator; + } + } + + function raiseError($msg) + { + if (isset($this->_options['ignore-errors'])) { + return $this->warning($msg); + } + + return PEAR::raiseError(sprintf($msg, $this->_registry->parsedPackageNameToString( + $this->_currentPackage, true))); + } + + function warning($msg) + { + return array(sprintf($msg, $this->_registry->parsedPackageNameToString( + $this->_currentPackage, true))); + } +}PEAR-1.9.0/PEAR/Downloader.php100664 764 764 201412 100664 10704 + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Martin Jansen + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Downloader.php 287109 2009-08-11 18:50:30Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.3.0 + */ + +/** + * Needed for constants, extending + */ +require_once 'PEAR/Common.php'; + +define('PEAR_INSTALLER_OK', 1); +define('PEAR_INSTALLER_FAILED', 0); +define('PEAR_INSTALLER_SKIPPED', -1); +define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2); + +/** + * Administration class used to download anything from the internet (PEAR Packages, + * static URLs, xml files) + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Martin Jansen + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.3.0 + */ +class PEAR_Downloader extends PEAR_Common +{ + /** + * @var PEAR_Registry + * @access private + */ + var $_registry; + + /** + * Preferred Installation State (snapshot, devel, alpha, beta, stable) + * @var string|null + * @access private + */ + var $_preferredState; + + /** + * Options from command-line passed to Install. + * + * Recognized options:
    + * - onlyreqdeps : install all required dependencies as well + * - alldeps : install all dependencies, including optional + * - installroot : base relative path to install files in + * - force : force a download even if warnings would prevent it + * - nocompress : download uncompressed tarballs + * @see PEAR_Command_Install + * @access private + * @var array + */ + var $_options; + + /** + * Downloaded Packages after a call to download(). + * + * Format of each entry: + * + * + * array('pkg' => 'package_name', 'file' => '/path/to/local/file', + * 'info' => array() // parsed package.xml + * ); + * + * @access private + * @var array + */ + var $_downloadedPackages = array(); + + /** + * Packages slated for download. + * + * This is used to prevent downloading a package more than once should it be a dependency + * for two packages to be installed. + * Format of each entry: + * + *
    +     * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml,
    +     * );
    +     * 
    + * @access private + * @var array + */ + var $_toDownload = array(); + + /** + * Array of every package installed, with names lower-cased. + * + * Format: + * + * array('package1' => 0, 'package2' => 1, ); + * + * @var array + */ + var $_installed = array(); + + /** + * @var array + * @access private + */ + var $_errorStack = array(); + + /** + * @var boolean + * @access private + */ + var $_internalDownload = false; + + /** + * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()} + * @var array + * @access private + */ + var $_packageSortTree; + + /** + * Temporary directory, or configuration value where downloads will occur + * @var string + */ + var $_downloadDir; + + /** + * @param PEAR_Frontend_* + * @param array + * @param PEAR_Config + */ + function PEAR_Downloader(&$ui, $options, &$config) + { + parent::PEAR_Common(); + $this->_options = $options; + $this->config = &$config; + $this->_preferredState = $this->config->get('preferred_state'); + $this->ui = &$ui; + if (!$this->_preferredState) { + // don't inadvertantly use a non-set preferred_state + $this->_preferredState = null; + } + + if (isset($this->_options['installroot'])) { + $this->config->setInstallRoot($this->_options['installroot']); + } + $this->_registry = &$config->getRegistry(); + + if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) { + $this->_installed = $this->_registry->listAllPackages(); + foreach ($this->_installed as $key => $unused) { + if (!count($unused)) { + continue; + } + $strtolower = create_function('$a','return strtolower($a);'); + array_walk($this->_installed[$key], $strtolower); + } + } + } + + /** + * Attempt to discover a channel's remote capabilities from + * its server name + * @param string + * @return boolean + */ + function discover($channel) + { + $this->log(1, 'Attempting to discover channel "' . $channel . '"...'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $callback = $this->ui ? array(&$this, '_downloadCallback') : null; + if (!class_exists('System')) { + require_once 'System.php'; + } + + $tmp = System::mktemp(array('-d')); + $a = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false); + PEAR::popErrorHandling(); + if (PEAR::isError($a)) { + // Attempt to fallback to https automatically. + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $this->log(1, 'Attempting fallback to https instead of http on channel "' . $channel . '"...'); + $a = $this->downloadHttp('https://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false); + PEAR::popErrorHandling(); + if (PEAR::isError($a)) { + return false; + } + } + + list($a, $lastmodified) = $a; + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $b = new PEAR_ChannelFile; + if ($b->fromXmlFile($a)) { + unlink($a); + if ($this->config->get('auto_discover')) { + $this->_registry->addChannel($b, $lastmodified); + $alias = $b->getName(); + if ($b->getName() == $this->_registry->channelName($b->getAlias())) { + $alias = $b->getAlias(); + } + + $this->log(1, 'Auto-discovered channel "' . $channel . + '", alias "' . $alias . '", adding to registry'); + } + + return true; + } + + unlink($a); + return false; + } + + /** + * For simpler unit-testing + * @param PEAR_Downloader + * @return PEAR_Downloader_Package + */ + function &newDownloaderPackage(&$t) + { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'PEAR/Downloader/Package.php'; + } + $a = &new PEAR_Downloader_Package($t); + return $a; + } + + /** + * For simpler unit-testing + * @param PEAR_Config + * @param array + * @param array + * @param int + */ + function &getDependency2Object(&$c, $i, $p, $s) + { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + $z = &new PEAR_Dependency2($c, $i, $p, $s); + return $z; + } + + function &download($params) + { + if (!count($params)) { + $a = array(); + return $a; + } + + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + $channelschecked = array(); + // convert all parameters into PEAR_Downloader_Package objects + foreach ($params as $i => $param) { + $params[$i] = &$this->newDownloaderPackage($this); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $params[$i]->initialize($param); + PEAR::staticPopErrorHandling(); + if (!$err) { + // skip parameters that were missed by preferred_state + continue; + } + + if (PEAR::isError($err)) { + if (!isset($this->_options['soft']) && $err->getMessage() !== '') { + $this->log(0, $err->getMessage()); + } + + $params[$i] = false; + if (is_object($param)) { + $param = $param->getChannel() . '/' . $param->getPackage(); + } + + if (!isset($this->_options['soft'])) { + $this->log(2, 'Package "' . $param . '" is not valid'); + } + + // Message logged above in a specific verbose mode, passing null to not show up on CLI + $this->pushError(null, PEAR_INSTALLER_SKIPPED); + } else { + do { + if ($params[$i] && $params[$i]->getType() == 'local') { + // bug #7090 skip channel.xml check for local packages + break; + } + + if ($params[$i] && !isset($channelschecked[$params[$i]->getChannel()]) && + !isset($this->_options['offline']) + ) { + $channelschecked[$params[$i]->getChannel()] = true; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!class_exists('System')) { + require_once 'System.php'; + } + + $curchannel = &$this->_registry->getChannel($params[$i]->getChannel()); + if (PEAR::isError($curchannel)) { + PEAR::staticPopErrorHandling(); + return $this->raiseError($curchannel); + } + + if (PEAR::isError($dir = $this->getDownloadDir())) { + PEAR::staticPopErrorHandling(); + break; + } + + $mirror = $this->config->get('preferred_mirror', null, $params[$i]->getChannel()); + $url = 'http://' . $mirror . '/channel.xml'; + $a = $this->downloadHttp($url, $this->ui, $dir, null, $curchannel->lastModified()); + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($a) || !$a) { + // Attempt fallback to https automatically + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $a = $this->downloadHttp('https://' . $mirror . + '/channel.xml', $this->ui, $dir, null, $curchannel->lastModified()); + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($a) || !$a) { + break; + } + } + $this->log(0, 'WARNING: channel "' . $params[$i]->getChannel() . '" has ' . + 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $params[$i]->getChannel() . + '" to update'); + } + } while (false); + + if ($params[$i] && !isset($this->_options['downloadonly'])) { + if (isset($this->_options['packagingroot'])) { + $checkdir = $this->_prependPath( + $this->config->get('php_dir', null, $params[$i]->getChannel()), + $this->_options['packagingroot']); + } else { + $checkdir = $this->config->get('php_dir', + null, $params[$i]->getChannel()); + } + + while ($checkdir && $checkdir != '/' && !file_exists($checkdir)) { + $checkdir = dirname($checkdir); + } + + if ($checkdir == '.') { + $checkdir = '/'; + } + + if (!is_writeable($checkdir)) { + return PEAR::raiseError('Cannot install, php_dir for channel "' . + $params[$i]->getChannel() . '" is not writeable by the current user'); + } + } + } + } + + unset($channelschecked); + PEAR_Downloader_Package::removeDuplicates($params); + if (!count($params)) { + $a = array(); + return $a; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['offline'])) { + $reverify = true; + while ($reverify) { + $reverify = false; + foreach ($params as $i => $param) { + //PHP Bug 40768 / PEAR Bug #10944 + //Nested foreaches fail in PHP 5.2.1 + key($params); + $ret = $params[$i]->detectDependencies($params); + if (PEAR::isError($ret)) { + $reverify = true; + $params[$i] = false; + PEAR_Downloader_Package::removeDuplicates($params); + if (!isset($this->_options['soft'])) { + $this->log(0, $ret->getMessage()); + } + continue 2; + } + } + } + } + + if (isset($this->_options['offline'])) { + $this->log(3, 'Skipping dependency download check, --offline specified'); + } + + if (!count($params)) { + $a = array(); + return $a; + } + + while (PEAR_Downloader_Package::mergeDependencies($params)); + PEAR_Downloader_Package::removeDuplicates($params, true); + $errorparams = array(); + if (PEAR_Downloader_Package::detectStupidDuplicates($params, $errorparams)) { + if (count($errorparams)) { + foreach ($errorparams as $param) { + $name = $this->_registry->parsedPackageNameToString($param->getParsedPackage()); + $this->pushError('Duplicate package ' . $name . ' found', PEAR_INSTALLER_FAILED); + } + $a = array(); + return $a; + } + } + + PEAR_Downloader_Package::removeInstalled($params); + if (!count($params)) { + $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($params); + PEAR::popErrorHandling(); + if (!count($params)) { + $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + + $ret = array(); + $newparams = array(); + if (isset($this->_options['pretend'])) { + return $params; + } + + $somefailed = false; + foreach ($params as $i => $package) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$params[$i]->download(); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + if (!isset($this->_options['soft'])) { + $this->log(1, $pf->getMessage()); + $this->log(0, 'Error: cannot download "' . + $this->_registry->parsedPackageNameToString($package->getParsedPackage(), + true) . + '"'); + } + $somefailed = true; + continue; + } + + $newparams[] = &$params[$i]; + $ret[] = array( + 'file' => $pf->getArchiveFile(), + 'info' => &$pf, + 'pkg' => $pf->getPackage() + ); + } + + if ($somefailed) { + // remove params that did not download successfully + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($newparams, true); + PEAR::popErrorHandling(); + if (!count($newparams)) { + $this->pushError('Download failed', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + } + + $this->_downloadedPackages = $ret; + return $newparams; + } + + /** + * @param array all packages to be installed + */ + function analyzeDependencies(&$params, $force = false) + { + $hasfailed = $failed = false; + if (isset($this->_options['downloadonly'])) { + return; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $redo = true; + $reset = false; + while ($redo) { + $redo = false; + foreach ($params as $i => $param) { + $deps = $param->getDeps(); + if (!$deps) { + $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(), + $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING); + $send = $param->getPackageFile(); + + $installcheck = $depchecker->validatePackage($send, $this, $params); + if (PEAR::isError($installcheck)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $installcheck->getMessage()); + } + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + continue; + } + + if (!$reset && $param->alreadyValidated() && !$force) { + continue; + } + + if (count($deps)) { + $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(), + $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING); + $send = $param->getPackageFile(); + if ($send === null) { + $send = $param->getDownloadURL(); + } + + $installcheck = $depchecker->validatePackage($send, $this, $params); + if (PEAR::isError($installcheck)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $installcheck->getMessage()); + } + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + + $failed = false; + if (isset($deps['required'])) { + foreach ($deps['required'] as $type => $dep) { + // note: Dependency2 will never return a PEAR_Error if ignore-errors + // is specified, so soft is needed to turn off logging + if (!isset($dep[0])) { + if (PEAR::isError($e = $depchecker->{"validate{$type}Dependency"}($dep, + true, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + true, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + + if (isset($deps['optional'])) { + foreach ($deps['optional'] as $type => $dep) { + if (!isset($dep[0])) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($dep, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + } + + $groupname = $param->getGroup(); + if (isset($deps['group']) && $groupname) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + $found = false; + foreach ($deps['group'] as $group) { + if ($group['attribs']['name'] == $groupname) { + $found = true; + break; + } + } + + if ($found) { + unset($group['attribs']); + foreach ($group as $type => $dep) { + if (!isset($dep[0])) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($dep, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + } + } + } else { + foreach ($deps as $dep) { + if (PEAR::isError($e = $depchecker->validateDependency1($dep, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + $params[$i]->setValidated(); + } + + if ($failed) { + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + } + } + PEAR::staticPopErrorHandling(); + if ($hasfailed && (isset($this->_options['ignore-errors']) || + isset($this->_options['nodeps']))) { + // this is probably not needed, but just in case + if (!isset($this->_options['soft'])) { + $this->log(0, 'WARNING: dependencies failed'); + } + } + } + + /** + * Retrieve the directory that downloads will happen in + * @access private + * @return string + */ + function getDownloadDir() + { + if (isset($this->_downloadDir)) { + return $this->_downloadDir; + } + $downloaddir = $this->config->get('download_dir'); + if (empty($downloaddir) || (is_dir($downloaddir) && !is_writable($downloaddir))) { + if (is_dir($downloaddir) && !is_writable($downloaddir)) { + $this->log(0, 'WARNING: configuration download directory "' . $downloaddir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir to avoid this warning'); + } + if (!class_exists('System')) { + require_once 'System.php'; + } + if (PEAR::isError($downloaddir = System::mktemp('-d'))) { + return $downloaddir; + } + $this->log(3, '+ tmp dir created at ' . $downloaddir); + } + if (!is_writable($downloaddir)) { + if (PEAR::isError(System::mkdir(array('-p', $downloaddir))) || + !is_writable($downloaddir)) { + return PEAR::raiseError('download directory "' . $downloaddir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir'); + } + } + return $this->_downloadDir = $downloaddir; + } + + function setDownloadDir($dir) + { + if (!@is_writable($dir)) { + if (PEAR::isError(System::mkdir(array('-p', $dir)))) { + return PEAR::raiseError('download directory "' . $dir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir'); + } + } + $this->_downloadDir = $dir; + } + + function configSet($key, $value, $layer = 'user', $channel = false) + { + $this->config->set($key, $value, $layer, $channel); + $this->_preferredState = $this->config->get('preferred_state', null, $channel); + if (!$this->_preferredState) { + // don't inadvertantly use a non-set preferred_state + $this->_preferredState = null; + } + } + + function setOptions($options) + { + $this->_options = $options; + } + + // }}} + // {{{ setOptions() + function getOptions() + { + return $this->_options; + } + + /** + * For simpler unit-testing + * @param PEAR_Config + * @param int + * @param string + */ + function &getPackagefileObject(&$c, $d, $t = false) + { + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + $a = &new PEAR_PackageFile($c, $d, $t); + return $a; + } + + /** + * @param array output of {@link parsePackageName()} + * @access private + */ + function _getPackageDownloadUrl($parr) + { + $curchannel = $this->config->get('default_channel'); + $this->configSet('default_channel', $parr['channel']); + // getDownloadURL returns an array. On error, it only contains information + // on the latest release as array(version, info). On success it contains + // array(version, info, download url string) + $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state'); + if (!$this->_registry->channelExists($parr['channel'])) { + do { + if ($this->config->get('auto_discover') && $this->discover($parr['channel'])) { + break; + } + + $this->configSet('default_channel', $curchannel); + return PEAR::raiseError('Unknown remote channel: ' . $parr['channel']); + } while (false); + } + + $chan = &$this->_registry->getChannel($parr['channel']); + if (PEAR::isError($chan)) { + return $chan; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $version = $this->_registry->packageInfo($parr['package'], 'version', $parr['channel']); + $stability = $this->_registry->packageInfo($parr['package'], 'stability', $parr['channel']); + // package is installed - use the installed release stability level + if (!isset($parr['state']) && $stability !== null) { + $state = $stability['release']; + } + PEAR::staticPopErrorHandling(); + $base2 = false; + + $preferred_mirror = $this->config->get('preferred_mirror'); + if (!$chan->supportsREST($preferred_mirror) || + ( + !($base2 = $chan->getBaseURL('REST1.3', $preferred_mirror)) + && + !($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + ) { + return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.'); + } + + if ($base2) { + $rest = &$this->config->getREST('1.3', $this->_options); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', $this->_options); + } + + $downloadVersion = false; + if (!isset($parr['version']) && !isset($parr['state']) && $version + && !PEAR::isError($version) + && !isset($this->_options['downloadonly']) + ) { + $downloadVersion = $version; + } + + $url = $rest->getDownloadURL($base, $parr, $state, $downloadVersion, $chan->getName()); + if (PEAR::isError($url)) { + $this->configSet('default_channel', $curchannel); + return $url; + } + + if ($parr['channel'] != $curchannel) { + $this->configSet('default_channel', $curchannel); + } + + if (!is_array($url)) { + return $url; + } + + $url['raw'] = false; // no checking is necessary for REST + if (!is_array($url['info'])) { + return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' . + 'this should never happen'); + } + + if (!isset($this->_options['force']) && + !isset($this->_options['downloadonly']) && + $version && + !PEAR::isError($version) && + !isset($parr['group']) + ) { + if (version_compare($version, $url['version'], '=')) { + return PEAR::raiseError($this->_registry->parsedPackageNameToString( + $parr, true) . ' is already installed and is the same as the ' . + 'released version ' . $url['version'], -976); + } + + if (version_compare($version, $url['version'], '>')) { + return PEAR::raiseError($this->_registry->parsedPackageNameToString( + $parr, true) . ' is already installed and is newer than detected ' . + 'released version ' . $url['version'], -976); + } + } + + if (isset($url['info']['required']) || $url['compatible']) { + require_once 'PEAR/PackageFile/v2.php'; + $pf = new PEAR_PackageFile_v2; + $pf->setRawChannel($parr['channel']); + if ($url['compatible']) { + $pf->setRawCompatible($url['compatible']); + } + } else { + require_once 'PEAR/PackageFile/v1.php'; + $pf = new PEAR_PackageFile_v1; + } + + $pf->setRawPackage($url['package']); + $pf->setDeps($url['info']); + if ($url['compatible']) { + $pf->setCompatible($url['compatible']); + } + + $pf->setRawState($url['stability']); + $url['info'] = &$pf; + if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + if (is_array($url) && isset($url['url'])) { + $url['url'] .= $ext; + } + + return $url; + } + + /** + * @param array dependency array + * @access private + */ + function _getDepPackageDownloadUrl($dep, $parr) + { + $xsdversion = isset($dep['rel']) ? '1.0' : '2.0'; + $curchannel = $this->config->get('default_channel'); + if (isset($dep['uri'])) { + $xsdversion = '2.0'; + $chan = &$this->_registry->getChannel('__uri'); + if (PEAR::isError($chan)) { + return $chan; + } + + $version = $this->_registry->packageInfo($dep['name'], 'version', '__uri'); + $this->configSet('default_channel', '__uri'); + } else { + if (isset($dep['channel'])) { + $remotechannel = $dep['channel']; + } else { + $remotechannel = 'pear.php.net'; + } + + if (!$this->_registry->channelExists($remotechannel)) { + do { + if ($this->config->get('auto_discover')) { + if ($this->discover($remotechannel)) { + break; + } + } + return PEAR::raiseError('Unknown remote channel: ' . $remotechannel); + } while (false); + } + + $chan = &$this->_registry->getChannel($remotechannel); + if (PEAR::isError($chan)) { + return $chan; + } + + $version = $this->_registry->packageInfo($dep['name'], 'version', $remotechannel); + $this->configSet('default_channel', $remotechannel); + } + + $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state'); + if (isset($parr['state']) && isset($parr['version'])) { + unset($parr['state']); + } + + if (isset($dep['uri'])) { + $info = &$this->newDownloaderPackage($this); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $info->initialize($dep); + PEAR::staticPopErrorHandling(); + if (!$err) { + // skip parameters that were missed by preferred_state + return PEAR::raiseError('Cannot initialize dependency'); + } + + if (PEAR::isError($err)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $err->getMessage()); + } + + if (is_object($info)) { + $param = $info->getChannel() . '/' . $info->getPackage(); + } + return PEAR::raiseError('Package "' . $param . '" is not valid'); + } + return $info; + } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) + && $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror')) + ) { + $rest = &$this->config->getREST('1.0', $this->_options); + $url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr, + $state, $version, $chan->getName()); + if (PEAR::isError($url)) { + return $url; + } + + if ($parr['channel'] != $curchannel) { + $this->configSet('default_channel', $curchannel); + } + + if (!is_array($url)) { + return $url; + } + + $url['raw'] = false; // no checking is necessary for REST + if (!is_array($url['info'])) { + return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' . + 'this should never happen'); + } + + if (isset($url['info']['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + $pf = new PEAR_PackageFile_v2; + $pf->setRawChannel($remotechannel); + } else { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + + } + $pf->setRawPackage($url['package']); + $pf->setDeps($url['info']); + if ($url['compatible']) { + $pf->setCompatible($url['compatible']); + } + + $pf->setRawState($url['stability']); + $url['info'] = &$pf; + if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + if (is_array($url) && isset($url['url'])) { + $url['url'] .= $ext; + } + + return $url; + } + + return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.'); + } + + /** + * @deprecated in favor of _getPackageDownloadUrl + */ + function getPackageDownloadUrl($package, $version = null, $channel = false) + { + if ($version) { + $package .= "-$version"; + } + if ($this === null || $this->_registry === null) { + $package = "http://pear.php.net/get/$package"; + } else { + $chan = $this->_registry->getChannel($channel); + if (PEAR::isError($chan)) { + return ''; + } + $package = "http://" . $chan->getServer() . "/get/$package"; + } + if (!extension_loaded("zlib")) { + $package .= '?uncompress=yes'; + } + return $package; + } + + /** + * Retrieve a list of downloaded packages after a call to {@link download()}. + * + * Also resets the list of downloaded packages. + * @return array + */ + function getDownloadedPackages() + { + $ret = $this->_downloadedPackages; + $this->_downloadedPackages = array(); + $this->_toDownload = array(); + return $ret; + } + + function _downloadCallback($msg, $params = null) + { + switch ($msg) { + case 'saveas': + $this->log(1, "downloading $params ..."); + break; + case 'done': + $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes'); + break; + case 'bytesread': + static $bytes; + if (empty($bytes)) { + $bytes = 0; + } + if (!($bytes % 10240)) { + $this->log(1, '.', false); + } + $bytes += $params; + break; + case 'start': + if($params[1] == -1) { + $length = "Unknown size"; + } else { + $length = number_format($params[1], 0, '', ',')." bytes"; + } + $this->log(1, "Starting to download {$params[0]} ($length)"); + break; + } + if (method_exists($this->ui, '_downloadCallback')) + $this->ui->_downloadCallback($msg, $params); + } + + function _prependPath($path, $prepend) + { + if (strlen($prepend) > 0) { + if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) { + if (preg_match('/^[a-z]:/i', $prepend)) { + $prepend = substr($prepend, 2); + } elseif ($prepend{0} != '\\') { + $prepend = "\\$prepend"; + } + $path = substr($path, 0, 2) . $prepend . substr($path, 2); + } else { + $path = $prepend . $path; + } + } + return $path; + } + + /** + * @param string + * @param integer + */ + function pushError($errmsg, $code = -1) + { + array_push($this->_errorStack, array($errmsg, $code)); + } + + function getErrorMsgs() + { + $msgs = array(); + $errs = $this->_errorStack; + foreach ($errs as $err) { + $msgs[] = $err[0]; + } + $this->_errorStack = array(); + return $msgs; + } + + /** + * for BC + * + * @deprecated + */ + function sortPkgDeps(&$packages, $uninstall = false) + { + $uninstall ? + $this->sortPackagesForUninstall($packages) : + $this->sortPackagesForInstall($packages); + } + + /** + * Sort a list of arrays of array(downloaded packagefilename) by dependency. + * + * This uses the topological sort method from graph theory, and the + * Structures_Graph package to properly sort dependencies for installation. + * @param array an array of downloaded PEAR_Downloader_Packages + * @return array array of array(packagefilename, package.xml contents) + */ + function sortPackagesForInstall(&$packages) + { + require_once 'Structures/Graph.php'; + require_once 'Structures/Graph/Node.php'; + require_once 'Structures/Graph/Manipulator/TopologicalSorter.php'; + $depgraph = new Structures_Graph(true); + $nodes = array(); + $reg = &$this->config->getRegistry(); + foreach ($packages as $i => $package) { + $pname = $reg->parsedPackageNameToString( + array( + 'channel' => $package->getChannel(), + 'package' => strtolower($package->getPackage()), + )); + $nodes[$pname] = new Structures_Graph_Node; + $nodes[$pname]->setData($packages[$i]); + $depgraph->addNode($nodes[$pname]); + } + + $deplinks = array(); + foreach ($nodes as $package => $node) { + $pf = &$node->getData(); + $pdeps = $pf->getDeps(true); + if (!$pdeps) { + continue; + } + + if ($pf->getPackagexmlVersion() == '1.0') { + foreach ($pdeps as $dep) { + if ($dep['type'] != 'pkg' || + (isset($dep['optional']) && $dep['optional'] == 'yes')) { + continue; + } + + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => 'pear.php.net', + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + + $deplinks[$dname][$package] = 1; + // dependency is in installed packages + continue; + } + + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => 'pecl.php.net', + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + + $deplinks[$dname][$package] = 1; + // dependency is in installed packages + continue; + } + } + } else { + // the only ordering we care about is: + // 1) subpackages must be installed before packages that depend on them + // 2) required deps must be installed before packages that depend on them + if (isset($pdeps['required']['subpackage'])) { + $t = $pdeps['required']['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['group'])) { + if (!isset($pdeps['group'][0])) { + $pdeps['group'] = array($pdeps['group']); + } + + foreach ($pdeps['group'] as $group) { + if (isset($group['subpackage'])) { + $t = $group['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + } + } + + if (isset($pdeps['optional']['subpackage'])) { + $t = $pdeps['optional']['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['required']['package'])) { + $t = $pdeps['required']['package']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['group'])) { + if (!isset($pdeps['group'][0])) { + $pdeps['group'] = array($pdeps['group']); + } + + foreach ($pdeps['group'] as $group) { + if (isset($group['package'])) { + $t = $group['package']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + } + } + } + } + + $this->_detectDepCycle($deplinks); + foreach ($deplinks as $dependent => $parents) { + foreach ($parents as $parent => $unused) { + $nodes[$dependent]->connectTo($nodes[$parent]); + } + } + + $installOrder = Structures_Graph_Manipulator_TopologicalSorter::sort($depgraph); + $ret = array(); + for ($i = 0, $count = count($installOrder); $i < $count; $i++) { + foreach ($installOrder[$i] as $index => $sortedpackage) { + $data = &$installOrder[$i][$index]->getData(); + $ret[] = &$nodes[$reg->parsedPackageNameToString( + array( + 'channel' => $data->getChannel(), + 'package' => strtolower($data->getPackage()), + ))]->getData(); + } + } + + $packages = $ret; + return; + } + + /** + * Detect recursive links between dependencies and break the cycles + * + * @param array + * @access private + */ + function _detectDepCycle(&$deplinks) + { + do { + $keepgoing = false; + foreach ($deplinks as $dep => $parents) { + foreach ($parents as $parent => $unused) { + // reset the parent cycle detector + $this->_testCycle(null, null, null); + if ($this->_testCycle($dep, $deplinks, $parent)) { + $keepgoing = true; + unset($deplinks[$dep][$parent]); + if (count($deplinks[$dep]) == 0) { + unset($deplinks[$dep]); + } + + continue 3; + } + } + } + } while ($keepgoing); + } + + function _testCycle($test, $deplinks, $dep) + { + static $visited = array(); + if ($test === null) { + $visited = array(); + return; + } + + // this happens when a parent has a dep cycle on another dependency + // but the child is not part of the cycle + if (isset($visited[$dep])) { + return false; + } + + $visited[$dep] = 1; + if ($test == $dep) { + return true; + } + + if (isset($deplinks[$dep])) { + if (in_array($test, array_keys($deplinks[$dep]), true)) { + return true; + } + + foreach ($deplinks[$dep] as $parent => $unused) { + if ($this->_testCycle($test, $deplinks, $parent)) { + return true; + } + } + } + + return false; + } + + /** + * Set up the dependency for installation parsing + * + * @param array $t dependency information + * @param PEAR_Registry $reg + * @param array $deplinks list of dependency links already established + * @param array $nodes all existing package nodes + * @param string $package parent package name + * @access private + */ + function _setupGraph($t, $reg, &$deplinks, &$nodes, $package) + { + foreach ($t as $dep) { + $depchannel = !isset($dep['channel']) ? '__uri': $dep['channel']; + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => $depchannel, + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + $deplinks[$dname][$package] = 1; + } + } + } + + function _dependsOn($a, $b) + { + return $this->_checkDepTree(strtolower($a->getChannel()), strtolower($a->getPackage()), $b); + } + + function _checkDepTree($channel, $package, $b, $checked = array()) + { + $checked[$channel][$package] = true; + if (!isset($this->_depTree[$channel][$package])) { + return false; + } + + if (isset($this->_depTree[$channel][$package][strtolower($b->getChannel())] + [strtolower($b->getPackage())])) { + return true; + } + + foreach ($this->_depTree[$channel][$package] as $ch => $packages) { + foreach ($packages as $pa => $true) { + if ($this->_checkDepTree($ch, $pa, $b, $checked)) { + return true; + } + } + } + + return false; + } + + function _sortInstall($a, $b) + { + if (!$a->getDeps() && !$b->getDeps()) { + return 0; // neither package has dependencies, order is insignificant + } + if ($a->getDeps() && !$b->getDeps()) { + return 1; // $a must be installed after $b because $a has dependencies + } + if (!$a->getDeps() && $b->getDeps()) { + return -1; // $b must be installed after $a because $b has dependencies + } + // both packages have dependencies + if ($this->_dependsOn($a, $b)) { + return 1; + } + if ($this->_dependsOn($b, $a)) { + return -1; + } + return 0; + } + + /** + * Download a file through HTTP. Considers suggested file name in + * Content-disposition: header and can run a callback function for + * different events. The callback will be called with two + * parameters: the callback type, and parameters. The implemented + * callback types are: + * + * 'setup' called at the very beginning, parameter is a UI object + * that should be used for all output + * 'message' the parameter is a string with an informational message + * 'saveas' may be used to save with a different file name, the + * parameter is the filename that is about to be used. + * If a 'saveas' callback returns a non-empty string, + * that file name will be used as the filename instead. + * Note that $save_dir will not be affected by this, only + * the basename of the file. + * 'start' download is starting, parameter is number of bytes + * that are expected, or -1 if unknown + * 'bytesread' parameter is the number of bytes read so far + * 'done' download is complete, parameter is the total number + * of bytes read + * 'connfailed' if the TCP/SSL connection fails, this callback is called + * with array(host,port,errno,errmsg) + * 'writefailed' if writing to disk fails, this callback is called + * with array(destfile,errmsg) + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param object $ui PEAR_Frontend_* instance + * @param object $config PEAR_Config instance + * @param string $save_dir directory to save file in + * @param mixed $callback function/method to call for status + * updates + * @param false|string|array $lastmodified header values to check against for caching + * use false to return the header values from this download + * @param false|array $accept Accept headers to send + * @param false|string $channel Channel to use for retrieving authentication + * @return string|array Returns the full path of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). If caching is requested, then return the header + * values. + * + * @access public + */ + function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null, + $accept = false, $channel = false) + { + static $redirect = 0; + // always reset , so we are clean case of error + $wasredirect = $redirect; + $redirect = 0; + if ($callback) { + call_user_func($callback, 'setup', array(&$ui)); + } + + $info = parse_url($url); + if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) { + return PEAR::raiseError('Cannot download non-http URL "' . $url . '"'); + } + + if (!isset($info['host'])) { + return PEAR::raiseError('Cannot download from non-URL "' . $url . '"'); + } + + $host = isset($info['host']) ? $info['host'] : null; + $port = isset($info['port']) ? $info['port'] : null; + $path = isset($info['path']) ? $info['path'] : null; + + if (isset($this)) { + $config = &$this->config; + } else { + $config = &PEAR_Config::singleton(); + } + + $proxy_host = $proxy_port = $proxy_user = $proxy_pass = ''; + if ($config->get('http_proxy') && + $proxy = parse_url($config->get('http_proxy'))) { + $proxy_host = isset($proxy['host']) ? $proxy['host'] : null; + if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') { + $proxy_host = 'ssl://' . $proxy_host; + } + $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080; + $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null; + $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null; + + if ($callback) { + call_user_func($callback, 'message', "Using HTTP proxy $host:$port"); + } + } + + if (empty($port)) { + $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80; + } + + $scheme = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http'; + + if ($proxy_host != '') { + $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr); + if (!$fp) { + if ($callback) { + call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port, + $errno, $errstr)); + } + return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno); + } + + if ($lastmodified === false || $lastmodified) { + $request = "GET $url HTTP/1.1\r\n"; + $request .= "Host: $host:$port\r\n"; + } else { + $request = "GET $url HTTP/1.0\r\n"; + $request .= "Host: $host\r\n"; + } + } else { + $network_host = $host; + if (isset($info['scheme']) && $info['scheme'] == 'https') { + $network_host = 'ssl://' . $host; + } + + $fp = @fsockopen($network_host, $port, $errno, $errstr); + if (!$fp) { + if ($callback) { + call_user_func($callback, 'connfailed', array($host, $port, + $errno, $errstr)); + } + return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno); + } + + if ($lastmodified === false || $lastmodified) { + $request = "GET $path HTTP/1.1\r\n"; + $request .= "Host: $host:$port\r\n"; + } else { + $request = "GET $path HTTP/1.0\r\n"; + $request .= "Host: $host\r\n"; + } + } + + $ifmodifiedsince = ''; + if (is_array($lastmodified)) { + if (isset($lastmodified['Last-Modified'])) { + $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n"; + } + + if (isset($lastmodified['ETag'])) { + $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n"; + } + } else { + $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : ''); + } + + $request .= $ifmodifiedsince . + "User-Agent: PEAR/1.9.0/PHP/" . PHP_VERSION . "\r\n"; + + if (isset($this)) { // only pass in authentication for non-static calls + $username = $config->get('username', null, $channel); + $password = $config->get('password', null, $channel); + if ($username && $password) { + $tmp = base64_encode("$username:$password"); + $request .= "Authorization: Basic $tmp\r\n"; + } + } + + if ($proxy_host != '' && $proxy_user != '') { + $request .= 'Proxy-Authorization: Basic ' . + base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n"; + } + + if ($accept) { + $request .= 'Accept: ' . implode(', ', $accept) . "\r\n"; + } + + $request .= "Connection: close\r\n"; + $request .= "\r\n"; + fwrite($fp, $request); + $headers = array(); + $reply = 0; + while (trim($line = fgets($fp, 1024))) { + if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) { + $headers[strtolower($matches[1])] = trim($matches[2]); + } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) { + $reply = (int)$matches[1]; + if ($reply == 304 && ($lastmodified || ($lastmodified === false))) { + return false; + } + + if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (received: $line)"); + } + } + } + + if ($reply != 200) { + if (!isset($headers['location'])) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirected but no location)"); + } + + if ($wasredirect > 4) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirection looped more than 5 times)"); + } + + $redirect = $wasredirect + 1; + return $this->downloadHttp($headers['location'], + $ui, $save_dir, $callback, $lastmodified, $accept); + } + + if (isset($headers['content-disposition']) && + preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|\\z)/', $headers['content-disposition'], $matches)) { + $save_as = basename($matches[1]); + } else { + $save_as = basename($url); + } + + if ($callback) { + $tmp = call_user_func($callback, 'saveas', $save_as); + if ($tmp) { + $save_as = $tmp; + } + } + + $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as; + if (!$wp = @fopen($dest_file, 'wb')) { + fclose($fp); + if ($callback) { + call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); + } + return PEAR::raiseError("could not open $dest_file for writing"); + } + + $length = isset($headers['content-length']) ? $headers['content-length'] : -1; + + $bytes = 0; + if ($callback) { + call_user_func($callback, 'start', array(basename($dest_file), $length)); + } + + while ($data = fread($fp, 1024)) { + $bytes += strlen($data); + if ($callback) { + call_user_func($callback, 'bytesread', $bytes); + } + if (!@fwrite($wp, $data)) { + fclose($fp); + if ($callback) { + call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); + } + return PEAR::raiseError("$dest_file: write failed ($php_errormsg)"); + } + } + + fclose($fp); + fclose($wp); + if ($callback) { + call_user_func($callback, 'done', $bytes); + } + + if ($lastmodified === false || $lastmodified) { + if (isset($headers['etag'])) { + $lastmodified = array('ETag' => $headers['etag']); + } + + if (isset($headers['last-modified'])) { + if (is_array($lastmodified)) { + $lastmodified['Last-Modified'] = $headers['last-modified']; + } else { + $lastmodified = $headers['last-modified']; + } + } + return array($dest_file, $lastmodified, $headers); + } + return $dest_file; + } +} +// }}}PEAR-1.9.0/PEAR/ErrorStack.php100664 764 764 102251 100664 10666 + * @copyright 2004-2008 Greg Beaver + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: ErrorStack.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR_ErrorStack + */ + +/** + * Singleton storage + * + * Format: + *
    + * array(
    + *  'package1' => PEAR_ErrorStack object,
    + *  'package2' => PEAR_ErrorStack object,
    + *  ...
    + * )
    + * 
    + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] + */ +$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array(); + +/** + * Global error callback (default) + * + * This is only used if set to non-false. * is the default callback for + * all packages, whereas specific packages may set a default callback + * for all instances, regardless of whether they are a singleton or not. + * + * To exclude non-singletons, only set the local callback for the singleton + * @see PEAR_ErrorStack::setDefaultCallback() + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] + */ +$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array( + '*' => false, +); + +/** + * Global Log object (default) + * + * This is only used if set to non-false. Use to set a default log object for + * all stacks, regardless of instantiation order or location + * @see PEAR_ErrorStack::setDefaultLogger() + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] + */ +$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false; + +/** + * Global Overriding Callback + * + * This callback will override any error callbacks that specific loggers have set. + * Use with EXTREME caution + * @see PEAR_ErrorStack::staticPushCallback() + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] + */ +$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array(); + +/**#@+ + * One of four possible return values from the error Callback + * @see PEAR_ErrorStack::_errorCallback() + */ +/** + * If this is returned, then the error will be both pushed onto the stack + * and logged. + */ +define('PEAR_ERRORSTACK_PUSHANDLOG', 1); +/** + * If this is returned, then the error will only be pushed onto the stack, + * and not logged. + */ +define('PEAR_ERRORSTACK_PUSH', 2); +/** + * If this is returned, then the error will only be logged, but not pushed + * onto the error stack. + */ +define('PEAR_ERRORSTACK_LOG', 3); +/** + * If this is returned, then the error is completely ignored. + */ +define('PEAR_ERRORSTACK_IGNORE', 4); +/** + * If this is returned, then the error is logged and die() is called. + */ +define('PEAR_ERRORSTACK_DIE', 5); +/**#@-*/ + +/** + * Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in + * the singleton method. + */ +define('PEAR_ERRORSTACK_ERR_NONCLASS', 1); + +/** + * Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()} + * that has no __toString() method + */ +define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2); +/** + * Error Stack Implementation + * + * Usage: + * + * // global error stack + * $global_stack = &PEAR_ErrorStack::singleton('MyPackage'); + * // local error stack + * $local_stack = new PEAR_ErrorStack('MyPackage'); + * + * @author Greg Beaver + * @version 1.9.0 + * @package PEAR_ErrorStack + * @category Debugging + * @copyright 2004-2008 Greg Beaver + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: ErrorStack.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR_ErrorStack + */ +class PEAR_ErrorStack { + /** + * Errors are stored in the order that they are pushed on the stack. + * @since 0.4alpha Errors are no longer organized by error level. + * This renders pop() nearly unusable, and levels could be more easily + * handled in a callback anyway + * @var array + * @access private + */ + var $_errors = array(); + + /** + * Storage of errors by level. + * + * Allows easy retrieval and deletion of only errors from a particular level + * @since PEAR 1.4.0dev + * @var array + * @access private + */ + var $_errorsByLevel = array(); + + /** + * Package name this error stack represents + * @var string + * @access protected + */ + var $_package; + + /** + * Determines whether a PEAR_Error is thrown upon every error addition + * @var boolean + * @access private + */ + var $_compat = false; + + /** + * If set to a valid callback, this will be used to generate the error + * message from the error code, otherwise the message passed in will be + * used + * @var false|string|array + * @access private + */ + var $_msgCallback = false; + + /** + * If set to a valid callback, this will be used to generate the error + * context for an error. For PHP-related errors, this will be a file + * and line number as retrieved from debug_backtrace(), but can be + * customized for other purposes. The error might actually be in a separate + * configuration file, or in a database query. + * @var false|string|array + * @access protected + */ + var $_contextCallback = false; + + /** + * If set to a valid callback, this will be called every time an error + * is pushed onto the stack. The return value will be used to determine + * whether to allow an error to be pushed or logged. + * + * The return value must be one an PEAR_ERRORSTACK_* constant + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @var false|string|array + * @access protected + */ + var $_errorCallback = array(); + + /** + * PEAR::Log object for logging errors + * @var false|Log + * @access protected + */ + var $_logger = false; + + /** + * Error messages - designed to be overridden + * @var array + * @abstract + */ + var $_errorMsgs = array(); + + /** + * Set up a new error stack + * + * @param string $package name of the package this error stack represents + * @param callback $msgCallback callback used for error message generation + * @param callback $contextCallback callback used for context generation, + * defaults to {@link getFileLine()} + * @param boolean $throwPEAR_Error + */ + function PEAR_ErrorStack($package, $msgCallback = false, $contextCallback = false, + $throwPEAR_Error = false) + { + $this->_package = $package; + $this->setMessageCallback($msgCallback); + $this->setContextCallback($contextCallback); + $this->_compat = $throwPEAR_Error; + } + + /** + * Return a single error stack for this package. + * + * Note that all parameters are ignored if the stack for package $package + * has already been instantiated + * @param string $package name of the package this error stack represents + * @param callback $msgCallback callback used for error message generation + * @param callback $contextCallback callback used for context generation, + * defaults to {@link getFileLine()} + * @param boolean $throwPEAR_Error + * @param string $stackClass class to instantiate + * @static + * @return PEAR_ErrorStack + */ + function &singleton($package, $msgCallback = false, $contextCallback = false, + $throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack') + { + if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]; + } + if (!class_exists($stackClass)) { + if (function_exists('debug_backtrace')) { + $trace = debug_backtrace(); + } + PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS, + 'exception', array('stackclass' => $stackClass), + 'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)', + false, $trace); + } + $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] = + new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error); + + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]; + } + + /** + * Internal error handler for PEAR_ErrorStack class + * + * Dies if the error is an exception (and would have died anyway) + * @access private + */ + function _handleError($err) + { + if ($err['level'] == 'exception') { + $message = $err['message']; + if (isset($_SERVER['REQUEST_URI'])) { + echo '
    '; + } else { + echo "\n"; + } + var_dump($err['context']); + die($message); + } + } + + /** + * Set up a PEAR::Log object for all error stacks that don't have one + * @param Log $log + * @static + */ + function setDefaultLogger(&$log) + { + if (is_object($log) && method_exists($log, 'log') ) { + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log; + } elseif (is_callable($log)) { + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log; + } + } + + /** + * Set up a PEAR::Log object for this error stack + * @param Log $log + */ + function setLogger(&$log) + { + if (is_object($log) && method_exists($log, 'log') ) { + $this->_logger = &$log; + } elseif (is_callable($log)) { + $this->_logger = &$log; + } + } + + /** + * Set an error code => error message mapping callback + * + * This method sets the callback that can be used to generate error + * messages for any instance + * @param array|string Callback function/method + */ + function setMessageCallback($msgCallback) + { + if (!$msgCallback) { + $this->_msgCallback = array(&$this, 'getErrorMessage'); + } else { + if (is_callable($msgCallback)) { + $this->_msgCallback = $msgCallback; + } + } + } + + /** + * Get an error code => error message mapping callback + * + * This method returns the current callback that can be used to generate error + * messages + * @return array|string|false Callback function/method or false if none + */ + function getMessageCallback() + { + return $this->_msgCallback; + } + + /** + * Sets a default callback to be used by all error stacks + * + * This method sets the callback that can be used to generate error + * messages for a singleton + * @param array|string Callback function/method + * @param string Package name, or false for all packages + * @static + */ + function setDefaultCallback($callback = false, $package = false) + { + if (!is_callable($callback)) { + $callback = false; + } + $package = $package ? $package : '*'; + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback; + } + + /** + * Set a callback that generates context information (location of error) for an error stack + * + * This method sets the callback that can be used to generate context + * information for an error. Passing in NULL will disable context generation + * and remove the expensive call to debug_backtrace() + * @param array|string|null Callback function/method + */ + function setContextCallback($contextCallback) + { + if ($contextCallback === null) { + return $this->_contextCallback = false; + } + if (!$contextCallback) { + $this->_contextCallback = array(&$this, 'getFileLine'); + } else { + if (is_callable($contextCallback)) { + $this->_contextCallback = $contextCallback; + } + } + } + + /** + * Set an error Callback + * If set to a valid callback, this will be called every time an error + * is pushed onto the stack. The return value will be used to determine + * whether to allow an error to be pushed or logged. + * + * The return value must be one of the ERRORSTACK_* constants. + * + * This functionality can be used to emulate PEAR's pushErrorHandling, and + * the PEAR_ERROR_CALLBACK mode, without affecting the integrity of + * the error stack or logging + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @see popCallback() + * @param string|array $cb + */ + function pushCallback($cb) + { + array_push($this->_errorCallback, $cb); + } + + /** + * Remove a callback from the error callback stack + * @see pushCallback() + * @return array|string|false + */ + function popCallback() + { + if (!count($this->_errorCallback)) { + return false; + } + return array_pop($this->_errorCallback); + } + + /** + * Set a temporary overriding error callback for every package error stack + * + * Use this to temporarily disable all existing callbacks (can be used + * to emulate the @ operator, for instance) + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @see staticPopCallback(), pushCallback() + * @param string|array $cb + * @static + */ + function staticPushCallback($cb) + { + array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb); + } + + /** + * Remove a temporary overriding error callback + * @see staticPushCallback() + * @return array|string|false + * @static + */ + function staticPopCallback() + { + $ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']); + if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) { + $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array(); + } + return $ret; + } + + /** + * Add an error to the stack + * + * If the message generator exists, it is called with 2 parameters. + * - the current Error Stack object + * - an array that is in the same format as an error. Available indices + * are 'code', 'package', 'time', 'params', 'level', and 'context' + * + * Next, if the error should contain context information, this is + * handled by the context grabbing method. + * Finally, the error is pushed onto the proper error stack + * @param int $code Package-specific error code + * @param string $level Error level. This is NOT spell-checked + * @param array $params associative array of error parameters + * @param string $msg Error message, or a portion of it if the message + * is to be generated + * @param array $repackage If this error re-packages an error pushed by + * another package, place the array returned from + * {@link pop()} in this parameter + * @param array $backtrace Protected parameter: use this to pass in the + * {@link debug_backtrace()} that should be used + * to find error context + * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also + * thrown. If a PEAR_Error is returned, the userinfo + * property is set to the following array: + * + * + * array( + * 'code' => $code, + * 'params' => $params, + * 'package' => $this->_package, + * 'level' => $level, + * 'time' => time(), + * 'context' => $context, + * 'message' => $msg, + * //['repackage' => $err] repackaged error array/Exception class + * ); + * + * + * Normally, the previous array is returned. + */ + function push($code, $level = 'error', $params = array(), $msg = false, + $repackage = false, $backtrace = false) + { + $context = false; + // grab error context + if ($this->_contextCallback) { + if (!$backtrace) { + $backtrace = debug_backtrace(); + } + $context = call_user_func($this->_contextCallback, $code, $params, $backtrace); + } + + // save error + $time = explode(' ', microtime()); + $time = $time[1] + $time[0]; + $err = array( + 'code' => $code, + 'params' => $params, + 'package' => $this->_package, + 'level' => $level, + 'time' => $time, + 'context' => $context, + 'message' => $msg, + ); + + if ($repackage) { + $err['repackage'] = $repackage; + } + + // set up the error message, if necessary + if ($this->_msgCallback) { + $msg = call_user_func_array($this->_msgCallback, + array(&$this, $err)); + $err['message'] = $msg; + } + $push = $log = true; + $die = false; + // try the overriding callback first + $callback = $this->staticPopCallback(); + if ($callback) { + $this->staticPushCallback($callback); + } + if (!is_callable($callback)) { + // try the local callback next + $callback = $this->popCallback(); + if (is_callable($callback)) { + $this->pushCallback($callback); + } else { + // try the default callback + $callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ? + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] : + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*']; + } + } + if (is_callable($callback)) { + switch(call_user_func($callback, $err)){ + case PEAR_ERRORSTACK_IGNORE: + return $err; + break; + case PEAR_ERRORSTACK_PUSH: + $log = false; + break; + case PEAR_ERRORSTACK_LOG: + $push = false; + break; + case PEAR_ERRORSTACK_DIE: + $die = true; + break; + // anything else returned has the same effect as pushandlog + } + } + if ($push) { + array_unshift($this->_errors, $err); + if (!isset($this->_errorsByLevel[$err['level']])) { + $this->_errorsByLevel[$err['level']] = array(); + } + $this->_errorsByLevel[$err['level']][] = &$this->_errors[0]; + } + if ($log) { + if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) { + $this->_log($err); + } + } + if ($die) { + die(); + } + if ($this->_compat && $push) { + return $this->raiseError($msg, $code, null, null, $err); + } + return $err; + } + + /** + * Static version of {@link push()} + * + * @param string $package Package name this error belongs to + * @param int $code Package-specific error code + * @param string $level Error level. This is NOT spell-checked + * @param array $params associative array of error parameters + * @param string $msg Error message, or a portion of it if the message + * is to be generated + * @param array $repackage If this error re-packages an error pushed by + * another package, place the array returned from + * {@link pop()} in this parameter + * @param array $backtrace Protected parameter: use this to pass in the + * {@link debug_backtrace()} that should be used + * to find error context + * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also + * thrown. see docs for {@link push()} + * @static + */ + function staticPush($package, $code, $level = 'error', $params = array(), + $msg = false, $repackage = false, $backtrace = false) + { + $s = &PEAR_ErrorStack::singleton($package); + if ($s->_contextCallback) { + if (!$backtrace) { + if (function_exists('debug_backtrace')) { + $backtrace = debug_backtrace(); + } + } + } + return $s->push($code, $level, $params, $msg, $repackage, $backtrace); + } + + /** + * Log an error using PEAR::Log + * @param array $err Error array + * @param array $levels Error level => Log constant map + * @access protected + */ + function _log($err) + { + if ($this->_logger) { + $logger = &$this->_logger; + } else { + $logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']; + } + if (is_a($logger, 'Log')) { + $levels = array( + 'exception' => PEAR_LOG_CRIT, + 'alert' => PEAR_LOG_ALERT, + 'critical' => PEAR_LOG_CRIT, + 'error' => PEAR_LOG_ERR, + 'warning' => PEAR_LOG_WARNING, + 'notice' => PEAR_LOG_NOTICE, + 'info' => PEAR_LOG_INFO, + 'debug' => PEAR_LOG_DEBUG); + if (isset($levels[$err['level']])) { + $level = $levels[$err['level']]; + } else { + $level = PEAR_LOG_INFO; + } + $logger->log($err['message'], $level, $err); + } else { // support non-standard logs + call_user_func($logger, $err); + } + } + + + /** + * Pop an error off of the error stack + * + * @return false|array + * @since 0.4alpha it is no longer possible to specify a specific error + * level to return - the last error pushed will be returned, instead + */ + function pop() + { + $err = @array_shift($this->_errors); + if (!is_null($err)) { + @array_pop($this->_errorsByLevel[$err['level']]); + if (!count($this->_errorsByLevel[$err['level']])) { + unset($this->_errorsByLevel[$err['level']]); + } + } + return $err; + } + + /** + * Pop an error off of the error stack, static method + * + * @param string package name + * @return boolean + * @since PEAR1.5.0a1 + */ + function staticPop($package) + { + if ($package) { + if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { + return false; + } + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop(); + } + } + + /** + * Determine whether there are any errors on the stack + * @param string|array Level name. Use to determine if any errors + * of level (string), or levels (array) have been pushed + * @return boolean + */ + function hasErrors($level = false) + { + if ($level) { + return isset($this->_errorsByLevel[$level]); + } + return count($this->_errors); + } + + /** + * Retrieve all errors since last purge + * + * @param boolean set in order to empty the error stack + * @param string level name, to return only errors of a particular severity + * @return array + */ + function getErrors($purge = false, $level = false) + { + if (!$purge) { + if ($level) { + if (!isset($this->_errorsByLevel[$level])) { + return array(); + } else { + return $this->_errorsByLevel[$level]; + } + } else { + return $this->_errors; + } + } + if ($level) { + $ret = $this->_errorsByLevel[$level]; + foreach ($this->_errorsByLevel[$level] as $i => $unused) { + // entries are references to the $_errors array + $this->_errorsByLevel[$level][$i] = false; + } + // array_filter removes all entries === false + $this->_errors = array_filter($this->_errors); + unset($this->_errorsByLevel[$level]); + return $ret; + } + $ret = $this->_errors; + $this->_errors = array(); + $this->_errorsByLevel = array(); + return $ret; + } + + /** + * Determine whether there are any errors on a single error stack, or on any error stack + * + * The optional parameter can be used to test the existence of any errors without the need of + * singleton instantiation + * @param string|false Package name to check for errors + * @param string Level name to check for a particular severity + * @return boolean + * @static + */ + function staticHasErrors($package = false, $level = false) + { + if ($package) { + if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { + return false; + } + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level); + } + foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) { + if ($obj->hasErrors($level)) { + return true; + } + } + return false; + } + + /** + * Get a list of all errors since last purge, organized by package + * @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be + * @param boolean $purge Set to purge the error stack of existing errors + * @param string $level Set to a level name in order to retrieve only errors of a particular level + * @param boolean $merge Set to return a flat array, not organized by package + * @param array $sortfunc Function used to sort a merged array - default + * sorts by time, and should be good for most cases + * @static + * @return array + */ + function staticGetErrors($purge = false, $level = false, $merge = false, + $sortfunc = array('PEAR_ErrorStack', '_sortErrors')) + { + $ret = array(); + if (!is_callable($sortfunc)) { + $sortfunc = array('PEAR_ErrorStack', '_sortErrors'); + } + foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) { + $test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level); + if ($test) { + if ($merge) { + $ret = array_merge($ret, $test); + } else { + $ret[$package] = $test; + } + } + } + if ($merge) { + usort($ret, $sortfunc); + } + return $ret; + } + + /** + * Error sorting function, sorts by time + * @access private + */ + function _sortErrors($a, $b) + { + if ($a['time'] == $b['time']) { + return 0; + } + if ($a['time'] < $b['time']) { + return 1; + } + return -1; + } + + /** + * Standard file/line number/function/class context callback + * + * This function uses a backtrace generated from {@link debug_backtrace()} + * and so will not work at all in PHP < 4.3.0. The frame should + * reference the frame that contains the source of the error. + * @return array|false either array('file' => file, 'line' => line, + * 'function' => function name, 'class' => class name) or + * if this doesn't work, then false + * @param unused + * @param integer backtrace frame. + * @param array Results of debug_backtrace() + * @static + */ + function getFileLine($code, $params, $backtrace = null) + { + if ($backtrace === null) { + return false; + } + $frame = 0; + $functionframe = 1; + if (!isset($backtrace[1])) { + $functionframe = 0; + } else { + while (isset($backtrace[$functionframe]['function']) && + $backtrace[$functionframe]['function'] == 'eval' && + isset($backtrace[$functionframe + 1])) { + $functionframe++; + } + } + if (isset($backtrace[$frame])) { + if (!isset($backtrace[$frame]['file'])) { + $frame++; + } + $funcbacktrace = $backtrace[$functionframe]; + $filebacktrace = $backtrace[$frame]; + $ret = array('file' => $filebacktrace['file'], + 'line' => $filebacktrace['line']); + // rearrange for eval'd code or create function errors + if (strpos($filebacktrace['file'], '(') && + preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'], + $matches)) { + $ret['file'] = $matches[1]; + $ret['line'] = $matches[2] + 0; + } + if (isset($funcbacktrace['function']) && isset($backtrace[1])) { + if ($funcbacktrace['function'] != 'eval') { + if ($funcbacktrace['function'] == '__lambda_func') { + $ret['function'] = 'create_function() code'; + } else { + $ret['function'] = $funcbacktrace['function']; + } + } + } + if (isset($funcbacktrace['class']) && isset($backtrace[1])) { + $ret['class'] = $funcbacktrace['class']; + } + return $ret; + } + return false; + } + + /** + * Standard error message generation callback + * + * This method may also be called by a custom error message generator + * to fill in template values from the params array, simply + * set the third parameter to the error message template string to use + * + * The special variable %__msg% is reserved: use it only to specify + * where a message passed in by the user should be placed in the template, + * like so: + * + * Error message: %msg% - internal error + * + * If the message passed like so: + * + * + * $stack->push(ERROR_CODE, 'error', array(), 'server error 500'); + * + * + * The returned error message will be "Error message: server error 500 - + * internal error" + * @param PEAR_ErrorStack + * @param array + * @param string|false Pre-generated error message template + * @static + * @return string + */ + function getErrorMessage(&$stack, $err, $template = false) + { + if ($template) { + $mainmsg = $template; + } else { + $mainmsg = $stack->getErrorMessageTemplate($err['code']); + } + $mainmsg = str_replace('%__msg%', $err['message'], $mainmsg); + if (is_array($err['params']) && count($err['params'])) { + foreach ($err['params'] as $name => $val) { + if (is_array($val)) { + // @ is needed in case $val is a multi-dimensional array + $val = @implode(', ', $val); + } + if (is_object($val)) { + if (method_exists($val, '__toString')) { + $val = $val->__toString(); + } else { + PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING, + 'warning', array('obj' => get_class($val)), + 'object %obj% passed into getErrorMessage, but has no __toString() method'); + $val = 'Object'; + } + } + $mainmsg = str_replace('%' . $name . '%', $val, $mainmsg); + } + } + return $mainmsg; + } + + /** + * Standard Error Message Template generator from code + * @return string + */ + function getErrorMessageTemplate($code) + { + if (!isset($this->_errorMsgs[$code])) { + return '%__msg%'; + } + return $this->_errorMsgs[$code]; + } + + /** + * Set the Error Message Template array + * + * The array format must be: + *
    +     * array(error code => 'message template',...)
    +     * 
    + * + * Error message parameters passed into {@link push()} will be used as input + * for the error message. If the template is 'message %foo% was %bar%', and the + * parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will + * be 'message one was six' + * @return string + */ + function setErrorMessageTemplate($template) + { + $this->_errorMsgs = $template; + } + + + /** + * emulate PEAR::raiseError() + * + * @return PEAR_Error + */ + function raiseError() + { + require_once 'PEAR.php'; + $args = func_get_args(); + return call_user_func_array(array('PEAR', 'raiseError'), $args); + } +} +$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack'); +$stack->pushCallback(array('PEAR_ErrorStack', '_handleError')); +?> +PEAR-1.9.0/PEAR/Exception.php100664 764 764 33031 100664 10524 + * @author Hans Lellelid + * @author Bertrand Mansion + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Exception.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.3.3 + */ + + +/** + * Base PEAR_Exception Class + * + * 1) Features: + * + * - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception)) + * - Definable triggers, shot when exceptions occur + * - Pretty and informative error messages + * - Added more context info available (like class, method or cause) + * - cause can be a PEAR_Exception or an array of mixed + * PEAR_Exceptions/PEAR_ErrorStack warnings + * - callbacks for specific exception classes and their children + * + * 2) Ideas: + * + * - Maybe a way to define a 'template' for the output + * + * 3) Inherited properties from PHP Exception Class: + * + * protected $message + * protected $code + * protected $line + * protected $file + * private $trace + * + * 4) Inherited methods from PHP Exception Class: + * + * __clone + * __construct + * getMessage + * getCode + * getFile + * getLine + * getTraceSafe + * getTraceSafeAsString + * __toString + * + * 5) Usage example + * + * + * require_once 'PEAR/Exception.php'; + * + * class Test { + * function foo() { + * throw new PEAR_Exception('Error Message', ERROR_CODE); + * } + * } + * + * function myLogger($pear_exception) { + * echo $pear_exception->getMessage(); + * } + * // each time a exception is thrown the 'myLogger' will be called + * // (its use is completely optional) + * PEAR_Exception::addObserver('myLogger'); + * $test = new Test; + * try { + * $test->foo(); + * } catch (PEAR_Exception $e) { + * print $e; + * } + * + * + * @category pear + * @package PEAR + * @author Tomas V.V.Cox + * @author Hans Lellelid + * @author Bertrand Mansion + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.3.3 + * + */ +class PEAR_Exception extends Exception +{ + const OBSERVER_PRINT = -2; + const OBSERVER_TRIGGER = -4; + const OBSERVER_DIE = -8; + protected $cause; + private static $_observers = array(); + private static $_uniqueid = 0; + private $_trace; + + /** + * Supported signatures: + * - PEAR_Exception(string $message); + * - PEAR_Exception(string $message, int $code); + * - PEAR_Exception(string $message, Exception $cause); + * - PEAR_Exception(string $message, Exception $cause, int $code); + * - PEAR_Exception(string $message, PEAR_Error $cause); + * - PEAR_Exception(string $message, PEAR_Error $cause, int $code); + * - PEAR_Exception(string $message, array $causes); + * - PEAR_Exception(string $message, array $causes, int $code); + * @param string exception message + * @param int|Exception|PEAR_Error|array|null exception cause + * @param int|null exception code or null + */ + public function __construct($message, $p2 = null, $p3 = null) + { + if (is_int($p2)) { + $code = $p2; + $this->cause = null; + } elseif (is_object($p2) || is_array($p2)) { + // using is_object allows both Exception and PEAR_Error + if (is_object($p2) && !($p2 instanceof Exception)) { + if (!class_exists('PEAR_Error') || !($p2 instanceof PEAR_Error)) { + throw new PEAR_Exception('exception cause must be Exception, ' . + 'array, or PEAR_Error'); + } + } + $code = $p3; + if (is_array($p2) && isset($p2['message'])) { + // fix potential problem of passing in a single warning + $p2 = array($p2); + } + $this->cause = $p2; + } else { + $code = null; + $this->cause = null; + } + parent::__construct($message, $code); + $this->signal(); + } + + /** + * @param mixed $callback - A valid php callback, see php func is_callable() + * - A PEAR_Exception::OBSERVER_* constant + * - An array(const PEAR_Exception::OBSERVER_*, + * mixed $options) + * @param string $label The name of the observer. Use this if you want + * to remove it later with removeObserver() + */ + public static function addObserver($callback, $label = 'default') + { + self::$_observers[$label] = $callback; + } + + public static function removeObserver($label = 'default') + { + unset(self::$_observers[$label]); + } + + /** + * @return int unique identifier for an observer + */ + public static function getUniqueId() + { + return self::$_uniqueid++; + } + + private function signal() + { + foreach (self::$_observers as $func) { + if (is_callable($func)) { + call_user_func($func, $this); + continue; + } + settype($func, 'array'); + switch ($func[0]) { + case self::OBSERVER_PRINT : + $f = (isset($func[1])) ? $func[1] : '%s'; + printf($f, $this->getMessage()); + break; + case self::OBSERVER_TRIGGER : + $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE; + trigger_error($this->getMessage(), $f); + break; + case self::OBSERVER_DIE : + $f = (isset($func[1])) ? $func[1] : '%s'; + die(printf($f, $this->getMessage())); + break; + default: + trigger_error('invalid observer type', E_USER_WARNING); + } + } + } + + /** + * Return specific error information that can be used for more detailed + * error messages or translation. + * + * This method may be overridden in child exception classes in order + * to add functionality not present in PEAR_Exception and is a placeholder + * to define API + * + * The returned array must be an associative array of parameter => value like so: + *
    +     * array('name' => $name, 'context' => array(...))
    +     * 
    + * @return array + */ + public function getErrorData() + { + return array(); + } + + /** + * Returns the exception that caused this exception to be thrown + * @access public + * @return Exception|array The context of the exception + */ + public function getCause() + { + return $this->cause; + } + + /** + * Function must be public to call on caused exceptions + * @param array + */ + public function getCauseMessage(&$causes) + { + $trace = $this->getTraceSafe(); + $cause = array('class' => get_class($this), + 'message' => $this->message, + 'file' => 'unknown', + 'line' => 'unknown'); + if (isset($trace[0])) { + if (isset($trace[0]['file'])) { + $cause['file'] = $trace[0]['file']; + $cause['line'] = $trace[0]['line']; + } + } + $causes[] = $cause; + if ($this->cause instanceof PEAR_Exception) { + $this->cause->getCauseMessage($causes); + } elseif ($this->cause instanceof Exception) { + $causes[] = array('class' => get_class($this->cause), + 'message' => $this->cause->getMessage(), + 'file' => $this->cause->getFile(), + 'line' => $this->cause->getLine()); + } elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) { + $causes[] = array('class' => get_class($this->cause), + 'message' => $this->cause->getMessage(), + 'file' => 'unknown', + 'line' => 'unknown'); + } elseif (is_array($this->cause)) { + foreach ($this->cause as $cause) { + if ($cause instanceof PEAR_Exception) { + $cause->getCauseMessage($causes); + } elseif ($cause instanceof Exception) { + $causes[] = array('class' => get_class($cause), + 'message' => $cause->getMessage(), + 'file' => $cause->getFile(), + 'line' => $cause->getLine()); + } elseif (class_exists('PEAR_Error') && $cause instanceof PEAR_Error) { + $causes[] = array('class' => get_class($cause), + 'message' => $cause->getMessage(), + 'file' => 'unknown', + 'line' => 'unknown'); + } elseif (is_array($cause) && isset($cause['message'])) { + // PEAR_ErrorStack warning + $causes[] = array( + 'class' => $cause['package'], + 'message' => $cause['message'], + 'file' => isset($cause['context']['file']) ? + $cause['context']['file'] : + 'unknown', + 'line' => isset($cause['context']['line']) ? + $cause['context']['line'] : + 'unknown', + ); + } + } + } + } + + public function getTraceSafe() + { + if (!isset($this->_trace)) { + $this->_trace = $this->getTrace(); + if (empty($this->_trace)) { + $backtrace = debug_backtrace(); + $this->_trace = array($backtrace[count($backtrace)-1]); + } + } + return $this->_trace; + } + + public function getErrorClass() + { + $trace = $this->getTraceSafe(); + return $trace[0]['class']; + } + + public function getErrorMethod() + { + $trace = $this->getTraceSafe(); + return $trace[0]['function']; + } + + public function __toString() + { + if (isset($_SERVER['REQUEST_URI'])) { + return $this->toHtml(); + } + return $this->toText(); + } + + public function toHtml() + { + $trace = $this->getTraceSafe(); + $causes = array(); + $this->getCauseMessage($causes); + $html = '' . "\n"; + foreach ($causes as $i => $cause) { + $html .= '\n"; + } + $html .= '' . "\n" + . '' + . '' + . '' . "\n"; + + foreach ($trace as $k => $v) { + $html .= '' + . '' + . '' . "\n"; + } + $html .= '' + . '' + . '' . "\n" + . '
    ' + . str_repeat('-', $i) . ' ' . $cause['class'] . ': ' + . htmlspecialchars($cause['message']) . ' in ' . $cause['file'] . ' ' + . 'on line ' . $cause['line'] . '' + . "
    Exception trace
    #FunctionLocation
    ' . $k . ''; + if (!empty($v['class'])) { + $html .= $v['class'] . $v['type']; + } + $html .= $v['function']; + $args = array(); + if (!empty($v['args'])) { + foreach ($v['args'] as $arg) { + if (is_null($arg)) $args[] = 'null'; + elseif (is_array($arg)) $args[] = 'Array'; + elseif (is_object($arg)) $args[] = 'Object('.get_class($arg).')'; + elseif (is_bool($arg)) $args[] = $arg ? 'true' : 'false'; + elseif (is_int($arg) || is_double($arg)) $args[] = $arg; + else { + $arg = (string)$arg; + $str = htmlspecialchars(substr($arg, 0, 16)); + if (strlen($arg) > 16) $str .= '…'; + $args[] = "'" . $str . "'"; + } + } + } + $html .= '(' . implode(', ',$args) . ')' + . '' . (isset($v['file']) ? $v['file'] : 'unknown') + . ':' . (isset($v['line']) ? $v['line'] : 'unknown') + . '
    ' . ($k+1) . '{main} 
    '; + return $html; + } + + public function toText() + { + $causes = array(); + $this->getCauseMessage($causes); + $causeMsg = ''; + foreach ($causes as $i => $cause) { + $causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': ' + . $cause['message'] . ' in ' . $cause['file'] + . ' on line ' . $cause['line'] . "\n"; + } + return $causeMsg . $this->getTraceAsString(); + } +} + +?>PEAR-1.9.0/PEAR/FixPHP5PEARWarnings.php100664 764 764 231 100664 12066 PEAR-1.9.0/PEAR/Frontend.php100664 764 764 15077 100664 10357 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Frontend.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Include error handling + */ +//require_once 'PEAR.php'; + +/** + * Which user interface class is being used. + * @var string class name + */ +$GLOBALS['_PEAR_FRONTEND_CLASS'] = 'PEAR_Frontend_CLI'; + +/** + * Instance of $_PEAR_Command_uiclass. + * @var object + */ +$GLOBALS['_PEAR_FRONTEND_SINGLETON'] = null; + +/** + * Singleton-based frontend for PEAR user input/output + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Frontend extends PEAR +{ + /** + * Retrieve the frontend object + * @return PEAR_Frontend_CLI|PEAR_Frontend_Web|PEAR_Frontend_Gtk + * @static + */ + function &singleton($type = null) + { + if ($type === null) { + if (!isset($GLOBALS['_PEAR_FRONTEND_SINGLETON'])) { + $a = false; + return $a; + } + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + $a = PEAR_Frontend::setFrontendClass($type); + return $a; + } + + /** + * Set the frontend class that will be used by calls to {@link singleton()} + * + * Frontends are expected to conform to the PEAR naming standard of + * _ => DIRECTORY_SEPARATOR (PEAR_Frontend_CLI is in PEAR/Frontend/CLI.php) + * @param string $uiclass full class name + * @return PEAR_Frontend + * @static + */ + function &setFrontendClass($uiclass) + { + if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) && + is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], $uiclass)) { + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + if (!class_exists($uiclass)) { + $file = str_replace('_', '/', $uiclass) . '.php'; + if (PEAR_Frontend::isIncludeable($file)) { + include_once $file; + } + } + + if (class_exists($uiclass)) { + $obj = &new $uiclass; + // quick test to see if this class implements a few of the most + // important frontend methods + if (is_a($obj, 'PEAR_Frontend')) { + $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$obj; + $GLOBALS['_PEAR_FRONTEND_CLASS'] = $uiclass; + return $obj; + } + + $err = PEAR::raiseError("not a frontend class: $uiclass"); + return $err; + } + + $err = PEAR::raiseError("no such class: $uiclass"); + return $err; + } + + /** + * Set the frontend class that will be used by calls to {@link singleton()} + * + * Frontends are expected to be a descendant of PEAR_Frontend + * @param PEAR_Frontend + * @return PEAR_Frontend + * @static + */ + function &setFrontendObject($uiobject) + { + if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) && + is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], get_class($uiobject))) { + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + if (!is_a($uiobject, 'PEAR_Frontend')) { + $err = PEAR::raiseError('not a valid frontend class: (' . + get_class($uiobject) . ')'); + return $err; + } + + $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$uiobject; + $GLOBALS['_PEAR_FRONTEND_CLASS'] = get_class($uiobject); + return $uiobject; + } + + /** + * @param string $path relative or absolute include path + * @return boolean + * @static + */ + function isIncludeable($path) + { + if (file_exists($path) && is_readable($path)) { + return true; + } + + $fp = @fopen($path, 'r', true); + if ($fp) { + fclose($fp); + return true; + } + + return false; + } + + /** + * @param PEAR_Config + */ + function setConfig(&$config) + { + } + + /** + * This can be overridden to allow session-based temporary file management + * + * By default, all files are deleted at the end of a session. The web installer + * needs to be able to sustain a list over many sessions in order to support + * user interaction with install scripts + */ + function addTempFile($file) + { + $GLOBALS['_PEAR_Common_tempfiles'][] = $file; + } + + /** + * Log an action + * + * @param string $msg the message to log + * @param boolean $append_crlf + * @return boolean true + * @abstract + */ + function log($msg, $append_crlf = true) + { + } + + /** + * Run a post-installation script + * + * @param array $scripts array of post-install scripts + * @abstract + */ + function runPostinstallScripts(&$scripts) + { + } + + /** + * Display human-friendly output formatted depending on the + * $command parameter. + * + * This should be able to handle basic output data with no command + * @param mixed $data data structure containing the information to display + * @param string $command command from which this method was called + * @abstract + */ + function outputData($data, $command = '_default') + { + } + + /** + * Display a modal form dialog and return the given input + * + * A frontend that requires multiple requests to retrieve and process + * data must take these needs into account, and implement the request + * handling code. + * @param string $command command from which this method was called + * @param array $prompts associative array. keys are the input field names + * and values are the description + * @param array $types array of input field types (text, password, + * etc.) keys have to be the same like in $prompts + * @param array $defaults array of default values. again keys have + * to be the same like in $prompts. Do not depend + * on a default value being set. + * @return array input sent by the user + * @abstract + */ + function userDialog($command, $prompts, $types = array(), $defaults = array()) + { + } +}PEAR-1.9.0/PEAR/Installer.php100664 764 764 210603 100664 10545 + * @author Tomas V.V. Cox + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Installer.php 287446 2009-08-18 11:45:05Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * Used for installation groups in package.xml 2.0 and platform exceptions + */ +require_once 'OS/Guess.php'; +require_once 'PEAR/Downloader.php'; + +define('PEAR_INSTALLER_NOBINARY', -240); +/** + * Administration class used to install PEAR packages and maintain the + * installed package database. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Installer extends PEAR_Downloader +{ + // {{{ properties + + /** name of the package directory, for example Foo-1.0 + * @var string + */ + var $pkgdir; + + /** directory where PHP code files go + * @var string + */ + var $phpdir; + + /** directory where PHP extension files go + * @var string + */ + var $extdir; + + /** directory where documentation goes + * @var string + */ + var $docdir; + + /** installation root directory (ala PHP's INSTALL_ROOT or + * automake's DESTDIR + * @var string + */ + var $installroot = ''; + + /** debug level + * @var int + */ + var $debug = 1; + + /** temporary directory + * @var string + */ + var $tmpdir; + + /** + * PEAR_Registry object used by the installer + * @var PEAR_Registry + */ + var $registry; + + /** + * array of PEAR_Downloader_Packages + * @var array + */ + var $_downloadedPackages; + + /** List of file transactions queued for an install/upgrade/uninstall. + * + * Format: + * array( + * 0 => array("rename => array("from-file", "to-file")), + * 1 => array("delete" => array("file-to-delete")), + * ... + * ) + * + * @var array + */ + var $file_operations = array(); + + // }}} + + // {{{ constructor + + /** + * PEAR_Installer constructor. + * + * @param object $ui user interface object (instance of PEAR_Frontend_*) + * + * @access public + */ + function PEAR_Installer(&$ui) + { + parent::PEAR_Common(); + $this->setFrontendObject($ui); + $this->debug = $this->config->get('verbose'); + } + + function setOptions($options) + { + $this->_options = $options; + } + + function setConfig(&$config) + { + $this->config = &$config; + $this->_registry = &$config->getRegistry(); + } + + // }}} + + function _removeBackups($files) + { + foreach ($files as $path) { + $this->addFileOperation('removebackup', array($path)); + } + } + + // {{{ _deletePackageFiles() + + /** + * Delete a package's installed files, does not remove empty directories. + * + * @param string package name + * @param string channel name + * @param bool if true, then files are backed up first + * @return bool TRUE on success, or a PEAR error on failure + * @access protected + */ + function _deletePackageFiles($package, $channel = false, $backup = false) + { + if (!$channel) { + $channel = 'pear.php.net'; + } + + if (!strlen($package)) { + return $this->raiseError("No package to uninstall given"); + } + + if (strtolower($package) == 'pear' && $channel == 'pear.php.net') { + // to avoid race conditions, include all possible needed files + require_once 'PEAR/Task/Common.php'; + require_once 'PEAR/Task/Replace.php'; + require_once 'PEAR/Task/Unixeol.php'; + require_once 'PEAR/Task/Windowseol.php'; + require_once 'PEAR/PackageFile/v1.php'; + require_once 'PEAR/PackageFile/v2.php'; + require_once 'PEAR/PackageFile/Generator/v1.php'; + require_once 'PEAR/PackageFile/Generator/v2.php'; + } + + $filelist = $this->_registry->packageInfo($package, 'filelist', $channel); + if ($filelist == null) { + return $this->raiseError("$channel/$package not installed"); + } + + $ret = array(); + foreach ($filelist as $file => $props) { + if (empty($props['installed_as'])) { + continue; + } + + $path = $props['installed_as']; + if ($backup) { + $this->addFileOperation('backup', array($path)); + $ret[] = $path; + } + + $this->addFileOperation('delete', array($path)); + } + + if ($backup) { + return $ret; + } + + return true; + } + + // }}} + // {{{ _installFile() + + /** + * @param string filename + * @param array attributes from tag in package.xml + * @param string path to install the file in + * @param array options from command-line + * @access private + */ + function _installFile($file, $atts, $tmp_path, $options) + { + // {{{ return if this file is meant for another platform + static $os; + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + if (isset($atts['platform'])) { + if (empty($os)) { + $os = new OS_Guess(); + } + + if (strlen($atts['platform']) && $atts['platform']{0} == '!') { + $negate = true; + $platform = substr($atts['platform'], 1); + } else { + $negate = false; + $platform = $atts['platform']; + } + + if ((bool) $os->matchSignature($platform) === $negate) { + $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")"); + return PEAR_INSTALLER_SKIPPED; + } + } + // }}} + + $channel = $this->pkginfo->getChannel(); + // {{{ assemble the destination paths + switch ($atts['role']) { + case 'src': + case 'extsrc': + $this->source_files++; + return; + case 'doc': + case 'data': + case 'test': + $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) . + DIRECTORY_SEPARATOR . $this->pkginfo->getPackage(); + unset($atts['baseinstalldir']); + break; + case 'ext': + case 'php': + $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel); + break; + case 'script': + $dest_dir = $this->config->get('bin_dir', null, $channel); + break; + default: + return $this->raiseError("Invalid role `$atts[role]' for file $file"); + } + + $save_destdir = $dest_dir; + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + + if (dirname($file) != '.' && empty($atts['install-as'])) { + $dest_dir .= DIRECTORY_SEPARATOR . dirname($file); + } + + if (empty($atts['install-as'])) { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file); + } else { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as']; + } + $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file; + + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + array(DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), + array($dest_file, $orig_file)); + $final_dest_file = $installed_as = $dest_file; + if (isset($this->_options['packagingroot'])) { + $installedas_dest_dir = dirname($final_dest_file); + $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']); + } else { + $installedas_dest_dir = dirname($final_dest_file); + $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + } + + $dest_dir = dirname($final_dest_file); + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) { + return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED); + } + // }}} + + if (empty($this->_options['register-only']) && + (!file_exists($dest_dir) || !is_dir($dest_dir))) { + if (!$this->mkDirHier($dest_dir)) { + return $this->raiseError("failed to mkdir $dest_dir", + PEAR_INSTALLER_FAILED); + } + $this->log(3, "+ mkdir $dest_dir"); + } + + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (empty($atts['replacements'])) { + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + if (!@copy($orig_file, $dest_file)) { + return $this->raiseError("failed to write $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $orig_file $dest_file"); + if (isset($atts['md5sum'])) { + $md5sum = md5_file($dest_file); + } + } else { + // {{{ file with replacements + if (!file_exists($orig_file)) { + return $this->raiseError("file does not exist", + PEAR_INSTALLER_FAILED); + } + + $contents = file_get_contents($orig_file); + if ($contents === false) { + $contents = ''; + } + + if (isset($atts['md5sum'])) { + $md5sum = md5($contents); + } + + $subst_from = $subst_to = array(); + foreach ($atts['replacements'] as $a) { + $to = ''; + if ($a['type'] == 'php-const') { + if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) { + eval("\$to = $a[to];"); + } else { + if (!isset($options['soft'])) { + $this->log(0, "invalid php-const replacement: $a[to]"); + } + continue; + } + } elseif ($a['type'] == 'pear-config') { + if ($a['to'] == 'master_server') { + $chan = $this->_registry->getChannel($channel); + if (!PEAR::isError($chan)) { + $to = $chan->getServer(); + } else { + $to = $this->config->get($a['to'], null, $channel); + } + } else { + $to = $this->config->get($a['to'], null, $channel); + } + if (is_null($to)) { + if (!isset($options['soft'])) { + $this->log(0, "invalid pear-config replacement: $a[to]"); + } + continue; + } + } elseif ($a['type'] == 'package-info') { + if ($t = $this->pkginfo->packageInfo($a['to'])) { + $to = $t; + } else { + if (!isset($options['soft'])) { + $this->log(0, "invalid package-info replacement: $a[to]"); + } + continue; + } + } + if (!is_null($to)) { + $subst_from[] = $a['from']; + $subst_to[] = $to; + } + } + + $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file"); + if (sizeof($subst_from)) { + $contents = str_replace($subst_from, $subst_to, $contents); + } + + $wp = @fopen($dest_file, "wb"); + if (!is_resource($wp)) { + return $this->raiseError("failed to create $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + if (@fwrite($wp, $contents) === false) { + return $this->raiseError("failed writing to $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + fclose($wp); + // }}} + } + + // {{{ check the md5 + if (isset($md5sum)) { + if (strtolower($md5sum) === strtolower($atts['md5sum'])) { + $this->log(2, "md5sum ok: $final_dest_file"); + } else { + if (empty($options['force'])) { + // delete the file + if (file_exists($dest_file)) { + unlink($dest_file); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError("bad md5sum for file $final_dest_file", + PEAR_INSTALLER_FAILED); + } + + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } else { + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } + } + } + // }}} + // {{{ set file permissions + if (!OS_WINDOWS) { + if ($atts['role'] == 'script') { + $mode = 0777 & ~(int)octdec($this->config->get('umask')); + $this->log(3, "+ chmod +x $dest_file"); + } else { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + } + + if ($atts['role'] != 'src') { + $this->addFileOperation("chmod", array($mode, $dest_file)); + if (!@chmod($dest_file, $mode)) { + if (!isset($options['soft'])) { + $this->log(0, "failed to change mode of $dest_file: $php_errormsg"); + } + } + } + } + // }}} + + if ($atts['role'] == 'src') { + rename($dest_file, $final_dest_file); + $this->log(2, "renamed source file $dest_file to $final_dest_file"); + } else { + $this->addFileOperation("rename", array($dest_file, $final_dest_file, + $atts['role'] == 'ext')); + } + } + + // Store the full path where the file was installed for easy unistall + if ($atts['role'] != 'script') { + $loc = $this->config->get($atts['role'] . '_dir'); + } else { + $loc = $this->config->get('bin_dir'); + } + + if ($atts['role'] != 'src') { + $this->addFileOperation("installed_as", array($file, $installed_as, + $loc, + dirname(substr($installedas_dest_file, strlen($loc))))); + } + + //$this->log(2, "installed: $dest_file"); + return PEAR_INSTALLER_OK; + } + + // }}} + // {{{ _installFile2() + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string filename + * @param array attributes from tag in package.xml + * @param string path to install the file in + * @param array options from command-line + * @access private + */ + function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options) + { + $atts = $real_atts; + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + $channel = $pkg->getChannel(); + // {{{ assemble the destination paths + if (!in_array($atts['attribs']['role'], + PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) { + return $this->raiseError('Invalid role `' . $atts['attribs']['role'] . + "' for file $file"); + } + + $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config); + $err = $role->setup($this, $pkg, $atts['attribs'], $file); + if (PEAR::isError($err)) { + return $err; + } + + if (!$role->isInstallable()) { + return; + } + + $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path); + if (PEAR::isError($info)) { + return $info; + } + + list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info; + if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) { + return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED); + } + + $final_dest_file = $installed_as = $dest_file; + if (isset($this->_options['packagingroot'])) { + $final_dest_file = $this->_prependPath($final_dest_file, + $this->_options['packagingroot']); + } + + $dest_dir = dirname($final_dest_file); + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + // }}} + + if (empty($this->_options['register-only'])) { + if (!file_exists($dest_dir) || !is_dir($dest_dir)) { + if (!$this->mkDirHier($dest_dir)) { + return $this->raiseError("failed to mkdir $dest_dir", + PEAR_INSTALLER_FAILED); + } + $this->log(3, "+ mkdir $dest_dir"); + } + } + + $attribs = $atts['attribs']; + unset($atts['attribs']); + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (!count($atts)) { // no tasks + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + if (!@copy($orig_file, $dest_file)) { + return $this->raiseError("failed to write $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $orig_file $dest_file"); + if (isset($attribs['md5sum'])) { + $md5sum = md5_file($dest_file); + } + } else { // file with tasks + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + $contents = file_get_contents($orig_file); + if ($contents === false) { + $contents = ''; + } + + if (isset($attribs['md5sum'])) { + $md5sum = md5($contents); + } + + foreach ($atts as $tag => $raw) { + $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag); + $task = "PEAR_Task_$tag"; + $task = &new $task($this->config, $this, PEAR_TASK_INSTALL); + if (!$task->isScript()) { // scripts are only handled after installation + $task->init($raw, $attribs, $pkg->getLastInstalledVersion()); + $res = $task->startSession($pkg, $contents, $final_dest_file); + if ($res === false) { + continue; // skip this file + } + + if (PEAR::isError($res)) { + return $res; + } + + $contents = $res; // save changes + } + + $wp = @fopen($dest_file, "wb"); + if (!is_resource($wp)) { + return $this->raiseError("failed to create $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + if (fwrite($wp, $contents) === false) { + return $this->raiseError("failed writing to $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + fclose($wp); + } + } + + // {{{ check the md5 + if (isset($md5sum)) { + // Make sure the original md5 sum matches with expected + if (strtolower($md5sum) === strtolower($attribs['md5sum'])) { + $this->log(2, "md5sum ok: $final_dest_file"); + + if (isset($contents)) { + // set md5 sum based on $content in case any tasks were run. + $real_atts['attribs']['md5sum'] = md5($contents); + } + } else { + if (empty($options['force'])) { + // delete the file + if (file_exists($dest_file)) { + unlink($dest_file); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError("bad md5sum for file $final_dest_file", + PEAR_INSTALLER_FAILED); + } + + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } else { + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } + } + } else { + $real_atts['attribs']['md5sum'] = md5_file($dest_file); + } + + // }}} + // {{{ set file permissions + if (!OS_WINDOWS) { + if ($role->isExecutable()) { + $mode = 0777 & ~(int)octdec($this->config->get('umask')); + $this->log(3, "+ chmod +x $dest_file"); + } else { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + } + + if ($attribs['role'] != 'src') { + $this->addFileOperation("chmod", array($mode, $dest_file)); + if (!@chmod($dest_file, $mode)) { + if (!isset($options['soft'])) { + $this->log(0, "failed to change mode of $dest_file: $php_errormsg"); + } + } + } + } + // }}} + + if ($attribs['role'] == 'src') { + rename($dest_file, $final_dest_file); + $this->log(2, "renamed source file $dest_file to $final_dest_file"); + } else { + $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension())); + } + } + + // Store the full path where the file was installed for easy uninstall + if ($attribs['role'] != 'src') { + $loc = $this->config->get($role->getLocationConfig(), null, $channel); + $this->addFileOperation('installed_as', array($file, $installed_as, + $loc, + dirname(substr($installed_as, strlen($loc))))); + } + + //$this->log(2, "installed: $dest_file"); + return PEAR_INSTALLER_OK; + } + + // }}} + // {{{ addFileOperation() + + /** + * Add a file operation to the current file transaction. + * + * @see startFileTransaction() + * @param string $type This can be one of: + * - rename: rename a file ($data has 3 values) + * - backup: backup an existing file ($data has 1 value) + * - removebackup: clean up backups created during install ($data has 1 value) + * - chmod: change permissions on a file ($data has 2 values) + * - delete: delete a file ($data has 1 value) + * - rmdir: delete a directory if empty ($data has 1 value) + * - installed_as: mark a file as installed ($data has 4 values). + * @param array $data For all file operations, this array must contain the + * full path to the file or directory that is being operated on. For + * the rename command, the first parameter must be the file to rename, + * the second its new name, the third whether this is a PHP extension. + * + * The installed_as operation contains 4 elements in this order: + * 1. Filename as listed in the filelist element from package.xml + * 2. Full path to the installed file + * 3. Full path from the php_dir configuration variable used in this + * installation + * 4. Relative path from the php_dir that this file is installed in + */ + function addFileOperation($type, $data) + { + if (!is_array($data)) { + return $this->raiseError('Internal Error: $data in addFileOperation' + . ' must be an array, was ' . gettype($data)); + } + + if ($type == 'chmod') { + $octmode = decoct($data[0]); + $this->log(3, "adding to transaction: $type $octmode $data[1]"); + } else { + $this->log(3, "adding to transaction: $type " . implode(" ", $data)); + } + $this->file_operations[] = array($type, $data); + } + + // }}} + // {{{ startFileTransaction() + + function startFileTransaction($rollback_in_case = false) + { + if (count($this->file_operations) && $rollback_in_case) { + $this->rollbackFileTransaction(); + } + $this->file_operations = array(); + } + + // }}} + // {{{ commitFileTransaction() + + function commitFileTransaction() + { + $n = count($this->file_operations); + $this->log(2, "about to commit $n file operations"); + // {{{ first, check permissions and such manually + $errors = array(); + foreach ($this->file_operations as $tr) { + list($type, $data) = $tr; + switch ($type) { + case 'rename': + if (!file_exists($data[0])) { + $errors[] = "cannot rename file $data[0], doesn't exist"; + } + + // check that dest dir. is writable + if (!is_writable(dirname($data[1]))) { + $errors[] = "permission denied ($type): $data[1]"; + } + break; + case 'chmod': + // check that file is writable + if (!is_writable($data[1])) { + $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]); + } + break; + case 'delete': + if (!file_exists($data[0])) { + $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted"); + } + // check that directory is writable + if (file_exists($data[0])) { + if (!is_writable(dirname($data[0]))) { + $errors[] = "permission denied ($type): $data[0]"; + } else { + // make sure the file to be deleted can be opened for writing + $fp = false; + if (!is_dir($data[0]) && + (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) { + $errors[] = "permission denied ($type): $data[0]"; + } elseif ($fp) { + fclose($fp); + } + } + } + break; + } + + } + // }}} + $m = count($errors); + if ($m > 0) { + foreach ($errors as $error) { + if (!isset($this->_options['soft'])) { + $this->log(1, $error); + } + } + + if (!isset($this->_options['ignore-errors'])) { + return false; + } + } + + $this->_dirtree = array(); + // {{{ really commit the transaction + foreach ($this->file_operations as $i => $tr) { + if (!$tr) { + // support removal of non-existing backups + continue; + } + + list($type, $data) = $tr; + switch ($type) { + case 'backup': + if (!file_exists($data[0])) { + $this->file_operations[$i] = false; + break; + } + + if (!@copy($data[0], $data[0] . '.bak')) { + $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] . + '.bak ' . $php_errormsg); + return false; + } + $this->log(3, "+ backup $data[0] to $data[0].bak"); + break; + case 'removebackup': + if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) { + unlink($data[0] . '.bak'); + $this->log(3, "+ rm backup of $data[0] ($data[0].bak)"); + } + break; + case 'rename': + $test = file_exists($data[1]) ? @unlink($data[1]) : null; + if (!$test && file_exists($data[1])) { + if ($data[2]) { + $extra = ', this extension must be installed manually. Rename to "' . + basename($data[1]) . '"'; + } else { + $extra = ''; + } + + if (!isset($this->_options['soft'])) { + $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' . + $data[0] . $extra); + } + + if (!isset($this->_options['ignore-errors'])) { + return false; + } + } + + // permissions issues with rename - copy() is far superior + $perms = @fileperms($data[0]); + if (!@copy($data[0], $data[1])) { + $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] . + ' ' . $php_errormsg); + return false; + } + + // copy over permissions, otherwise they are lost + @chmod($data[1], $perms); + @unlink($data[0]); + $this->log(3, "+ mv $data[0] $data[1]"); + break; + case 'chmod': + if (!@chmod($data[1], $data[0])) { + $this->log(1, 'Could not chmod ' . $data[1] . ' to ' . + decoct($data[0]) . ' ' . $php_errormsg); + return false; + } + + $octmode = decoct($data[0]); + $this->log(3, "+ chmod $octmode $data[1]"); + break; + case 'delete': + if (file_exists($data[0])) { + if (!@unlink($data[0])) { + $this->log(1, 'Could not delete ' . $data[0] . ' ' . + $php_errormsg); + return false; + } + $this->log(3, "+ rm $data[0]"); + } + break; + case 'rmdir': + if (file_exists($data[0])) { + do { + $testme = opendir($data[0]); + while (false !== ($entry = readdir($testme))) { + if ($entry == '.' || $entry == '..') { + continue; + } + closedir($testme); + break 2; // this directory is not empty and can't be + // deleted + } + + closedir($testme); + if (!@rmdir($data[0])) { + $this->log(1, 'Could not rmdir ' . $data[0] . ' ' . + $php_errormsg); + return false; + } + $this->log(3, "+ rmdir $data[0]"); + } while (false); + } + break; + case 'installed_as': + $this->pkginfo->setInstalledAs($data[0], $data[1]); + if (!isset($this->_dirtree[dirname($data[1])])) { + $this->_dirtree[dirname($data[1])] = true; + $this->pkginfo->setDirtree(dirname($data[1])); + + while(!empty($data[3]) && dirname($data[3]) != $data[3] && + $data[3] != '/' && $data[3] != '\\') { + $this->pkginfo->setDirtree($pp = + $this->_prependPath($data[3], $data[2])); + $this->_dirtree[$pp] = true; + $data[3] = dirname($data[3]); + } + } + break; + } + } + // }}} + $this->log(2, "successfully committed $n file operations"); + $this->file_operations = array(); + return true; + } + + // }}} + // {{{ rollbackFileTransaction() + + function rollbackFileTransaction() + { + $n = count($this->file_operations); + $this->log(2, "rolling back $n file operations"); + foreach ($this->file_operations as $tr) { + list($type, $data) = $tr; + switch ($type) { + case 'backup': + if (file_exists($data[0] . '.bak')) { + if (file_exists($data[0] && is_writable($data[0]))) { + unlink($data[0]); + } + @copy($data[0] . '.bak', $data[0]); + $this->log(3, "+ restore $data[0] from $data[0].bak"); + } + break; + case 'removebackup': + if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) { + unlink($data[0] . '.bak'); + $this->log(3, "+ rm backup of $data[0] ($data[0].bak)"); + } + break; + case 'rename': + @unlink($data[0]); + $this->log(3, "+ rm $data[0]"); + break; + case 'mkdir': + @rmdir($data[0]); + $this->log(3, "+ rmdir $data[0]"); + break; + case 'chmod': + break; + case 'delete': + break; + case 'installed_as': + $this->pkginfo->setInstalledAs($data[0], false); + break; + } + } + $this->pkginfo->resetDirtree(); + $this->file_operations = array(); + } + + // }}} + // {{{ mkDirHier($dir) + + function mkDirHier($dir) + { + $this->addFileOperation('mkdir', array($dir)); + return parent::mkDirHier($dir); + } + + // }}} + // {{{ download() + + /** + * Download any files and their dependencies, if necessary + * + * @param array a mixed list of package names, local files, or package.xml + * @param PEAR_Config + * @param array options from the command line + * @param array this is the array that will be populated with packages to + * install. Format of each entry: + * + * + * array('pkg' => 'package_name', 'file' => '/path/to/local/file', + * 'info' => array() // parsed package.xml + * ); + * + * @param array this will be populated with any error messages + * @param false private recursion variable + * @param false private recursion variable + * @param false private recursion variable + * @deprecated in favor of PEAR_Downloader + */ + function download($packages, $options, &$config, &$installpackages, + &$errors, $installed = false, $willinstall = false, $state = false) + { + // trickiness: initialize here + parent::PEAR_Downloader($this->ui, $options, $config); + $ret = parent::download($packages); + $errors = $this->getErrorMsgs(); + $installpackages = $this->getDownloadedPackages(); + trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " . + "in favor of PEAR_Downloader class", E_USER_WARNING); + return $ret; + } + + // }}} + // {{{ _parsePackageXml() + + function _parsePackageXml(&$descfile, &$tmpdir) + { + if (substr($descfile, -4) == '.xml') { + $tmpdir = false; + } else { + // {{{ Decompress pack in tmp dir ------------------------------------- + + // To allow relative package file names + $descfile = realpath($descfile); + + if (PEAR::isError($tmpdir = System::mktemp('-d'))) { + return $tmpdir; + } + $this->log(3, '+ tmp dir created at ' . $tmpdir); + // }}} + } + + // Parse xml file ----------------------------------------------- + $pkg = new PEAR_PackageFile($this->config, $this->debug, $tmpdir); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($p)) { + if (is_array($p->getUserInfo())) { + foreach ($p->getUserInfo() as $err) { + $loglevel = $err['level'] == 'error' ? 0 : 1; + if (!isset($this->_options['soft'])) { + $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']); + } + } + } + return $this->raiseError('Installation failed: invalid package file'); + } + + $descfile = $p->getPackageFile(); + return $p; + } + + // }}} + /** + * Set the list of PEAR_Downloader_Package objects to allow more sane + * dependency validation + * @param array + */ + function setDownloadedPackages(&$pkgs) + { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($pkgs); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + return $err; + } + $this->_downloadedPackages = &$pkgs; + } + + /** + * Set the list of PEAR_Downloader_Package objects to allow more sane + * dependency validation + * @param array + */ + function setUninstallPackages(&$pkgs) + { + $this->_downloadedPackages = &$pkgs; + } + + function getInstallPackages() + { + return $this->_downloadedPackages; + } + + // {{{ install() + + /** + * Installs the files within the package file specified. + * + * @param string|PEAR_Downloader_Package $pkgfile path to the package file, + * or a pre-initialized packagefile object + * @param array $options + * recognized options: + * - installroot : optional prefix directory for installation + * - force : force installation + * - register-only : update registry but don't install files + * - upgrade : upgrade existing install + * - soft : fail silently + * - nodeps : ignore dependency conflicts/missing dependencies + * - alldeps : install all dependencies + * - onlyreqdeps : install only required dependencies + * + * @return array|PEAR_Error package info if successful + */ + function install($pkgfile, $options = array()) + { + $this->_options = $options; + $this->_registry = &$this->config->getRegistry(); + if (is_object($pkgfile)) { + $dlpkg = &$pkgfile; + $pkg = $pkgfile->getPackageFile(); + $pkgfile = $pkg->getArchiveFile(); + $descfile = $pkg->getPackageFile(); + $tmpdir = dirname($descfile); + } else { + $descfile = $pkgfile; + $tmpdir = ''; + $pkg = $this->_parsePackageXml($descfile, $tmpdir); + if (PEAR::isError($pkg)) { + return $pkg; + } + } + + if (realpath($descfile) != realpath($pkgfile)) { + $tar = new Archive_Tar($pkgfile); + if (!$tar->extract($tmpdir)) { + return $this->raiseError("unable to unpack $pkgfile"); + } + } + + $pkgname = $pkg->getName(); + $channel = $pkg->getChannel(); + if (isset($this->_options['packagingroot'])) { + $regdir = $this->_prependPath( + $this->config->get('php_dir', null, 'pear.php.net'), + $this->_options['packagingroot']); + + $packrootphp_dir = $this->_prependPath( + $this->config->get('php_dir', null, $channel), + $this->_options['packagingroot']); + } + + if (isset($options['installroot'])) { + $this->config->setInstallRoot($options['installroot']); + $this->_registry = &$this->config->getRegistry(); + $installregistry = &$this->_registry; + $this->installroot = ''; // all done automagically now + $php_dir = $this->config->get('php_dir', null, $channel); + } else { + $this->config->setInstallRoot(false); + $this->_registry = &$this->config->getRegistry(); + if (isset($this->_options['packagingroot'])) { + $installregistry = &new PEAR_Registry($regdir); + if (!$installregistry->channelExists($channel, true)) { + // we need to fake a channel-discover of this channel + $chanobj = $this->_registry->getChannel($channel, true); + $installregistry->addChannel($chanobj); + } + $php_dir = $packrootphp_dir; + } else { + $installregistry = &$this->_registry; + $php_dir = $this->config->get('php_dir', null, $channel); + } + $this->installroot = ''; + } + + // {{{ checks to do when not in "force" mode + if (empty($options['force']) && + (file_exists($this->config->get('php_dir')) && + is_dir($this->config->get('php_dir')))) { + $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname); + $instfilelist = $pkg->getInstallationFileList(true); + if (PEAR::isError($instfilelist)) { + return $instfilelist; + } + + // ensure we have the most accurate registry + $installregistry->flushFileMap(); + $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1'); + if (PEAR::isError($test)) { + return $test; + } + + if (sizeof($test)) { + $pkgs = $this->getInstallPackages(); + $found = false; + foreach ($pkgs as $param) { + if ($pkg->isSubpackageOf($param)) { + $found = true; + break; + } + } + + if ($found) { + // subpackages can conflict with earlier versions of parent packages + $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel()); + $tmp = $test; + foreach ($tmp as $file => $info) { + if (is_array($info)) { + if (strtolower($info[1]) == strtolower($param->getPackage()) && + strtolower($info[0]) == strtolower($param->getChannel()) + ) { + if (isset($parentreg['filelist'][$file])) { + unset($parentreg['filelist'][$file]); + } else{ + $pos = strpos($file, '/'); + $basedir = substr($file, 0, $pos); + $file2 = substr($file, $pos + 1); + if (isset($parentreg['filelist'][$file2]['baseinstalldir']) + && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir + ) { + unset($parentreg['filelist'][$file2]); + } + } + + unset($test[$file]); + } + } else { + if (strtolower($param->getChannel()) != 'pear.php.net') { + continue; + } + + if (strtolower($info) == strtolower($param->getPackage())) { + if (isset($parentreg['filelist'][$file])) { + unset($parentreg['filelist'][$file]); + } else{ + $pos = strpos($file, '/'); + $basedir = substr($file, 0, $pos); + $file2 = substr($file, $pos + 1); + if (isset($parentreg['filelist'][$file2]['baseinstalldir']) + && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir + ) { + unset($parentreg['filelist'][$file2]); + } + } + + unset($test[$file]); + } + } + } + + $pfk = &new PEAR_PackageFile($this->config); + $parentpkg = &$pfk->fromArray($parentreg); + $installregistry->updatePackage2($parentpkg); + } + + if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) { + $tmp = $test; + foreach ($tmp as $file => $info) { + if (is_string($info)) { + // pear.php.net packages are always stored as strings + if (strtolower($info) == strtolower($param->getPackage())) { + // upgrading existing package + unset($test[$file]); + } + } + } + } + + if (count($test)) { + $msg = "$channel/$pkgname: conflicting files found:\n"; + $longest = max(array_map("strlen", array_keys($test))); + $fmt = "%${longest}s (%s)\n"; + foreach ($test as $file => $info) { + if (!is_array($info)) { + $info = array('pear.php.net', $info); + } + $info = $info[0] . '/' . $info[1]; + $msg .= sprintf($fmt, $file, $info); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError($msg); + } + + if (!isset($options['soft'])) { + $this->log(0, "WARNING: $msg"); + } + } + } + } + // }}} + + $this->startFileTransaction(); + + if (empty($options['upgrade']) && empty($options['soft'])) { + // checks to do only when installing new packages + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + } + } else { + $test = $installregistry->packageExists($pkgname, $channel); + } + + if (empty($options['force']) && $test) { + return $this->raiseError("$channel/$pkgname is already installed"); + } + } else { + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + $usechannel = 'pear.php.net'; + } + } else { + $test = $installregistry->packageExists($pkgname, $channel); + } + + if ($test) { + $v1 = $installregistry->packageInfo($pkgname, 'version', $usechannel); + $v2 = $pkg->getVersion(); + $cmp = version_compare("$v1", "$v2", 'gt'); + if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) { + return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)"); + } + + if (empty($options['register-only'])) { + // when upgrading, remove old release's files first: + if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel, + true))) { + if (!isset($options['ignore-errors'])) { + return $this->raiseError($err); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $err->getMessage()); + } + } else { + $backedup = $err; + } + } + } + } + + // {{{ Copy files to dest dir --------------------------------------- + + // info from the package it self we want to access from _installFile + $this->pkginfo = &$pkg; + // used to determine whether we should build any C code + $this->source_files = 0; + + $savechannel = $this->config->get('default_channel'); + if (empty($options['register-only']) && !is_dir($php_dir)) { + if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) { + return $this->raiseError("no installation destination directory '$php_dir'\n"); + } + } + + $tmp_path = dirname($descfile); + if (substr($pkgfile, -4) != '.xml') { + $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion(); + } + + $this->configSet('default_channel', $channel); + // {{{ install files + + $ver = $pkg->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + $filelist = $pkg->getInstallationFilelist(); + } else { + $filelist = $pkg->getFileList(); + } + + if (PEAR::isError($filelist)) { + return $filelist; + } + + $p = &$installregistry->getPackage($pkgname, $channel); + $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false; + + $pkg->resetFilelist(); + $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(), + 'version', $pkg->getChannel())); + foreach ($filelist as $file => $atts) { + $this->expectError(PEAR_INSTALLER_FAILED); + if ($pkg->getPackagexmlVersion() == '1.0') { + $res = $this->_installFile($file, $atts, $tmp_path, $options); + } else { + $res = $this->_installFile2($pkg, $file, $atts, $tmp_path, $options); + } + $this->popExpect(); + + if (PEAR::isError($res)) { + if (empty($options['ignore-errors'])) { + $this->rollbackFileTransaction(); + if ($res->getMessage() == "file does not exist") { + $this->raiseError("file $file in package.xml does not exist"); + } + + return $this->raiseError($res); + } + + if (!isset($options['soft'])) { + $this->log(0, "Warning: " . $res->getMessage()); + } + } + + $real = isset($atts['attribs']) ? $atts['attribs'] : $atts; + if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') { + // Register files that were installed + $pkg->installedFile($file, $atts); + } + } + // }}} + + // {{{ compile and install source files + if ($this->source_files > 0 && empty($options['nobuild'])) { + if (PEAR::isError($err = + $this->_compileSourceFiles($savechannel, $pkg))) { + return $err; + } + } + // }}} + + if (isset($backedup)) { + $this->_removeBackups($backedup); + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED); + } + // }}} + + $ret = false; + $installphase = 'install'; + $oldversion = false; + // {{{ Register that the package is installed ----------------------- + if (empty($options['upgrade'])) { + // if 'force' is used, replace the info in registry + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + $usechannel = 'pear.php.net'; + } + } else { + $test = $installregistry->packageExists($pkgname, $channel); + } + + if (!empty($options['force']) && $test) { + $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel); + $installregistry->deletePackage($pkgname, $usechannel); + } + $ret = $installregistry->addPackage2($pkg); + } else { + if ($dirtree) { + $this->startFileTransaction(); + // attempt to delete empty directories + uksort($dirtree, array($this, '_sortDirs')); + foreach($dirtree as $dir => $notused) { + $this->addFileOperation('rmdir', array($dir)); + } + $this->commitFileTransaction(); + } + + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + $usechannel = 'pear.php.net'; + } + } else { + $test = $installregistry->packageExists($pkgname, $channel); + } + + // new: upgrade installs a package if it isn't installed + if (!$test) { + $ret = $installregistry->addPackage2($pkg); + } else { + if ($usechannel != $channel) { + $installregistry->deletePackage($pkgname, $usechannel); + $ret = $installregistry->addPackage2($pkg); + } else { + $ret = $installregistry->updatePackage2($pkg); + } + $installphase = 'upgrade'; + } + } + + if (!$ret) { + $this->configSet('default_channel', $savechannel); + return $this->raiseError("Adding package $channel/$pkgname to registry failed"); + } + // }}} + + $this->configSet('default_channel', $savechannel); + if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist + if (PEAR_Task_Common::hasPostinstallTasks()) { + PEAR_Task_Common::runPostinstallTasks($installphase); + } + } + + return $pkg->toArray(true); + } + + // }}} + + // {{{ _compileSourceFiles() + /** + * @param string + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function _compileSourceFiles($savechannel, &$filelist) + { + require_once 'PEAR/Builder.php'; + $this->log(1, "$this->source_files source files, building"); + $bob = &new PEAR_Builder($this->ui); + $bob->debug = $this->debug; + $built = $bob->build($filelist, array(&$this, '_buildCallback')); + if (PEAR::isError($built)) { + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + return $built; + } + + $this->log(1, "\nBuild process completed successfully"); + foreach ($built as $ext) { + $bn = basename($ext['file']); + list($_ext_name, $_ext_suff) = explode('.', $bn); + if ($_ext_suff == '.so' || $_ext_suff == '.dll') { + if (extension_loaded($_ext_name)) { + $this->raiseError("Extension '$_ext_name' already loaded. " . + 'Please unload it in your php.ini file ' . + 'prior to install or upgrade'); + } + $role = 'ext'; + } else { + $role = 'src'; + } + + $dest = $ext['dest']; + $packagingroot = ''; + if (isset($this->_options['packagingroot'])) { + $packagingroot = $this->_options['packagingroot']; + } + + $copyto = $this->_prependPath($dest, $packagingroot); + $extra = $copyto != $dest ? " as '$copyto'" : ''; + $this->log(1, "Installing '$dest'$extra"); + + $copydir = dirname($copyto); + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (!file_exists($copydir) || !is_dir($copydir)) { + if (!$this->mkDirHier($copydir)) { + return $this->raiseError("failed to mkdir $copydir", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ mkdir $copydir"); + } + + if (!@copy($ext['file'], $copyto)) { + return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $ext[file] $copyto"); + $this->addFileOperation('rename', array($ext['file'], $copyto)); + if (!OS_WINDOWS) { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + $this->addFileOperation('chmod', array($mode, $copyto)); + if (!@chmod($copyto, $mode)) { + $this->log(0, "failed to change mode of $copyto ($php_errormsg)"); + } + } + } + + + $data = array( + 'role' => $role, + 'name' => $bn, + 'installed_as' => $dest, + 'php_api' => $ext['php_api'], + 'zend_mod_api' => $ext['zend_mod_api'], + 'zend_ext_api' => $ext['zend_ext_api'], + ); + + if ($filelist->getPackageXmlVersion() == '1.0') { + $filelist->installedFile($bn, $data); + } else { + $filelist->installedFile($bn, array('attribs' => $data)); + } + } + } + + // }}} + function &getUninstallPackages() + { + return $this->_downloadedPackages; + } + // {{{ uninstall() + + /** + * Uninstall a package + * + * This method removes all files installed by the application, and then + * removes any empty directories. + * @param string package name + * @param array Command-line options. Possibilities include: + * + * - installroot: base installation dir, if not the default + * - register-only : update registry but don't remove files + * - nodeps: do not process dependencies of other packages to ensure + * uninstallation does not break things + */ + function uninstall($package, $options = array()) + { + $installRoot = isset($options['installroot']) ? $options['installroot'] : ''; + $this->config->setInstallRoot($installRoot); + + $this->installroot = ''; + $this->_registry = &$this->config->getRegistry(); + if (is_object($package)) { + $channel = $package->getChannel(); + $pkg = $package; + $package = $pkg->getPackage(); + } else { + $pkg = false; + $info = $this->_registry->parsePackageName($package, + $this->config->get('default_channel')); + $channel = $info['channel']; + $package = $info['package']; + } + + $savechannel = $this->config->get('default_channel'); + $this->configSet('default_channel', $channel); + if (!is_object($pkg)) { + $pkg = $this->_registry->getPackage($package, $channel); + } + + if (!$pkg) { + $this->configSet('default_channel', $savechannel); + return $this->raiseError($this->_registry->parsedPackageNameToString( + array( + 'channel' => $channel, + 'package' => $package + ), true) . ' not installed'); + } + + if ($pkg->getInstalledBinary()) { + // this is just an alias for a binary package + return $this->_registry->deletePackage($package, $channel); + } + + $filelist = $pkg->getFilelist(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + $depchecker = &new PEAR_Dependency2($this->config, $options, + array('channel' => $channel, 'package' => $package), + PEAR_VALIDATE_UNINSTALLING); + $e = $depchecker->validatePackageUninstall($this); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['ignore-errors'])) { + return $this->raiseError($e); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $e->getMessage()); + } + } elseif (is_array($e)) { + if (!isset($options['soft'])) { + $this->log(0, $e[0]); + } + } + + $this->pkginfo = &$pkg; + // pretty much nothing happens if we are only registering the uninstall + if (empty($options['register-only'])) { + // {{{ Delete the files + $this->startFileTransaction(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) { + PEAR::popErrorHandling(); + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + if (!isset($options['ignore-errors'])) { + return $this->raiseError($err); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $err->getMessage()); + } + } else { + PEAR::popErrorHandling(); + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + if (!isset($options['ignore-errors'])) { + return $this->raiseError("uninstall failed"); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: uninstall failed'); + } + } else { + $this->startFileTransaction(); + $dirtree = $pkg->getDirTree(); + if ($dirtree === false) { + $this->configSet('default_channel', $savechannel); + return $this->_registry->deletePackage($package, $channel); + } + + // attempt to delete empty directories + uksort($dirtree, array($this, '_sortDirs')); + foreach($dirtree as $dir => $notused) { + $this->addFileOperation('rmdir', array($dir)); + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + if (!isset($options['ignore-errors'])) { + return $this->raiseError("uninstall failed"); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: uninstall failed'); + } + } + } + // }}} + } + + $this->configSet('default_channel', $savechannel); + // Register that the package is no longer installed + return $this->_registry->deletePackage($package, $channel); + } + + /** + * Sort a list of arrays of array(downloaded packagefilename) by dependency. + * + * It also removes duplicate dependencies + * @param array an array of PEAR_PackageFile_v[1/2] objects + * @return array|PEAR_Error array of array(packagefilename, package.xml contents) + */ + function sortPackagesForUninstall(&$packages) + { + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config); + if (PEAR::isError($this->_dependencyDB)) { + return $this->_dependencyDB; + } + usort($packages, array(&$this, '_sortUninstall')); + } + + function _sortUninstall($a, $b) + { + if (!$a->getDeps() && !$b->getDeps()) { + return 0; // neither package has dependencies, order is insignificant + } + if ($a->getDeps() && !$b->getDeps()) { + return -1; // $a must be installed after $b because $a has dependencies + } + if (!$a->getDeps() && $b->getDeps()) { + return 1; // $b must be installed after $a because $b has dependencies + } + // both packages have dependencies + if ($this->_dependencyDB->dependsOn($a, $b)) { + return -1; + } + if ($this->_dependencyDB->dependsOn($b, $a)) { + return 1; + } + return 0; + } + + // }}} + // {{{ _sortDirs() + function _sortDirs($a, $b) + { + if (strnatcmp($a, $b) == -1) return 1; + if (strnatcmp($a, $b) == 1) return -1; + return 0; + } + + // }}} + + // {{{ _buildCallback() + + function _buildCallback($what, $data) + { + if (($what == 'cmdoutput' && $this->debug > 1) || + ($what == 'output' && $this->debug > 0)) { + $this->ui->outputData(rtrim($data), 'build'); + } + } + + // }}} +}PEAR-1.9.0/PEAR/PackageFile.php100664 764 764 37466 100664 10741 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: PackageFile.php 286670 2009-08-02 14:16:06Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * needed for PEAR_VALIDATE_* constants + */ +require_once 'PEAR/Validate.php'; +/** + * Error code if the package.xml tag does not contain a valid version + */ +define('PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION', 1); +/** + * Error code if the package.xml tag version is not supported (version 1.0 and 1.1 are the only supported versions, + * currently + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_PACKAGEVERSION', 2); +/** + * Abstraction for the package.xml package description file + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile +{ + /** + * @var PEAR_Config + */ + var $_config; + var $_debug; + /** + * Temp directory for uncompressing tgz files. + * @var string|false + */ + var $_tmpdir; + var $_logger = false; + /** + * @var boolean + */ + var $_rawReturn = false; + + /** + * + * @param PEAR_Config $config + * @param ? $debug + * @param string @tmpdir Optional temporary directory for uncompressing + * files + */ + function PEAR_PackageFile(&$config, $debug = false, $tmpdir = false) + { + $this->_config = $config; + $this->_debug = $debug; + $this->_tmpdir = $tmpdir; + } + + /** + * Turn off validation - return a parsed package.xml without checking it + * + * This is used by the package-validate command + */ + function rawReturn() + { + $this->_rawReturn = true; + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + /** + * Create a PEAR_PackageFile_Parser_v* of a given version. + * @param int $version + * @return PEAR_PackageFile_Parser_v1|PEAR_PackageFile_Parser_v1 + */ + function &parserFactory($version) + { + if (!in_array($version{0}, array('1', '2'))) { + $a = false; + return $a; + } + + include_once 'PEAR/PackageFile/Parser/v' . $version{0} . '.php'; + $version = $version{0}; + $class = "PEAR_PackageFile_Parser_v$version"; + $a = new $class; + return $a; + } + + /** + * For simpler unit-testing + * @return string + */ + function getClassPrefix() + { + return 'PEAR_PackageFile_v'; + } + + /** + * Create a PEAR_PackageFile_v* of a given version. + * @param int $version + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v1 + */ + function &factory($version) + { + if (!in_array($version{0}, array('1', '2'))) { + $a = false; + return $a; + } + + include_once 'PEAR/PackageFile/v' . $version{0} . '.php'; + $version = $version{0}; + $class = $this->getClassPrefix() . $version; + $a = new $class; + return $a; + } + + /** + * Create a PEAR_PackageFile_v* from its toArray() method + * + * WARNING: no validation is performed, the array is assumed to be valid, + * always parse from xml if you want validation. + * @param array $arr + * @return PEAR_PackageFileManager_v1|PEAR_PackageFileManager_v2 + * @uses factory() to construct the returned object. + */ + function &fromArray($arr) + { + if (isset($arr['xsdversion'])) { + $obj = &$this->factory($arr['xsdversion']); + if ($this->_logger) { + $obj->setLogger($this->_logger); + } + + $obj->setConfig($this->_config); + $obj->fromArray($arr); + return $obj; + } + + if (isset($arr['package']['attribs']['version'])) { + $obj = &$this->factory($arr['package']['attribs']['version']); + } else { + $obj = &$this->factory('1.0'); + } + + if ($this->_logger) { + $obj->setLogger($this->_logger); + } + + $obj->setConfig($this->_config); + $obj->fromArray($arr); + return $obj; + } + + /** + * Create a PEAR_PackageFile_v* from an XML string. + * @access public + * @param string $data contents of package.xml file + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @param string $file full path to the package.xml file (and the files + * it references) + * @param string $archive optional name of the archive that the XML was + * extracted from, if any + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses parserFactory() to construct a parser to load the package. + */ + function &fromXmlString($data, $state, $file, $archive = false) + { + if (preg_match('/]+version="([0-9]+\.[0-9]+)"/', $data, $packageversion)) { + if (!in_array($packageversion[1], array('1.0', '2.0', '2.1'))) { + return PEAR::raiseError('package.xml version "' . $packageversion[1] . + '" is not supported, only 1.0, 2.0, and 2.1 are supported.'); + } + + $object = &$this->parserFactory($packageversion[1]); + if ($this->_logger) { + $object->setLogger($this->_logger); + } + + $object->setConfig($this->_config); + $pf = $object->parse($data, $file, $archive); + if (PEAR::isError($pf)) { + return $pf; + } + + if ($this->_rawReturn) { + return $pf; + } + + if (!$pf->validate($state)) {; + if ($this->_config->get('verbose') > 0 + && $this->_logger && $pf->getValidationWarnings(false) + ) { + foreach ($pf->getValidationWarnings(false) as $warning) { + $this->_logger->log(0, 'ERROR: ' . $warning['message']); + } + } + + $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', + 2, null, null, $pf->getValidationWarnings()); + return $a; + } + + if ($this->_logger && $pf->getValidationWarnings(false)) { + foreach ($pf->getValidationWarnings() as $warning) { + $this->_logger->log(0, 'WARNING: ' . $warning['message']); + } + } + + if (method_exists($pf, 'flattenFilelist')) { + $pf->flattenFilelist(); // for v2 + } + + return $pf; + } elseif (preg_match('/]+version="([^"]+)"/', $data, $packageversion)) { + $a = PEAR::raiseError('package.xml file "' . $file . + '" has unsupported package.xml version "' . $packageversion[1] . '"'); + return $a; + } else { + if (!class_exists('PEAR_ErrorStack')) { + require_once 'PEAR/ErrorStack.php'; + } + + PEAR_ErrorStack::staticPush('PEAR_PackageFile', + PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION, + 'warning', array('xml' => $data), 'package.xml "' . $file . + '" has no package.xml version'); + $object = &$this->parserFactory('1.0'); + $object->setConfig($this->_config); + $pf = $object->parse($data, $file, $archive); + if (PEAR::isError($pf)) { + return $pf; + } + + if ($this->_rawReturn) { + return $pf; + } + + if (!$pf->validate($state)) { + $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', + 2, null, null, $pf->getValidationWarnings()); + return $a; + } + + if ($this->_logger && $pf->getValidationWarnings(false)) { + foreach ($pf->getValidationWarnings() as $warning) { + $this->_logger->log(0, 'WARNING: ' . $warning['message']); + } + } + + if (method_exists($pf, 'flattenFilelist')) { + $pf->flattenFilelist(); // for v2 + } + + return $pf; + } + } + + /** + * Register a temporary file or directory. When the destructor is + * executed, all registered temporary files and directories are + * removed. + * + * @param string $file name of file or directory + * @return void + */ + function addTempFile($file) + { + $GLOBALS['_PEAR_Common_tempfiles'][] = $file; + } + + /** + * Create a PEAR_PackageFile_v* from a compresed Tar or Tgz file. + * @access public + * @param string contents of package.xml file + * @param int package state (one of PEAR_VALIDATE_* constants) + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @using Archive_Tar to extract the files + * @using fromPackageFile() to load the package after the package.xml + * file is extracted. + */ + function &fromTgzFile($file, $state) + { + if (!class_exists('Archive_Tar')) { + require_once 'Archive/Tar.php'; + } + + $tar = new Archive_Tar($file); + if ($this->_debug <= 1) { + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + } + + $content = $tar->listContent(); + if ($this->_debug <= 1) { + $tar->popErrorHandling(); + } + + if (!is_array($content)) { + if (is_string($file) && strlen($file < 255) && + (!file_exists($file) || !@is_file($file))) { + $ret = PEAR::raiseError("could not open file \"$file\""); + return $ret; + } + + $file = realpath($file); + $ret = PEAR::raiseError("Could not get contents of package \"$file\"". + '. Invalid tgz file.'); + return $ret; + } + + if (!count($content) && !@is_file($file)) { + $ret = PEAR::raiseError("could not open file \"$file\""); + return $ret; + } + + $xml = null; + $origfile = $file; + foreach ($content as $file) { + $name = $file['filename']; + if ($name == 'package2.xml') { // allow a .tgz to distribute both versions + $xml = $name; + break; + } + + if ($name == 'package.xml') { + $xml = $name; + break; + } elseif (preg_match('/package.xml$/', $name, $match)) { + $xml = $name; + break; + } + } + + if ($this->_tmpdir) { + $tmpdir = $this->_tmpdir; + } else { + $tmpdir = System::mkTemp(array('-t', $this->_config->get('temp_dir'), '-d', 'pear')); + if ($tmpdir === false) { + $ret = PEAR::raiseError("there was a problem with getting the configured temp directory"); + return $ret; + } + + PEAR_PackageFile::addTempFile($tmpdir); + } + + $this->_extractErrors(); + PEAR::staticPushErrorHandling(PEAR_ERROR_CALLBACK, array($this, '_extractErrors')); + if (!$xml || !$tar->extractList(array($xml), $tmpdir)) { + $extra = implode("\n", $this->_extractErrors()); + if ($extra) { + $extra = ' ' . $extra; + } + + PEAR::staticPopErrorHandling(); + $ret = PEAR::raiseError('could not extract the package.xml file from "' . + $origfile . '"' . $extra); + return $ret; + } + + PEAR::staticPopErrorHandling(); + $ret = &PEAR_PackageFile::fromPackageFile("$tmpdir/$xml", $state, $origfile); + return $ret; + } + + /** + * helper for extracting Archive_Tar errors + * @var array + * @access private + */ + var $_extractErrors = array(); + + /** + * helper callback for extracting Archive_Tar errors + * + * @param PEAR_Error|null $err + * @return array + * @access private + */ + function _extractErrors($err = null) + { + static $errors = array(); + if ($err === null) { + $e = $errors; + $errors = array(); + return $e; + } + $errors[] = $err->getMessage(); + } + + /** + * Create a PEAR_PackageFile_v* from a package.xml file. + * + * @access public + * @param string $descfile name of package xml file + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @param string|false $archive name of the archive this package.xml came + * from, if any + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses PEAR_PackageFile::fromXmlString to create the oject after the + * XML is loaded from the package.xml file. + */ + function &fromPackageFile($descfile, $state, $archive = false) + { + $fp = false; + if (is_string($descfile) && strlen($descfile) < 255 && + ( + !file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) + || (!$fp = @fopen($descfile, 'r')) + ) + ) { + $a = PEAR::raiseError("Unable to open $descfile"); + return $a; + } + + // read the whole thing so we only get one cdata callback + // for each block of cdata + fclose($fp); + $data = file_get_contents($descfile); + $ret = &PEAR_PackageFile::fromXmlString($data, $state, $descfile, $archive); + return $ret; + } + + + /** + * Create a PEAR_PackageFile_v* from a .tgz archive or package.xml file. + * + * This method is able to extract information about a package from a .tgz + * archive or from a XML package definition file. + * + * @access public + * @param string $info file name + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses fromPackageFile() if the file appears to be XML + * @uses fromTgzFile() to load all non-XML files + */ + function &fromAnyFile($info, $state) + { + if (is_dir($info)) { + $dir_name = realpath($info); + if (file_exists($dir_name . '/package.xml')) { + $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package.xml', $state); + } elseif (file_exists($dir_name . '/package2.xml')) { + $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package2.xml', $state); + } else { + $info = PEAR::raiseError("No package definition found in '$info' directory"); + } + + return $info; + } + + $fp = false; + if (is_string($info) && strlen($info) < 255 && + (file_exists($info) || ($fp = @fopen($info, 'r'))) + ) { + + if ($fp) { + fclose($fp); + } + + $tmp = substr($info, -4); + if ($tmp == '.xml') { + $info = &PEAR_PackageFile::fromPackageFile($info, $state); + } elseif ($tmp == '.tar' || $tmp == '.tgz') { + $info = &PEAR_PackageFile::fromTgzFile($info, $state); + } else { + $fp = fopen($info, 'r'); + $test = fread($fp, 5); + fclose($fp); + if ($test == ' + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Packager.php 286809 2009-08-04 15:10:26Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Common.php'; +require_once 'PEAR/PackageFile.php'; +require_once 'System.php'; + +/** + * Administration class used to make a PEAR release tarball. + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Packager extends PEAR_Common +{ + /** + * @var PEAR_Registry + */ + var $_registry; + + function package($pkgfile = null, $compress = true, $pkg2 = null) + { + // {{{ validate supplied package.xml file + if (empty($pkgfile)) { + $pkgfile = 'package.xml'; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pkg = &new PEAR_PackageFile($this->config, $this->debug); + $pf = &$pkg->fromPackageFile($pkgfile, PEAR_VALIDATE_NORMAL); + $main = &$pf; + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + if (is_array($pf->getUserInfo())) { + foreach ($pf->getUserInfo() as $error) { + $this->log(0, 'Error: ' . $error['message']); + } + } + + $this->log(0, $pf->getMessage()); + return $this->raiseError("Cannot package, errors in package file"); + } + + foreach ($pf->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + // }}} + if ($pkg2) { + $this->log(0, 'Attempting to process the second package file'); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pf2 = &$pkg->fromPackageFile($pkg2, PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf2)) { + if (is_array($pf2->getUserInfo())) { + foreach ($pf2->getUserInfo() as $error) { + $this->log(0, 'Error: ' . $error['message']); + } + } + $this->log(0, $pf2->getMessage()); + return $this->raiseError("Cannot package, errors in second package file"); + } + + foreach ($pf2->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + if ($pf2->getPackagexmlVersion() == '2.0' || + $pf2->getPackagexmlVersion() == '2.1' + ) { + $main = &$pf2; + $other = &$pf; + } else { + $main = &$pf; + $other = &$pf2; + } + + if ($main->getPackagexmlVersion() != '2.0' && + $main->getPackagexmlVersion() != '2.1') { + return PEAR::raiseError('Error: cannot package two package.xml version 1.0, can ' . + 'only package together a package.xml 1.0 and package.xml 2.0'); + } + + if ($other->getPackagexmlVersion() != '1.0') { + return PEAR::raiseError('Error: cannot package two package.xml version 2.0, can ' . + 'only package together a package.xml 1.0 and package.xml 2.0'); + } + } + + $main->setLogger($this); + if (!$main->validate(PEAR_VALIDATE_PACKAGING)) { + foreach ($main->getValidationWarnings() as $warning) { + $this->log(0, 'Error: ' . $warning['message']); + } + return $this->raiseError("Cannot package, errors in package"); + } + + foreach ($main->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + if ($pkg2) { + $other->setLogger($this); + $a = false; + if (!$other->validate(PEAR_VALIDATE_NORMAL) || $a = !$main->isEquivalent($other)) { + foreach ($other->getValidationWarnings() as $warning) { + $this->log(0, 'Error: ' . $warning['message']); + } + + foreach ($main->getValidationWarnings() as $warning) { + $this->log(0, 'Error: ' . $warning['message']); + } + + if ($a) { + return $this->raiseError('The two package.xml files are not equivalent!'); + } + + return $this->raiseError("Cannot package, errors in package"); + } + + foreach ($other->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + $gen = &$main->getDefaultGenerator(); + $tgzfile = $gen->toTgz2($this, $other, $compress); + if (PEAR::isError($tgzfile)) { + return $tgzfile; + } + + $dest_package = basename($tgzfile); + $pkgdir = dirname($pkgfile); + + // TAR the Package ------------------------------------------- + $this->log(1, "Package $dest_package done"); + if (file_exists("$pkgdir/CVS/Root")) { + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pf->getVersion()); + $cvstag = "RELEASE_$cvsversion"; + $this->log(1, 'Tag the released code with "pear cvstag ' . + $main->getPackageFile() . '"'); + $this->log(1, "(or set the CVS tag $cvstag by hand)"); + } elseif (file_exists("$pkgdir/.svn")) { + $svnversion = preg_replace('/[^a-z0-9]/i', '.', $pf->getVersion()); + $svntag = $pf->getName() . "-$svnversion"; + $this->log(1, 'Tag the released code with "pear svntag ' . + $main->getPackageFile() . '"'); + $this->log(1, "(or set the SVN tag $svntag by hand)"); + } + } else { // this branch is executed for single packagefile packaging + $gen = &$pf->getDefaultGenerator(); + $tgzfile = $gen->toTgz($this, $compress); + if (PEAR::isError($tgzfile)) { + $this->log(0, $tgzfile->getMessage()); + return $this->raiseError("Cannot package, errors in package"); + } + + $dest_package = basename($tgzfile); + $pkgdir = dirname($pkgfile); + + // TAR the Package ------------------------------------------- + $this->log(1, "Package $dest_package done"); + if (file_exists("$pkgdir/CVS/Root")) { + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pf->getVersion()); + $cvstag = "RELEASE_$cvsversion"; + $this->log(1, "Tag the released code with `pear cvstag $pkgfile'"); + $this->log(1, "(or set the CVS tag $cvstag by hand)"); + } elseif (file_exists("$pkgdir/.svn")) { + $svnversion = preg_replace('/[^a-z0-9]/i', '.', $pf->getVersion()); + $svntag = $pf->getName() . "-$svnversion"; + $this->log(1, "Tag the released code with `pear svntag $pkgfile'"); + $this->log(1, "(or set the SVN tag $svntag by hand)"); + } + } + + return $dest_package; + } +}PEAR-1.9.0/PEAR/Registry.php100664 764 764 224124 100664 10423 + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Registry.php 287555 2009-08-21 21:27:27Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * for PEAR_Error + */ +require_once 'PEAR.php'; +require_once 'PEAR/DependencyDB.php'; + +define('PEAR_REGISTRY_ERROR_LOCK', -2); +define('PEAR_REGISTRY_ERROR_FORMAT', -3); +define('PEAR_REGISTRY_ERROR_FILE', -4); +define('PEAR_REGISTRY_ERROR_CONFLICT', -5); +define('PEAR_REGISTRY_ERROR_CHANNEL_FILE', -6); + +/** + * Administration class used to maintain the installed package database. + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Registry extends PEAR +{ + /** + * File containing all channel information. + * @var string + */ + var $channels = ''; + + /** Directory where registry files are stored. + * @var string + */ + var $statedir = ''; + + /** File where the file map is stored + * @var string + */ + var $filemap = ''; + + /** Directory where registry files for channels are stored. + * @var string + */ + var $channelsdir = ''; + + /** Name of file used for locking the registry + * @var string + */ + var $lockfile = ''; + + /** File descriptor used during locking + * @var resource + */ + var $lock_fp = null; + + /** Mode used during locking + * @var int + */ + var $lock_mode = 0; // XXX UNUSED + + /** Cache of package information. Structure: + * array( + * 'package' => array('id' => ... ), + * ... ) + * @var array + */ + var $pkginfo_cache = array(); + + /** Cache of file map. Structure: + * array( '/path/to/file' => 'package', ... ) + * @var array + */ + var $filemap_cache = array(); + + /** + * @var false|PEAR_ChannelFile + */ + var $_pearChannel; + + /** + * @var false|PEAR_ChannelFile + */ + var $_peclChannel; + + /** + * @var false|PEAR_ChannelFile + */ + var $_docChannel; + + /** + * @var PEAR_DependencyDB + */ + var $_dependencyDB; + + /** + * @var PEAR_Config + */ + var $_config; + + /** + * PEAR_Registry constructor. + * + * @param string (optional) PEAR install directory (for .php files) + * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PEAR channel, if + * default values are not desired. Only used the very first time a PEAR + * repository is initialized + * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PECL channel, if + * default values are not desired. Only used the very first time a PEAR + * repository is initialized + * + * @access public + */ + function PEAR_Registry($pear_install_dir = PEAR_INSTALL_DIR, $pear_channel = false, + $pecl_channel = false) + { + parent::PEAR(); + $this->setInstallDir($pear_install_dir); + $this->_pearChannel = $pear_channel; + $this->_peclChannel = $pecl_channel; + $this->_config = false; + } + + function setInstallDir($pear_install_dir = PEAR_INSTALL_DIR) + { + $ds = DIRECTORY_SEPARATOR; + $this->install_dir = $pear_install_dir; + $this->channelsdir = $pear_install_dir.$ds.'.channels'; + $this->statedir = $pear_install_dir.$ds.'.registry'; + $this->filemap = $pear_install_dir.$ds.'.filemap'; + $this->lockfile = $pear_install_dir.$ds.'.lock'; + } + + function hasWriteAccess() + { + if (!file_exists($this->install_dir)) { + $dir = $this->install_dir; + while ($dir && $dir != '.') { + $olddir = $dir; + $dir = dirname($dir); + if ($dir != '.' && file_exists($dir)) { + if (is_writeable($dir)) { + return true; + } + + return false; + } + + if ($dir == $olddir) { // this can happen in safe mode + return @is_writable($dir); + } + } + + return false; + } + + return is_writeable($this->install_dir); + } + + function setConfig(&$config, $resetInstallDir = true) + { + $this->_config = &$config; + if ($resetInstallDir) { + $this->setInstallDir($config->get('php_dir')); + } + } + + function _initializeChannelDirs() + { + static $running = false; + if (!$running) { + $running = true; + $ds = DIRECTORY_SEPARATOR; + if (!is_dir($this->channelsdir) || + !file_exists($this->channelsdir . $ds . 'pear.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') || + !file_exists($this->channelsdir . $ds . '__uri.reg')) { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $pear_channel = $this->_pearChannel; + if (!is_a($pear_channel, 'PEAR_ChannelFile') || !$pear_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setAlias('pear'); + $pear_channel->setServer('pear.php.net'); + $pear_channel->setSummary('PHP Extension and Application Repository'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/'); + //$pear_channel->setBaseURL('REST1.4', 'http://pear.php.net/rest/'); + } else { + $pear_channel->setServer('pear.php.net'); + $pear_channel->setAlias('pear'); + } + + $pear_channel->validate(); + $this->_addChannel($pear_channel); + } + + if (!file_exists($this->channelsdir . $ds . 'pecl.php.net.reg')) { + $pecl_channel = $this->_peclChannel; + if (!is_a($pecl_channel, 'PEAR_ChannelFile') || !$pecl_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $pecl_channel = new PEAR_ChannelFile; + $pecl_channel->setAlias('pecl'); + $pecl_channel->setServer('pecl.php.net'); + $pecl_channel->setSummary('PHP Extension Community Library'); + $pecl_channel->setDefaultPEARProtocols(); + $pecl_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/'); + $pecl_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/'); + $pecl_channel->setValidationPackage('PEAR_Validator_PECL', '1.0'); + } else { + $pecl_channel->setServer('pecl.php.net'); + $pecl_channel->setAlias('pecl'); + } + + $pecl_channel->validate(); + $this->_addChannel($pecl_channel); + } + + if (!file_exists($this->channelsdir . $ds . 'doc.php.net.reg')) { + $doc_channel = $this->_docChannel; + if (!is_a($doc_channel, 'PEAR_ChannelFile') || !$doc_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $doc_channel = new PEAR_ChannelFile; + $doc_channel->setAlias('phpdocs'); + $doc_channel->setServer('doc.php.net'); + $doc_channel->setSummary('PHP Documentation Team'); + $doc_channel->setDefaultPEARProtocols(); + $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/'); + } else { + $doc_channel->setServer('doc.php.net'); + $doc_channel->setAlias('doc'); + } + + $doc_channel->validate(); + $this->_addChannel($doc_channel); + } + + if (!file_exists($this->channelsdir . $ds . '__uri.reg')) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $private = new PEAR_ChannelFile; + $private->setName('__uri'); + $private->setDefaultPEARProtocols(); + $private->setBaseURL('REST1.0', '****'); + $private->setSummary('Pseudo-channel for static packages'); + $this->_addChannel($private); + } + $this->_rebuildFileMap(); + } + + $running = false; + } + } + + function _initializeDirs() + { + $ds = DIRECTORY_SEPARATOR; + // XXX Compatibility code should be removed in the future + // rename all registry files if any to lowercase + if (!OS_WINDOWS && file_exists($this->statedir) && is_dir($this->statedir) && + $handle = opendir($this->statedir)) { + $dest = $this->statedir . $ds; + while (false !== ($file = readdir($handle))) { + if (preg_match('/^.*[A-Z].*\.reg\\z/', $file)) { + rename($dest . $file, $dest . strtolower($file)); + } + } + closedir($handle); + } + + $this->_initializeChannelDirs(); + if (!file_exists($this->filemap)) { + $this->_rebuildFileMap(); + } + $this->_initializeDepDB(); + } + + function _initializeDepDB() + { + if (!isset($this->_dependencyDB)) { + static $initializing = false; + if (!$initializing) { + $initializing = true; + if (!$this->_config) { // never used? + $file = OS_WINDOWS ? 'pear.ini' : '.pearrc'; + $this->_config = &new PEAR_Config($this->statedir . DIRECTORY_SEPARATOR . + $file); + $this->_config->setRegistry($this); + $this->_config->set('php_dir', $this->install_dir); + } + + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config); + if (PEAR::isError($this->_dependencyDB)) { + // attempt to recover by removing the dep db + if (file_exists($this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb')) { + @unlink($this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb'); + } + + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config); + if (PEAR::isError($this->_dependencyDB)) { + echo $this->_dependencyDB->getMessage(); + echo 'Unrecoverable error'; + exit(1); + } + } + + $initializing = false; + } + } + } + + /** + * PEAR_Registry destructor. Makes sure no locks are forgotten. + * + * @access private + */ + function _PEAR_Registry() + { + parent::_PEAR(); + if (is_resource($this->lock_fp)) { + $this->_unlock(); + } + } + + /** + * Make sure the directory where we keep registry files exists. + * + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertStateDir($channel = false) + { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_assertChannelStateDir($channel); + } + + static $init = false; + if (!file_exists($this->statedir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $this->statedir))) { + return $this->raiseError("could not create directory '{$this->statedir}'"); + } + $init = true; + } elseif (!is_dir($this->statedir)) { + return $this->raiseError('Cannot create directory ' . $this->statedir . ', ' . + 'it already exists and is not a directory'); + } + + $ds = DIRECTORY_SEPARATOR; + if (!file_exists($this->channelsdir)) { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') || + !file_exists($this->channelsdir . $ds . '__uri.reg')) { + $init = true; + } + } elseif (!is_dir($this->channelsdir)) { + return $this->raiseError('Cannot create directory ' . $this->channelsdir . ', ' . + 'it already exists and is not a directory'); + } + + if ($init) { + static $running = false; + if (!$running) { + $running = true; + $this->_initializeDirs(); + $running = false; + $init = false; + } + } else { + $this->_initializeDepDB(); + } + + return true; + } + + /** + * Make sure the directory where we keep registry files exists for a non-standard channel. + * + * @param string channel name + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertChannelStateDir($channel) + { + $ds = DIRECTORY_SEPARATOR; + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $this->_initializeChannelDirs(); + } + return $this->_assertStateDir($channel); + } + + $channelDir = $this->_channelDirectoryName($channel); + if (!is_dir($this->channelsdir) || + !file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $this->_initializeChannelDirs(); + } + + if (!file_exists($channelDir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $channelDir))) { + return $this->raiseError("could not create directory '" . $channelDir . + "'"); + } + } elseif (!is_dir($channelDir)) { + return $this->raiseError("could not create directory '" . $channelDir . + "', already exists and is not a directory"); + } + + return true; + } + + /** + * Make sure the directory where we keep registry files for channels exists + * + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertChannelDir() + { + if (!file_exists($this->channelsdir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $this->channelsdir))) { + return $this->raiseError("could not create directory '{$this->channelsdir}'"); + } + } elseif (!is_dir($this->channelsdir)) { + return $this->raiseError("could not create directory '{$this->channelsdir}" . + "', it already exists and is not a directory"); + } + + if (!file_exists($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $this->channelsdir . DIRECTORY_SEPARATOR . '.alias'))) { + return $this->raiseError("could not create directory '{$this->channelsdir}/.alias'"); + } + } elseif (!is_dir($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) { + return $this->raiseError("could not create directory '{$this->channelsdir}" . + "/.alias', it already exists and is not a directory"); + } + + return true; + } + + /** + * Get the name of the file where data for a given package is stored. + * + * @param string channel name, or false if this is a PEAR package + * @param string package name + * + * @return string registry file name + * + * @access public + */ + function _packageFileName($package, $channel = false) + { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_channelDirectoryName($channel) . DIRECTORY_SEPARATOR . + strtolower($package) . '.reg'; + } + + return $this->statedir . DIRECTORY_SEPARATOR . strtolower($package) . '.reg'; + } + + /** + * Get the name of the file where data for a given channel is stored. + * @param string channel name + * @return string registry file name + */ + function _channelFileName($channel, $noaliases = false) + { + if (!$noaliases) { + if (file_exists($this->_getChannelAliasFileName($channel))) { + $channel = implode('', file($this->_getChannelAliasFileName($channel))); + } + } + return $this->channelsdir . DIRECTORY_SEPARATOR . str_replace('/', '_', + strtolower($channel)) . '.reg'; + } + + /** + * @param string + * @return string + */ + function _getChannelAliasFileName($alias) + { + return $this->channelsdir . DIRECTORY_SEPARATOR . '.alias' . + DIRECTORY_SEPARATOR . str_replace('/', '_', strtolower($alias)) . '.txt'; + } + + /** + * Get the name of a channel from its alias + */ + function _getChannelFromAlias($channel) + { + if (!$this->_channelExists($channel)) { + if ($channel == 'pear.php.net') { + return 'pear.php.net'; + } + + if ($channel == 'pecl.php.net') { + return 'pecl.php.net'; + } + + if ($channel == 'doc.php.net') { + return 'doc.php.net'; + } + + if ($channel == '__uri') { + return '__uri'; + } + + return false; + } + + $channel = strtolower($channel); + if (file_exists($this->_getChannelAliasFileName($channel))) { + // translate an alias to an actual channel + return implode('', file($this->_getChannelAliasFileName($channel))); + } + + return $channel; + } + + /** + * Get the alias of a channel from its alias or its name + */ + function _getAlias($channel) + { + if (!$this->_channelExists($channel)) { + if ($channel == 'pear.php.net') { + return 'pear'; + } + + if ($channel == 'pecl.php.net') { + return 'pecl'; + } + + if ($channel == 'doc.php.net') { + return 'phpdocs'; + } + + return false; + } + + $channel = $this->_getChannel($channel); + if (PEAR::isError($channel)) { + return $channel; + } + + return $channel->getAlias(); + } + + /** + * Get the name of the file where data for a given package is stored. + * + * @param string channel name, or false if this is a PEAR package + * @param string package name + * + * @return string registry file name + * + * @access public + */ + function _channelDirectoryName($channel) + { + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + return $this->statedir; + } + + $ch = $this->_getChannelFromAlias($channel); + if (!$ch) { + $ch = $channel; + } + + return $this->statedir . DIRECTORY_SEPARATOR . strtolower('.channel.' . + str_replace('/', '_', $ch)); + } + + function _openPackageFile($package, $mode, $channel = false) + { + if (!$this->_assertStateDir($channel)) { + return null; + } + + if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) { + return null; + } + + $file = $this->_packageFileName($package, $channel); + if (!file_exists($file) && $mode == 'r' || $mode == 'rb') { + return null; + } + + $fp = @fopen($file, $mode); + if (!$fp) { + return null; + } + + return $fp; + } + + function _closePackageFile($fp) + { + fclose($fp); + } + + function _openChannelFile($channel, $mode) + { + if (!$this->_assertChannelDir()) { + return null; + } + + if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) { + return null; + } + + $file = $this->_channelFileName($channel); + if (!file_exists($file) && $mode == 'r' || $mode == 'rb') { + return null; + } + + $fp = @fopen($file, $mode); + if (!$fp) { + return null; + } + + return $fp; + } + + function _closeChannelFile($fp) + { + fclose($fp); + } + + function _rebuildFileMap() + { + if (!class_exists('PEAR_Installer_Role')) { + require_once 'PEAR/Installer/Role.php'; + } + + $channels = $this->_listAllPackages(); + $files = array(); + foreach ($channels as $channel => $packages) { + foreach ($packages as $package) { + $version = $this->_packageInfo($package, 'version', $channel); + $filelist = $this->_packageInfo($package, 'filelist', $channel); + if (!is_array($filelist)) { + continue; + } + + foreach ($filelist as $name => $attrs) { + if (isset($attrs['attribs'])) { + $attrs = $attrs['attribs']; + } + + // it is possible for conflicting packages in different channels to + // conflict with data files/doc files + if ($name == 'dirtree') { + continue; + } + + if (isset($attrs['role']) && !in_array($attrs['role'], + PEAR_Installer_Role::getInstallableRoles())) { + // these are not installed + continue; + } + + if (isset($attrs['role']) && !in_array($attrs['role'], + PEAR_Installer_Role::getBaseinstallRoles())) { + $attrs['baseinstalldir'] = $package; + } + + if (isset($attrs['baseinstalldir'])) { + $file = $attrs['baseinstalldir'].DIRECTORY_SEPARATOR.$name; + } else { + $file = $name; + } + + $file = preg_replace(',^/+,', '', $file); + if ($channel != 'pear.php.net') { + if (!isset($files[$attrs['role']])) { + $files[$attrs['role']] = array(); + } + $files[$attrs['role']][$file] = array(strtolower($channel), + strtolower($package)); + } else { + if (!isset($files[$attrs['role']])) { + $files[$attrs['role']] = array(); + } + $files[$attrs['role']][$file] = strtolower($package); + } + } + } + } + + + $this->_assertStateDir(); + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->filemap, 'wb'); + if (!$fp) { + return false; + } + + $this->filemap_cache = $files; + fwrite($fp, serialize($files)); + fclose($fp); + return true; + } + + function _readFileMap() + { + if (!file_exists($this->filemap)) { + return array(); + } + + $fp = @fopen($this->filemap, 'r'); + if (!$fp) { + return $this->raiseError('PEAR_Registry: could not open filemap "' . $this->filemap . '"', PEAR_REGISTRY_ERROR_FILE, null, null, $php_errormsg); + } + + clearstatcache(); + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + $fsize = filesize($this->filemap); + fclose($fp); + $data = file_get_contents($this->filemap); + set_magic_quotes_runtime($rt); + $tmp = unserialize($data); + if (!$tmp && $fsize > 7) { + return $this->raiseError('PEAR_Registry: invalid filemap data', PEAR_REGISTRY_ERROR_FORMAT, null, null, $data); + } + + $this->filemap_cache = $tmp; + return true; + } + + /** + * Lock the registry. + * + * @param integer lock mode, one of LOCK_EX, LOCK_SH or LOCK_UN. + * See flock manual for more information. + * + * @return bool TRUE on success, FALSE if locking failed, or a + * PEAR error if some other error occurs (such as the + * lock file not being writable). + * + * @access private + */ + function _lock($mode = LOCK_EX) + { + if (stristr(php_uname(), 'Windows 9')) { + return true; + } + + if ($mode != LOCK_UN && is_resource($this->lock_fp)) { + // XXX does not check type of lock (LOCK_SH/LOCK_EX) + return true; + } + + if (!$this->_assertStateDir()) { + if ($mode == LOCK_EX) { + return $this->raiseError('Registry directory is not writeable by the current user'); + } + + return true; + } + + $open_mode = 'w'; + // XXX People reported problems with LOCK_SH and 'w' + if ($mode === LOCK_SH || $mode === LOCK_UN) { + if (!file_exists($this->lockfile)) { + touch($this->lockfile); + } + $open_mode = 'r'; + } + + if (!is_resource($this->lock_fp)) { + $this->lock_fp = @fopen($this->lockfile, $open_mode); + } + + if (!is_resource($this->lock_fp)) { + $this->lock_fp = null; + return $this->raiseError("could not create lock file" . + (isset($php_errormsg) ? ": " . $php_errormsg : "")); + } + + if (!(int)flock($this->lock_fp, $mode)) { + switch ($mode) { + case LOCK_SH: $str = 'shared'; break; + case LOCK_EX: $str = 'exclusive'; break; + case LOCK_UN: $str = 'unlock'; break; + default: $str = 'unknown'; break; + } + + //is resource at this point, close it on error. + fclose($this->lock_fp); + $this->lock_fp = null; + return $this->raiseError("could not acquire $str lock ($this->lockfile)", + PEAR_REGISTRY_ERROR_LOCK); + } + + return true; + } + + function _unlock() + { + $ret = $this->_lock(LOCK_UN); + if (is_resource($this->lock_fp)) { + fclose($this->lock_fp); + } + + $this->lock_fp = null; + return $ret; + } + + function _packageExists($package, $channel = false) + { + return file_exists($this->_packageFileName($package, $channel)); + } + + /** + * Determine whether a channel exists in the registry + * + * @param string Channel name + * @param bool if true, then aliases will be ignored + * @return boolean + */ + function _channelExists($channel, $noaliases = false) + { + $a = file_exists($this->_channelFileName($channel, $noaliases)); + if (!$a && $channel == 'pear.php.net') { + return true; + } + + if (!$a && $channel == 'pecl.php.net') { + return true; + } + + if (!$a && $channel == 'doc.php.net') { + return true; + } + + return $a; + } + + /** + * Determine whether a mirror exists within the deafult channel in the registry + * + * @param string Channel name + * @param string Mirror name + * + * @return boolean + */ + function _mirrorExists($channel, $mirror) + { + $data = $this->_channelInfo($channel); + if (!isset($data['servers']['mirror'])) { + return false; + } + + foreach ($data['servers']['mirror'] as $m) { + if ($m['attribs']['host'] == $mirror) { + return true; + } + } + + return false; + } + + /** + * @param PEAR_ChannelFile Channel object + * @param donotuse + * @param string Last-Modified HTTP tag from remote request + * @return boolean|PEAR_Error True on creation, false if it already exists + */ + function _addChannel($channel, $update = false, $lastmodified = false) + { + if (!is_a($channel, 'PEAR_ChannelFile')) { + return false; + } + + if (!$channel->validate()) { + return false; + } + + if (file_exists($this->_channelFileName($channel->getName()))) { + if (!$update) { + return false; + } + + $checker = $this->_getChannel($channel->getName()); + if (PEAR::isError($checker)) { + return $checker; + } + + if ($channel->getAlias() != $checker->getAlias()) { + if (file_exists($this->_getChannelAliasFileName($checker->getAlias()))) { + @unlink($this->_getChannelAliasFileName($checker->getAlias())); + } + } + } else { + if ($update && !in_array($channel->getName(), array('pear.php.net', 'pecl.php.net', 'doc.php.net'))) { + return false; + } + } + + $ret = $this->_assertChannelDir(); + if (PEAR::isError($ret)) { + return $ret; + } + + $ret = $this->_assertChannelStateDir($channel->getName()); + if (PEAR::isError($ret)) { + return $ret; + } + + if ($channel->getAlias() != $channel->getName()) { + if (file_exists($this->_getChannelAliasFileName($channel->getAlias())) && + $this->_getChannelFromAlias($channel->getAlias()) != $channel->getName()) { + $channel->setAlias($channel->getName()); + } + + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->_getChannelAliasFileName($channel->getAlias()), 'w'); + if (!$fp) { + return false; + } + + fwrite($fp, $channel->getName()); + fclose($fp); + } + + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->_channelFileName($channel->getName()), 'wb'); + if (!$fp) { + return false; + } + + $info = $channel->toArray(); + if ($lastmodified) { + $info['_lastmodified'] = $lastmodified; + } else { + $info['_lastmodified'] = date('r'); + } + + fwrite($fp, serialize($info)); + fclose($fp); + return true; + } + + /** + * Deletion fails if there are any packages installed from the channel + * @param string|PEAR_ChannelFile channel name + * @return boolean|PEAR_Error True on deletion, false if it doesn't exist + */ + function _deleteChannel($channel) + { + if (!is_string($channel)) { + if (!is_a($channel, 'PEAR_ChannelFile')) { + return false; + } + + if (!$channel->validate()) { + return false; + } + $channel = $channel->getName(); + } + + if ($this->_getChannelFromAlias($channel) == '__uri') { + return false; + } + + if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') { + return false; + } + + if ($this->_getChannelFromAlias($channel) == 'doc.php.net') { + return false; + } + + if (!$this->_channelExists($channel)) { + return false; + } + + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + return false; + } + + $channel = $this->_getChannelFromAlias($channel); + if ($channel == 'pear.php.net') { + return false; + } + + $test = $this->_listChannelPackages($channel); + if (count($test)) { + return false; + } + + $test = @rmdir($this->_channelDirectoryName($channel)); + if (!$test) { + return false; + } + + $file = $this->_getChannelAliasFileName($this->_getAlias($channel)); + if (file_exists($file)) { + $test = @unlink($file); + if (!$test) { + return false; + } + } + + $file = $this->_channelFileName($channel); + $ret = true; + if (file_exists($file)) { + $ret = @unlink($file); + } + + return $ret; + } + + /** + * Determine whether a channel exists in the registry + * @param string Channel Alias + * @return boolean + */ + function _isChannelAlias($alias) + { + return file_exists($this->_getChannelAliasFileName($alias)); + } + + /** + * @param string|null + * @param string|null + * @param string|null + * @return array|null + * @access private + */ + function _packageInfo($package = null, $key = null, $channel = 'pear.php.net') + { + if ($package === null) { + if ($channel === null) { + $channels = $this->_listChannels(); + $ret = array(); + foreach ($channels as $channel) { + $channel = strtolower($channel); + $ret[$channel] = array(); + $packages = $this->_listPackages($channel); + foreach ($packages as $package) { + $ret[$channel][] = $this->_packageInfo($package, null, $channel); + } + } + + return $ret; + } + + $ps = $this->_listPackages($channel); + if (!count($ps)) { + return array(); + } + return array_map(array(&$this, '_packageInfo'), + $ps, array_fill(0, count($ps), null), + array_fill(0, count($ps), $channel)); + } + + $fp = $this->_openPackageFile($package, 'r', $channel); + if ($fp === null) { + return null; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + $this->_closePackageFile($fp); + $data = file_get_contents($this->_packageFileName($package, $channel)); + set_magic_quotes_runtime($rt); + $data = unserialize($data); + if ($key === null) { + return $data; + } + + // compatibility for package.xml version 2.0 + if (isset($data['old'][$key])) { + return $data['old'][$key]; + } + + if (isset($data[$key])) { + return $data[$key]; + } + + return null; + } + + /** + * @param string Channel name + * @param bool whether to strictly retrieve info of channels, not just aliases + * @return array|null + */ + function _channelInfo($channel, $noaliases = false) + { + if (!$this->_channelExists($channel, $noaliases)) { + return null; + } + + $fp = $this->_openChannelFile($channel, 'r'); + if ($fp === null) { + return null; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + $this->_closeChannelFile($fp); + $data = file_get_contents($this->_channelFileName($channel)); + set_magic_quotes_runtime($rt); + $data = unserialize($data); + return $data; + } + + function _listChannels() + { + $channellist = array(); + if (!file_exists($this->channelsdir) || !is_dir($this->channelsdir)) { + return array('pear.php.net', 'pecl.php.net', 'doc.php.net', '__uri'); + } + + $dp = opendir($this->channelsdir); + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + + if ($ent == '__uri.reg') { + $channellist[] = '__uri'; + continue; + } + + $channellist[] = str_replace('_', '/', substr($ent, 0, -4)); + } + + closedir($dp); + if (!in_array('pear.php.net', $channellist)) { + $channellist[] = 'pear.php.net'; + } + + if (!in_array('pecl.php.net', $channellist)) { + $channellist[] = 'pecl.php.net'; + } + + if (!in_array('doc.php.net', $channellist)) { + $channellist[] = 'doc.php.net'; + } + + + if (!in_array('__uri', $channellist)) { + $channellist[] = '__uri'; + } + + natsort($channellist); + return $channellist; + } + + function _listPackages($channel = false) + { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_listChannelPackages($channel); + } + + if (!file_exists($this->statedir) || !is_dir($this->statedir)) { + return array(); + } + + $pkglist = array(); + $dp = opendir($this->statedir); + if (!$dp) { + return $pkglist; + } + + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + + $pkglist[] = substr($ent, 0, -4); + } + closedir($dp); + return $pkglist; + } + + function _listChannelPackages($channel) + { + $pkglist = array(); + if (!file_exists($this->_channelDirectoryName($channel)) || + !is_dir($this->_channelDirectoryName($channel))) { + return array(); + } + + $dp = opendir($this->_channelDirectoryName($channel)); + if (!$dp) { + return $pkglist; + } + + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + $pkglist[] = substr($ent, 0, -4); + } + + closedir($dp); + return $pkglist; + } + + function _listAllPackages() + { + $ret = array(); + foreach ($this->_listChannels() as $channel) { + $ret[$channel] = $this->_listPackages($channel); + } + + return $ret; + } + + /** + * Add an installed package to the registry + * @param string package name + * @param array package info (parsed by PEAR_Common::infoFrom*() methods) + * @return bool success of saving + * @access private + */ + function _addPackage($package, $info) + { + if ($this->_packageExists($package)) { + return false; + } + + $fp = $this->_openPackageFile($package, 'wb'); + if ($fp === null) { + return false; + } + + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + if (isset($info['filelist'])) { + $this->_rebuildFileMap(); + } + + return true; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + * @access private + */ + function _addPackage2($info) + { + if (!is_a($info, 'PEAR_PackageFile_v1') && !is_a($info, 'PEAR_PackageFile_v2')) { + return false; + } + + if (!$info->validate()) { + if (class_exists('PEAR_Common')) { + $ui = PEAR_Frontend::singleton(); + if ($ui) { + foreach ($info->getValidationWarnings() as $err) { + $ui->log($err['message'], true); + } + } + } + return false; + } + + $channel = $info->getChannel(); + $package = $info->getPackage(); + $save = $info; + if ($this->_packageExists($package, $channel)) { + return false; + } + + if (!$this->_channelExists($channel, true)) { + return false; + } + + $info = $info->toArray(true); + if (!$info) { + return false; + } + + $fp = $this->_openPackageFile($package, 'wb', $channel); + if ($fp === null) { + return false; + } + + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + $this->_rebuildFileMap(); + return true; + } + + /** + * @param string Package name + * @param array parsed package.xml 1.0 + * @param bool this parameter is only here for BC. Don't use it. + * @access private + */ + function _updatePackage($package, $info, $merge = true) + { + $oldinfo = $this->_packageInfo($package); + if (empty($oldinfo)) { + return false; + } + + $fp = $this->_openPackageFile($package, 'w'); + if ($fp === null) { + return false; + } + + if (is_object($info)) { + $info = $info->toArray(); + } + $info['_lastmodified'] = time(); + + $newinfo = $info; + if ($merge) { + $info = array_merge($oldinfo, $info); + } else { + $diff = $info; + } + + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + if (isset($newinfo['filelist'])) { + $this->_rebuildFileMap(); + } + + return true; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + * @access private + */ + function _updatePackage2($info) + { + if (!$this->_packageExists($info->getPackage(), $info->getChannel())) { + return false; + } + + $fp = $this->_openPackageFile($info->getPackage(), 'w', $info->getChannel()); + if ($fp === null) { + return false; + } + + $save = $info; + $info = $save->getArray(true); + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + $this->_rebuildFileMap(); + return true; + } + + /** + * @param string Package name + * @param string Channel name + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null + * @access private + */ + function &_getPackage($package, $channel = 'pear.php.net') + { + $info = $this->_packageInfo($package, null, $channel); + if ($info === null) { + return $info; + } + + $a = $this->_config; + if (!$a) { + $this->_config = &new PEAR_Config; + $this->_config->set('php_dir', $this->statedir); + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $pkg = &new PEAR_PackageFile($this->_config); + $pf = &$pkg->fromArray($info); + return $pf; + } + + /** + * @param string channel name + * @param bool whether to strictly retrieve channel names + * @return PEAR_ChannelFile|PEAR_Error + * @access private + */ + function &_getChannel($channel, $noaliases = false) + { + $ch = false; + if ($this->_channelExists($channel, $noaliases)) { + $chinfo = $this->_channelInfo($channel, $noaliases); + if ($chinfo) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $ch = &PEAR_ChannelFile::fromArrayWithErrors($chinfo); + } + } + + if ($ch) { + if ($ch->validate()) { + return $ch; + } + + foreach ($ch->getErrors(true) as $err) { + $message = $err['message'] . "\n"; + } + + $ch = PEAR::raiseError($message); + return $ch; + } + + if ($this->_getChannelFromAlias($channel) == 'pear.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setServer('pear.php.net'); + $pear_channel->setAlias('pear'); + $pear_channel->setSummary('PHP Extension and Application Repository'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/'); + return $pear_channel; + } + + if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setServer('pecl.php.net'); + $pear_channel->setAlias('pecl'); + $pear_channel->setSummary('PHP Extension Community Library'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/'); + $pear_channel->setValidationPackage('PEAR_Validator_PECL', '1.0'); + return $pear_channel; + } + + if ($this->_getChannelFromAlias($channel) == 'doc.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $doc_channel = new PEAR_ChannelFile; + $doc_channel->setServer('doc.php.net'); + $doc_channel->setAlias('phpdocs'); + $doc_channel->setSummary('PHP Documentation Team'); + $doc_channel->setDefaultPEARProtocols(); + $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/'); + return $doc_channel; + } + + + if ($this->_getChannelFromAlias($channel) == '__uri') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $private = new PEAR_ChannelFile; + $private->setName('__uri'); + $private->setDefaultPEARProtocols(); + $private->setBaseURL('REST1.0', '****'); + $private->setSummary('Pseudo-channel for static packages'); + return $private; + } + + return $ch; + } + + /** + * @param string Package name + * @param string Channel name + * @return bool + */ + function packageExists($package, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_packageExists($package, $channel); + $this->_unlock(); + return $ret; + } + + // }}} + + // {{{ channelExists() + + /** + * @param string channel name + * @param bool if true, then aliases will be ignored + * @return bool + */ + function channelExists($channel, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_channelExists($channel, $noaliases); + $this->_unlock(); + return $ret; + } + + // }}} + + /** + * @param string channel name mirror is in + * @param string mirror name + * + * @return bool + */ + function mirrorExists($channel, $mirror) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + + $ret = $this->_mirrorExists($channel, $mirror); + $this->_unlock(); + return $ret; + } + + // {{{ isAlias() + + /** + * Determines whether the parameter is an alias of a channel + * @param string + * @return bool + */ + function isAlias($alias) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_isChannelAlias($alias); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ packageInfo() + + /** + * @param string|null + * @param string|null + * @param string + * @return array|null + */ + function packageInfo($package = null, $key = null, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_packageInfo($package, $key, $channel); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ channelInfo() + + /** + * Retrieve a raw array of channel data. + * + * Do not use this, instead use {@link getChannel()} for normal + * operations. Array structure is undefined in this method + * @param string channel name + * @param bool whether to strictly retrieve information only on non-aliases + * @return array|null|PEAR_Error + */ + function channelInfo($channel = null, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_channelInfo($channel, $noaliases); + $this->_unlock(); + return $ret; + } + + // }}} + + /** + * @param string + */ + function channelName($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_getChannelFromAlias($channel); + $this->_unlock(); + return $ret; + } + + /** + * @param string + */ + function channelAlias($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_getAlias($channel); + $this->_unlock(); + return $ret; + } + // {{{ listPackages() + + function listPackages($channel = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listPackages($channel); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ listAllPackages() + + function listAllPackages() + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listAllPackages(); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ listChannel() + + function listChannels() + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listChannels(); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ addPackage() + + /** + * Add an installed package to the registry + * @param string|PEAR_PackageFile_v1|PEAR_PackageFile_v2 package name or object + * that will be passed to {@link addPackage2()} + * @param array package info (parsed by PEAR_Common::infoFrom*() methods) + * @return bool success of saving + */ + function addPackage($package, $info) + { + if (is_object($info)) { + return $this->addPackage2($info); + } + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + $ret = $this->_addPackage($package, $info); + $this->_unlock(); + if ($ret) { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + $pf->fromArray($info); + $this->_dependencyDB->uninstallPackage($pf); + $this->_dependencyDB->installPackage($pf); + } + return $ret; + } + + // }}} + // {{{ addPackage2() + + function addPackage2($info) + { + if (!is_object($info)) { + return $this->addPackage($info['package'], $info); + } + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + $ret = $this->_addPackage2($info); + $this->_unlock(); + if ($ret) { + $this->_dependencyDB->uninstallPackage($info); + $this->_dependencyDB->installPackage($info); + } + return $ret; + } + + // }}} + // {{{ updateChannel() + + /** + * For future expandibility purposes, separate this + * @param PEAR_ChannelFile + */ + function updateChannel($channel, $lastmodified = null) + { + if ($channel->getName() == '__uri') { + return false; + } + return $this->addChannel($channel, $lastmodified, true); + } + + // }}} + // {{{ deleteChannel() + + /** + * Deletion fails if there are any packages installed from the channel + * @param string|PEAR_ChannelFile channel name + * @return boolean|PEAR_Error True on deletion, false if it doesn't exist + */ + function deleteChannel($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_deleteChannel($channel); + $this->_unlock(); + if ($ret && is_a($this->_config, 'PEAR_Config')) { + $this->_config->setChannels($this->listChannels()); + } + + return $ret; + } + + // }}} + // {{{ addChannel() + + /** + * @param PEAR_ChannelFile Channel object + * @param string Last-Modified header from HTTP for caching + * @return boolean|PEAR_Error True on creation, false if it already exists + */ + function addChannel($channel, $lastmodified = false, $update = false) + { + if (!is_a($channel, 'PEAR_ChannelFile') || !$channel->validate()) { + return false; + } + + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_addChannel($channel, $update, $lastmodified); + $this->_unlock(); + if (!$update && $ret && is_a($this->_config, 'PEAR_Config')) { + $this->_config->setChannels($this->listChannels()); + } + + return $ret; + } + + // }}} + // {{{ deletePackage() + + function deletePackage($package, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $file = $this->_packageFileName($package, $channel); + $ret = file_exists($file) ? @unlink($file) : false; + $this->_rebuildFileMap(); + $this->_unlock(); + $p = array('channel' => $channel, 'package' => $package); + $this->_dependencyDB->uninstallPackage($p); + return $ret; + } + + // }}} + // {{{ updatePackage() + + function updatePackage($package, $info, $merge = true) + { + if (is_object($info)) { + return $this->updatePackage2($info, $merge); + } + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + $ret = $this->_updatePackage($package, $info, $merge); + $this->_unlock(); + if ($ret) { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + $pf->fromArray($this->packageInfo($package)); + $this->_dependencyDB->uninstallPackage($pf); + $this->_dependencyDB->installPackage($pf); + } + return $ret; + } + + // }}} + // {{{ updatePackage2() + + function updatePackage2($info) + { + + if (!is_object($info)) { + return $this->updatePackage($info['package'], $info, $merge); + } + + if (!$info->validate(PEAR_VALIDATE_DOWNLOADING)) { + return false; + } + + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_updatePackage2($info); + $this->_unlock(); + if ($ret) { + $this->_dependencyDB->uninstallPackage($info); + $this->_dependencyDB->installPackage($info); + } + + return $ret; + } + + // }}} + // {{{ getChannel() + /** + * @param string channel name + * @param bool whether to strictly return raw channels (no aliases) + * @return PEAR_ChannelFile|PEAR_Error + */ + function &getChannel($channel, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = &$this->_getChannel($channel, $noaliases); + $this->_unlock(); + if (!$ret) { + return PEAR::raiseError('Unknown channel: ' . $channel); + } + return $ret; + } + + // }}} + // {{{ getPackage() + /** + * @param string package name + * @param string channel name + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null + */ + function &getPackage($package, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $pf = &$this->_getPackage($package, $channel); + $this->_unlock(); + return $pf; + } + + // }}} + + /** + * Get PEAR_PackageFile_v[1/2] objects representing the contents of + * a dependency group that are installed. + * + * This is used at uninstall-time + * @param array + * @return array|false + */ + function getInstalledGroup($group) + { + $ret = array(); + if (isset($group['package'])) { + if (!isset($group['package'][0])) { + $group['package'] = array($group['package']); + } + foreach ($group['package'] as $package) { + $depchannel = isset($package['channel']) ? $package['channel'] : '__uri'; + $p = &$this->getPackage($package['name'], $depchannel); + if ($p) { + $save = &$p; + $ret[] = &$save; + } + } + } + if (isset($group['subpackage'])) { + if (!isset($group['subpackage'][0])) { + $group['subpackage'] = array($group['subpackage']); + } + foreach ($group['subpackage'] as $package) { + $depchannel = isset($package['channel']) ? $package['channel'] : '__uri'; + $p = &$this->getPackage($package['name'], $depchannel); + if ($p) { + $save = &$p; + $ret[] = &$save; + } + } + } + if (!count($ret)) { + return false; + } + return $ret; + } + + // {{{ getChannelValidator() + /** + * @param string channel name + * @return PEAR_Validate|false + */ + function &getChannelValidator($channel) + { + $chan = $this->getChannel($channel); + if (PEAR::isError($chan)) { + return $chan; + } + $val = $chan->getValidationObject(); + return $val; + } + // }}} + // {{{ getChannels() + /** + * @param string channel name + * @return array an array of PEAR_ChannelFile objects representing every installed channel + */ + function &getChannels() + { + $ret = array(); + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + foreach ($this->_listChannels() as $channel) { + $e = &$this->_getChannel($channel); + if (!$e || PEAR::isError($e)) { + continue; + } + $ret[] = $e; + } + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ checkFileMap() + + /** + * Test whether a file or set of files belongs to a package. + * + * If an array is passed in + * @param string|array file path, absolute or relative to the pear + * install dir + * @param string|array name of PEAR package or array('package' => name, 'channel' => + * channel) of a package that will be ignored + * @param string API version - 1.1 will exclude any files belonging to a package + * @param array private recursion variable + * @return array|false which package and channel the file belongs to, or an empty + * string if the file does not belong to an installed package, + * or belongs to the second parameter's package + */ + function checkFileMap($path, $package = false, $api = '1.0', $attrs = false) + { + if (is_array($path)) { + static $notempty; + if (empty($notempty)) { + if (!class_exists('PEAR_Installer_Role')) { + require_once 'PEAR/Installer/Role.php'; + } + $notempty = create_function('$a','return !empty($a);'); + } + $package = is_array($package) ? array(strtolower($package[0]), strtolower($package[1])) + : strtolower($package); + $pkgs = array(); + foreach ($path as $name => $attrs) { + if (is_array($attrs)) { + if (isset($attrs['install-as'])) { + $name = $attrs['install-as']; + } + if (!in_array($attrs['role'], PEAR_Installer_Role::getInstallableRoles())) { + // these are not installed + continue; + } + if (!in_array($attrs['role'], PEAR_Installer_Role::getBaseinstallRoles())) { + $attrs['baseinstalldir'] = is_array($package) ? $package[1] : $package; + } + if (isset($attrs['baseinstalldir'])) { + $name = $attrs['baseinstalldir'] . DIRECTORY_SEPARATOR . $name; + } + } + $pkgs[$name] = $this->checkFileMap($name, $package, $api, $attrs); + if (PEAR::isError($pkgs[$name])) { + return $pkgs[$name]; + } + } + return array_filter($pkgs, $notempty); + } + if (empty($this->filemap_cache)) { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $err = $this->_readFileMap(); + $this->_unlock(); + if (PEAR::isError($err)) { + return $err; + } + } + if (!$attrs) { + $attrs = array('role' => 'php'); // any old call would be for PHP role only + } + if (isset($this->filemap_cache[$attrs['role']][$path])) { + if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) { + return false; + } + return $this->filemap_cache[$attrs['role']][$path]; + } + $l = strlen($this->install_dir); + if (substr($path, 0, $l) == $this->install_dir) { + $path = preg_replace('!^'.DIRECTORY_SEPARATOR.'+!', '', substr($path, $l)); + } + if (isset($this->filemap_cache[$attrs['role']][$path])) { + if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) { + return false; + } + return $this->filemap_cache[$attrs['role']][$path]; + } + return false; + } + + // }}} + // {{{ flush() + /** + * Force a reload of the filemap + * @since 1.5.0RC3 + */ + function flushFileMap() + { + $this->filemap_cache = null; + clearstatcache(); // ensure that the next read gets the full, current filemap + } + + // }}} + // {{{ apiVersion() + /** + * Get the expected API version. Channels API is version 1.1, as it is backwards + * compatible with 1.0 + * @return string + */ + function apiVersion() + { + return '1.1'; + } + // }}} + + + /** + * Parse a package name, or validate a parsed package name array + * @param string|array pass in an array of format + * array( + * 'package' => 'pname', + * ['channel' => 'channame',] + * ['version' => 'version',] + * ['state' => 'state',] + * ['group' => 'groupname']) + * or a string of format + * [channel://][channame/]pname[-version|-state][/group=groupname] + * @return array|PEAR_Error + */ + function parsePackageName($param, $defaultchannel = 'pear.php.net') + { + $saveparam = $param; + if (is_array($param)) { + // convert to string for error messages + $saveparam = $this->parsedPackageNameToString($param); + // process the array + if (!isset($param['package'])) { + return PEAR::raiseError('parsePackageName(): array $param ' . + 'must contain a valid package name in index "param"', + 'package', null, null, $param); + } + if (!isset($param['uri'])) { + if (!isset($param['channel'])) { + $param['channel'] = $defaultchannel; + } + } else { + $param['channel'] = '__uri'; + } + } else { + $components = @parse_url((string) $param); + if (isset($components['scheme'])) { + if ($components['scheme'] == 'http') { + // uri package + $param = array('uri' => $param, 'channel' => '__uri'); + } elseif($components['scheme'] != 'channel') { + return PEAR::raiseError('parsePackageName(): only channel:// uris may ' . + 'be downloaded, not "' . $param . '"', 'invalid', null, null, $param); + } + } + if (!isset($components['path'])) { + return PEAR::raiseError('parsePackageName(): array $param ' . + 'must contain a valid package name in "' . $param . '"', + 'package', null, null, $param); + } + if (isset($components['host'])) { + // remove the leading "/" + $components['path'] = substr($components['path'], 1); + } + if (!isset($components['scheme'])) { + if (strpos($components['path'], '/') !== false) { + if ($components['path']{0} == '/') { + return PEAR::raiseError('parsePackageName(): this is not ' . + 'a package name, it begins with "/" in "' . $param . '"', + 'invalid', null, null, $param); + } + $parts = explode('/', $components['path']); + $components['host'] = array_shift($parts); + if (count($parts) > 1) { + $components['path'] = array_pop($parts); + $components['host'] .= '/' . implode('/', $parts); + } else { + $components['path'] = implode('/', $parts); + } + } else { + $components['host'] = $defaultchannel; + } + } else { + if (strpos($components['path'], '/')) { + $parts = explode('/', $components['path']); + $components['path'] = array_pop($parts); + $components['host'] .= '/' . implode('/', $parts); + } + } + + if (is_array($param)) { + $param['package'] = $components['path']; + } else { + $param = array( + 'package' => $components['path'] + ); + if (isset($components['host'])) { + $param['channel'] = $components['host']; + } + } + if (isset($components['fragment'])) { + $param['group'] = $components['fragment']; + } + if (isset($components['user'])) { + $param['user'] = $components['user']; + } + if (isset($components['pass'])) { + $param['pass'] = $components['pass']; + } + if (isset($components['query'])) { + parse_str($components['query'], $param['opts']); + } + // check for extension + $pathinfo = pathinfo($param['package']); + if (isset($pathinfo['extension']) && + in_array(strtolower($pathinfo['extension']), array('tgz', 'tar'))) { + $param['extension'] = $pathinfo['extension']; + $param['package'] = substr($pathinfo['basename'], 0, + strlen($pathinfo['basename']) - 4); + } + // check for version + if (strpos($param['package'], '-')) { + $test = explode('-', $param['package']); + if (count($test) != 2) { + return PEAR::raiseError('parsePackageName(): only one version/state ' . + 'delimiter "-" is allowed in "' . $saveparam . '"', + 'version', null, null, $param); + } + list($param['package'], $param['version']) = $test; + } + } + // validation + $info = $this->channelExists($param['channel']); + if (PEAR::isError($info)) { + return $info; + } + if (!$info) { + return PEAR::raiseError('unknown channel "' . $param['channel'] . + '" in "' . $saveparam . '"', 'channel', null, null, $param); + } + $chan = $this->getChannel($param['channel']); + if (PEAR::isError($chan)) { + return $chan; + } + if (!$chan) { + return PEAR::raiseError("Exception: corrupt registry, could not " . + "retrieve channel " . $param['channel'] . " information", + 'registry', null, null, $param); + } + $param['channel'] = $chan->getName(); + $validate = $chan->getValidationObject(); + $vpackage = $chan->getValidationPackage(); + // validate package name + if (!$validate->validPackageName($param['package'], $vpackage['_content'])) { + return PEAR::raiseError('parsePackageName(): invalid package name "' . + $param['package'] . '" in "' . $saveparam . '"', + 'package', null, null, $param); + } + if (isset($param['group'])) { + if (!PEAR_Validate::validGroupName($param['group'])) { + return PEAR::raiseError('parsePackageName(): dependency group "' . $param['group'] . + '" is not a valid group name in "' . $saveparam . '"', 'group', null, null, + $param); + } + } + if (isset($param['state'])) { + if (!in_array(strtolower($param['state']), $validate->getValidStates())) { + return PEAR::raiseError('parsePackageName(): state "' . $param['state'] + . '" is not a valid state in "' . $saveparam . '"', + 'state', null, null, $param); + } + } + if (isset($param['version'])) { + if (isset($param['state'])) { + return PEAR::raiseError('parsePackageName(): cannot contain both ' . + 'a version and a stability (state) in "' . $saveparam . '"', + 'version/state', null, null, $param); + } + // check whether version is actually a state + if (in_array(strtolower($param['version']), $validate->getValidStates())) { + $param['state'] = strtolower($param['version']); + unset($param['version']); + } else { + if (!$validate->validVersion($param['version'])) { + return PEAR::raiseError('parsePackageName(): "' . $param['version'] . + '" is neither a valid version nor a valid state in "' . + $saveparam . '"', 'version/state', null, null, $param); + } + } + } + return $param; + } + + /** + * @param array + * @return string + */ + function parsedPackageNameToString($parsed, $brief = false) + { + if (is_string($parsed)) { + return $parsed; + } + if (is_object($parsed)) { + $p = $parsed; + $parsed = array( + 'package' => $p->getPackage(), + 'channel' => $p->getChannel(), + 'version' => $p->getVersion(), + ); + } + if (isset($parsed['uri'])) { + return $parsed['uri']; + } + if ($brief) { + if ($channel = $this->channelAlias($parsed['channel'])) { + return $channel . '/' . $parsed['package']; + } + } + $upass = ''; + if (isset($parsed['user'])) { + $upass = $parsed['user']; + if (isset($parsed['pass'])) { + $upass .= ':' . $parsed['pass']; + } + $upass = "$upass@"; + } + $ret = 'channel://' . $upass . $parsed['channel'] . '/' . $parsed['package']; + if (isset($parsed['version']) || isset($parsed['state'])) { + $ver = isset($parsed['version']) ? $parsed['version'] : ''; + $ver .= isset($parsed['state']) ? $parsed['state'] : ''; + $ret .= '-' . $ver; + } + if (isset($parsed['extension'])) { + $ret .= '.' . $parsed['extension']; + } + if (isset($parsed['opts'])) { + $ret .= '?'; + foreach ($parsed['opts'] as $name => $value) { + $parsed['opts'][$name] = "$name=$value"; + } + $ret .= implode('&', $parsed['opts']); + } + if (isset($parsed['group'])) { + $ret .= '#' . $parsed['group']; + } + return $ret; + } +}PEAR-1.9.0/PEAR/REST.php100664 764 764 36073 100664 7354 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: REST.php 286489 2009-07-29 05:59:08Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * For downloading xml files + */ +require_once 'PEAR.php'; +require_once 'PEAR/XMLParser.php'; + +/** + * Intelligently retrieve data, following hyperlinks if necessary, and re-directing + * as well + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_REST +{ + var $config; + var $_options; + + function PEAR_REST(&$config, $options = array()) + { + $this->config = &$config; + $this->_options = $options; + } + + /** + * Retrieve REST data, but always retrieve the local cache if it is available. + * + * This is useful for elements that should never change, such as information on a particular + * release + * @param string full URL to this resource + * @param array|false contents of the accept-encoding header + * @param boolean if true, xml will be returned as a string, otherwise, xml will be + * parsed using PEAR_XMLParser + * @return string|array + */ + function retrieveCacheFirst($url, $accept = false, $forcestring = false, $channel = false) + { + $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cachefile'; + + if (file_exists($cachefile)) { + return unserialize(implode('', file($cachefile))); + } + + return $this->retrieveData($url, $accept, $forcestring, $channel); + } + + /** + * Retrieve a remote REST resource + * @param string full URL to this resource + * @param array|false contents of the accept-encoding header + * @param boolean if true, xml will be returned as a string, otherwise, xml will be + * parsed using PEAR_XMLParser + * @return string|array + */ + function retrieveData($url, $accept = false, $forcestring = false, $channel = false) + { + $cacheId = $this->getCacheId($url); + if ($ret = $this->useLocalCache($url, $cacheId)) { + return $ret; + } + + $file = $trieddownload = false; + if (!isset($this->_options['offline'])) { + $trieddownload = true; + $file = $this->downloadHttp($url, $cacheId ? $cacheId['lastChange'] : false, $accept, $channel); + } + + if (PEAR::isError($file)) { + if ($file->getCode() !== -9276) { + return $file; + } + + $trieddownload = false; + $file = false; // use local copy if available on socket connect error + } + + if (!$file) { + $ret = $this->getCache($url); + if (!PEAR::isError($ret) && $trieddownload) { + // reset the age of the cache if the server says it was unmodified + $this->saveCache($url, $ret, null, true, $cacheId); + } + + return $ret; + } + + if (is_array($file)) { + $headers = $file[2]; + $lastmodified = $file[1]; + $content = $file[0]; + } else { + $headers = array(); + $lastmodified = false; + $content = $file; + } + + if ($forcestring) { + $this->saveCache($url, $content, $lastmodified, false, $cacheId); + return $content; + } + + if (isset($headers['content-type'])) { + switch ($headers['content-type']) { + case 'text/xml' : + case 'application/xml' : + case 'text/plain' : + if ($headers['content-type'] === 'text/plain') { + $check = substr($content, 0, 5); + if ($check !== 'parse($content); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + return PEAR::raiseError('Invalid xml downloaded from "' . $url . '": ' . + $err->getMessage()); + } + $content = $parser->getData(); + case 'text/html' : + default : + // use it as a string + } + } else { + // assume XML + $parser = new PEAR_XMLParser; + $parser->parse($content); + $content = $parser->getData(); + } + + $this->saveCache($url, $content, $lastmodified, false, $cacheId); + return $content; + } + + function useLocalCache($url, $cacheid = null) + { + if ($cacheid === null) { + $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cacheid'; + if (!file_exists($cacheidfile)) { + return false; + } + + $cacheid = unserialize(implode('', file($cacheidfile))); + } + + $cachettl = $this->config->get('cache_ttl'); + // If cache is newer than $cachettl seconds, we use the cache! + if (time() - $cacheid['age'] < $cachettl) { + return $this->getCache($url); + } + + return false; + } + + function getCacheId($url) + { + $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cacheid'; + + if (!file_exists($cacheidfile)) { + return false; + } + + $ret = unserialize(implode('', file($cacheidfile))); + return $ret; + } + + function getCache($url) + { + $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cachefile'; + + if (!file_exists($cachefile)) { + return PEAR::raiseError('No cached content available for "' . $url . '"'); + } + + return unserialize(implode('', file($cachefile))); + } + + /** + * @param string full URL to REST resource + * @param string original contents of the REST resource + * @param array HTTP Last-Modified and ETag headers + * @param bool if true, then the cache id file should be regenerated to + * trigger a new time-to-live value + */ + function saveCache($url, $contents, $lastmodified, $nochange = false, $cacheid = null) + { + $cachedir = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . md5($url); + $cacheidfile = $cachedir . 'rest.cacheid'; + $cachefile = $cachedir . 'rest.cachefile'; + + if ($cacheid === null && $nochange) { + $cacheid = unserialize(implode('', file($cacheidfile))); + } + + $fp = @fopen($cacheidfile, 'wb'); + if (!$fp) { + $cache_dir = $this->config->get('cache_dir'); + if (is_dir($cache_dir)) { + return false; + } + + System::mkdir(array('-p', $cache_dir)); + $fp = @fopen($cacheidfile, 'wb'); + if (!$fp) { + return false; + } + } + + if ($nochange) { + fwrite($fp, serialize(array( + 'age' => time(), + 'lastChange' => $cacheid['lastChange'], + )) + ); + + fclose($fp); + return true; + } + + fwrite($fp, serialize(array( + 'age' => time(), + 'lastChange' => $lastmodified, + )) + ); + + fclose($fp); + $fp = @fopen($cachefile, 'wb'); + if (!$fp) { + if (file_exists($cacheidfile)) { + @unlink($cacheidfile); + } + + return false; + } + + fwrite($fp, serialize($contents)); + fclose($fp); + return true; + } + + /** + * Efficiently Download a file through HTTP. Returns downloaded file as a string in-memory + * This is best used for small files + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param string $save_dir directory to save file in + * @param false|string|array $lastmodified header values to check against for caching + * use false to return the header values from this download + * @param false|array $accept Accept headers to send + * @return string|array Returns the contents of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). If caching is requested, then return the header + * values. + * + * @access public + */ + function downloadHttp($url, $lastmodified = null, $accept = false, $channel = false) + { + static $redirect = 0; + // always reset , so we are clean case of error + $wasredirect = $redirect; + $redirect = 0; + + $info = parse_url($url); + if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) { + return PEAR::raiseError('Cannot download non-http URL "' . $url . '"'); + } + + if (!isset($info['host'])) { + return PEAR::raiseError('Cannot download from non-URL "' . $url . '"'); + } + + $host = isset($info['host']) ? $info['host'] : null; + $port = isset($info['port']) ? $info['port'] : null; + $path = isset($info['path']) ? $info['path'] : null; + $schema = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http'; + + $proxy_host = $proxy_port = $proxy_user = $proxy_pass = ''; + if ($this->config->get('http_proxy')&& + $proxy = parse_url($this->config->get('http_proxy')) + ) { + $proxy_host = isset($proxy['host']) ? $proxy['host'] : null; + if ($schema === 'https') { + $proxy_host = 'ssl://' . $proxy_host; + } + + $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080; + $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null; + $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null; + $proxy_schema = (isset($proxy['scheme']) && $proxy['scheme'] == 'https') ? 'https' : 'http'; + } + + if (empty($port)) { + $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80; + } + + if (isset($proxy['host'])) { + $request = "GET $url HTTP/1.1\r\n"; + } else { + $request = "GET $path HTTP/1.1\r\n"; + } + + $request .= "Host: $host:$port\r\n"; + $ifmodifiedsince = ''; + if (is_array($lastmodified)) { + if (isset($lastmodified['Last-Modified'])) { + $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n"; + } + + if (isset($lastmodified['ETag'])) { + $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n"; + } + } else { + $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : ''); + } + + $request .= $ifmodifiedsince . + "User-Agent: PEAR/1.9.0/PHP/" . PHP_VERSION . "\r\n"; + + $username = $this->config->get('username', null, $channel); + $password = $this->config->get('password', null, $channel); + + if ($username && $password) { + $tmp = base64_encode("$username:$password"); + $request .= "Authorization: Basic $tmp\r\n"; + } + + if ($proxy_host != '' && $proxy_user != '') { + $request .= 'Proxy-Authorization: Basic ' . + base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n"; + } + + if ($accept) { + $request .= 'Accept: ' . implode(', ', $accept) . "\r\n"; + } + + $request .= "Accept-Encoding:\r\n"; + $request .= "Connection: close\r\n"; + $request .= "\r\n"; + + if ($proxy_host != '') { + $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr, 15); + if (!$fp) { + return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", -9276); + } + } else { + if ($schema === 'https') { + $host = 'ssl://' . $host; + } + + $fp = @fsockopen($host, $port, $errno, $errstr); + if (!$fp) { + return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno); + } + } + + fwrite($fp, $request); + + $headers = array(); + $reply = 0; + while ($line = trim(fgets($fp, 1024))) { + if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) { + $headers[strtolower($matches[1])] = trim($matches[2]); + } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) { + $reply = (int)$matches[1]; + if ($reply == 304 && ($lastmodified || ($lastmodified === false))) { + return false; + } + + if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (received: $line)"); + } + } + } + + if ($reply != 200) { + if (!isset($headers['location'])) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (redirected but no location)"); + } + + if ($wasredirect > 4) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (redirection looped more than 5 times)"); + } + + $redirect = $wasredirect + 1; + return $this->downloadHttp($headers['location'], $lastmodified, $accept, $channel); + } + + $length = isset($headers['content-length']) ? $headers['content-length'] : -1; + + $data = ''; + while ($chunk = @fread($fp, 8192)) { + $data .= $chunk; + } + fclose($fp); + + if ($lastmodified === false || $lastmodified) { + if (isset($headers['etag'])) { + $lastmodified = array('ETag' => $headers['etag']); + } + + if (isset($headers['last-modified'])) { + if (is_array($lastmodified)) { + $lastmodified['Last-Modified'] = $headers['last-modified']; + } else { + $lastmodified = $headers['last-modified']; + } + } + + return array($data, $lastmodified, $headers); + } + + return $data; + } +}PEAR-1.9.0/PEAR/RunTest.php100664 764 764 105252 100664 10217 + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: RunTest.php 287447 2009-08-18 11:46:19Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.3.3 + */ + +/** + * for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Config.php'; + +define('DETAILED', 1); +putenv("PHP_PEAR_RUNTESTS=1"); + +/** + * Simplified version of PHP's test suite + * + * Try it with: + * + * $ php -r 'include "../PEAR/RunTest.php"; $t=new PEAR_RunTest; $o=$t->run("./pear_system.phpt");print_r($o);' + * + * + * @category pear + * @package PEAR + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.3.3 + */ +class PEAR_RunTest +{ + var $_headers = array(); + var $_logger; + var $_options; + var $_php; + var $tests_count; + var $xdebug_loaded; + /** + * Saved value of php executable, used to reset $_php when we + * have a test that uses cgi + * + * @var unknown_type + */ + var $_savephp; + var $ini_overwrites = array( + 'output_handler=', + 'open_basedir=', + 'safe_mode=0', + 'disable_functions=', + 'output_buffering=Off', + 'display_errors=1', + 'log_errors=0', + 'html_errors=0', + 'track_errors=1', + 'report_memleaks=0', + 'report_zend_debug=0', + 'docref_root=', + 'docref_ext=.html', + 'error_prepend_string=', + 'error_append_string=', + 'auto_prepend_file=', + 'auto_append_file=', + 'magic_quotes_runtime=0', + 'xdebug.default_enable=0', + 'allow_url_fopen=1', + ); + + /** + * An object that supports the PEAR_Common->log() signature, or null + * @param PEAR_Common|null + */ + function PEAR_RunTest($logger = null, $options = array()) + { + if (!defined('E_DEPRECATED')) { + define('E_DEPRECATED', 0); + } + if (!defined('E_STRICT')) { + define('E_STRICT', 0); + } + $this->ini_overwrites[] = 'error_reporting=' . (E_ALL & ~(E_DEPRECATED | E_STRICT)); + if (is_null($logger)) { + require_once 'PEAR/Common.php'; + $logger = new PEAR_Common; + } + $this->_logger = $logger; + $this->_options = $options; + + $conf = &PEAR_Config::singleton(); + $this->_php = $conf->get('php_bin'); + } + + /** + * Taken from php-src/run-tests.php + * + * @param string $commandline command name + * @param array $env + * @param string $stdin standard input to pass to the command + * @return unknown + */ + function system_with_timeout($commandline, $env = null, $stdin = null) + { + $data = ''; + if (version_compare(phpversion(), '5.0.0', '<')) { + $proc = proc_open($commandline, array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w') + ), $pipes); + } else { + $proc = proc_open($commandline, array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w') + ), $pipes, null, $env, array('suppress_errors' => true)); + } + + if (!$proc) { + return false; + } + + if (is_string($stdin)) { + fwrite($pipes[0], $stdin); + } + fclose($pipes[0]); + + while (true) { + /* hide errors from interrupted syscalls */ + $r = $pipes; + $e = $w = null; + $n = @stream_select($r, $w, $e, 60); + + if ($n === 0) { + /* timed out */ + $data .= "\n ** ERROR: process timed out **\n"; + proc_terminate($proc); + return array(1234567890, $data); + } else if ($n > 0) { + $line = fread($pipes[1], 8192); + if (strlen($line) == 0) { + /* EOF */ + break; + } + $data .= $line; + } + } + if (function_exists('proc_get_status')) { + $stat = proc_get_status($proc); + if ($stat['signaled']) { + $data .= "\nTermsig=".$stat['stopsig']; + } + } + $code = proc_close($proc); + if (function_exists('proc_get_status')) { + $code = $stat['exitcode']; + } + return array($code, $data); + } + + /** + * Turns a PHP INI string into an array + * + * Turns -d "include_path=/foo/bar" into this: + * array( + * 'include_path' => array( + * 'operator' => '-d', + * 'value' => '/foo/bar', + * ) + * ) + * Works both with quotes and without + * + * @param string an PHP INI string, -d "include_path=/foo/bar" + * @return array + */ + function iniString2array($ini_string) + { + if (!$ini_string) { + return array(); + } + $split = preg_split('/[\s]|=/', $ini_string, -1, PREG_SPLIT_NO_EMPTY); + $key = $split[1][0] == '"' ? substr($split[1], 1) : $split[1]; + $value = $split[2][strlen($split[2]) - 1] == '"' ? substr($split[2], 0, -1) : $split[2]; + // FIXME review if this is really the struct to go with + $array = array($key => array('operator' => $split[0], 'value' => $value)); + return $array; + } + + function settings2array($settings, $ini_settings) + { + foreach ($settings as $setting) { + if (strpos($setting, '=') !== false) { + $setting = explode('=', $setting, 2); + $name = trim(strtolower($setting[0])); + $value = trim($setting[1]); + $ini_settings[$name] = $value; + } + } + return $ini_settings; + } + + function settings2params($ini_settings) + { + $settings = ''; + foreach ($ini_settings as $name => $value) { + if (is_array($value)) { + $operator = $value['operator']; + $value = $value['value']; + } else { + $operator = '-d'; + } + $value = addslashes($value); + $settings .= " $operator \"$name=$value\""; + } + return $settings; + } + + function _preparePhpBin($php, $file, $ini_settings) + { + $file = escapeshellarg($file); + // This was fixed in php 5.3 and is not needed after that + if (OS_WINDOWS && version_compare(PHP_VERSION, '5.3', '<')) { + $cmd = '"'.escapeshellarg($php).' '.$ini_settings.' -f ' . $file .'"'; + } else { + $cmd = $php . $ini_settings . ' -f ' . $file; + } + + return $cmd; + } + + function runPHPUnit($file, $ini_settings = '') + { + if (!file_exists($file) && file_exists(getcwd() . DIRECTORY_SEPARATOR . $file)) { + $file = realpath(getcwd() . DIRECTORY_SEPARATOR . $file); + } elseif (file_exists($file)) { + $file = realpath($file); + } + + $cmd = $this->_preparePhpBin($this->_php, $file, $ini_settings); + if (isset($this->_logger)) { + $this->_logger->log(2, 'Running command "' . $cmd . '"'); + } + + $savedir = getcwd(); // in case the test moves us around + chdir(dirname($file)); + echo `$cmd`; + chdir($savedir); + return 'PASSED'; // we have no way of knowing this information so assume passing + } + + /** + * Runs an individual test case. + * + * @param string The filename of the test + * @param array|string INI settings to be applied to the test run + * @param integer Number what the current running test is of the + * whole test suite being runned. + * + * @return string|object Returns PASSED, WARNED, FAILED depending on how the + * test came out. + * PEAR Error when the tester it self fails + */ + function run($file, $ini_settings = array(), $test_number = 1) + { + if (isset($this->_savephp)) { + $this->_php = $this->_savephp; + unset($this->_savephp); + } + if (empty($this->_options['cgi'])) { + // try to see if php-cgi is in the path + $res = $this->system_with_timeout('php-cgi -v'); + if (false !== $res && !(is_array($res) && $res === array(127, ''))) { + $this->_options['cgi'] = 'php-cgi'; + } + } + if (1 < $len = strlen($this->tests_count)) { + $test_number = str_pad($test_number, $len, ' ', STR_PAD_LEFT); + $test_nr = "[$test_number/$this->tests_count] "; + } else { + $test_nr = ''; + } + + $file = realpath($file); + $section_text = $this->_readFile($file); + if (PEAR::isError($section_text)) { + return $section_text; + } + + if (isset($section_text['POST_RAW']) && isset($section_text['UPLOAD'])) { + return PEAR::raiseError("Cannot contain both POST_RAW and UPLOAD in test file: $file"); + } + + $cwd = getcwd(); + + $pass_options = ''; + if (!empty($this->_options['ini'])) { + $pass_options = $this->_options['ini']; + } + + if (is_string($ini_settings)) { + $ini_settings = $this->iniString2array($ini_settings); + } + + $ini_settings = $this->settings2array($this->ini_overwrites, $ini_settings); + if ($section_text['INI']) { + if (strpos($section_text['INI'], '{PWD}') !== false) { + $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']); + } + $ini = preg_split( "/[\n\r]+/", $section_text['INI']); + $ini_settings = $this->settings2array($ini, $ini_settings); + } + $ini_settings = $this->settings2params($ini_settings); + $shortname = str_replace($cwd . DIRECTORY_SEPARATOR, '', $file); + + $tested = trim($section_text['TEST']); + $tested.= !isset($this->_options['simple']) ? "[$shortname]" : ' '; + + if (!empty($section_text['POST']) || !empty($section_text['POST_RAW']) || + !empty($section_text['UPLOAD']) || !empty($section_text['GET']) || + !empty($section_text['COOKIE']) || !empty($section_text['EXPECTHEADERS'])) { + if (empty($this->_options['cgi'])) { + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "SKIP $test_nr$tested (reason: --cgi option needed for this test, type 'pear help run-tests')"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' # skip --cgi option needed for this test, "pear help run-tests" for info'); + } + return 'SKIPPED'; + } + $this->_savephp = $this->_php; + $this->_php = $this->_options['cgi']; + } + + $temp_dir = realpath(dirname($file)); + $main_file_name = basename($file, 'phpt'); + $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'diff'; + $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'log'; + $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'exp'; + $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'out'; + $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'mem'; + $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'php'; + $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'skip.php'; + $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'clean.php'; + $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.'); + + // unlink old test results + $this->_cleanupOldFiles($file); + + // Check if test should be skipped. + $res = $this->_runSkipIf($section_text, $temp_skipif, $tested, $ini_settings); + if (count($res) != 2) { + return $res; + } + $info = $res['info']; + $warn = $res['warn']; + + // We've satisfied the preconditions - run the test! + if (isset($this->_options['coverage']) && $this->xdebug_loaded) { + $xdebug_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'xdebug'; + $text = '"; + + $len_f = 5; + if (substr($section_text['FILE'], 0, 5) != 'save_text($temp_file, $text); + } else { + $this->save_text($temp_file, $section_text['FILE']); + } + + $args = $section_text['ARGS'] ? ' -- '.$section_text['ARGS'] : ''; + $cmd = $this->_preparePhpBin($this->_php, $temp_file, $ini_settings); + $cmd.= "$args 2>&1"; + if (isset($this->_logger)) { + $this->_logger->log(2, 'Running command "' . $cmd . '"'); + } + + // Reset environment from any previous test. + $env = $this->_resetEnv($section_text, $temp_file); + + $section_text = $this->_processUpload($section_text, $file); + if (PEAR::isError($section_text)) { + return $section_text; + } + + if (array_key_exists('POST_RAW', $section_text) && !empty($section_text['POST_RAW'])) { + $post = trim($section_text['POST_RAW']); + $raw_lines = explode("\n", $post); + + $request = ''; + $started = false; + foreach ($raw_lines as $i => $line) { + if (empty($env['CONTENT_TYPE']) && + preg_match('/^Content-Type:(.*)/i', $line, $res)) { + $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1])); + continue; + } + if ($started) { + $request .= "\n"; + } + $started = true; + $request .= $line; + } + + $env['CONTENT_LENGTH'] = strlen($request); + $env['REQUEST_METHOD'] = 'POST'; + + $this->save_text($tmp_post, $request); + $cmd = "$this->_php$pass_options$ini_settings \"$temp_file\" 2>&1 < $tmp_post"; + } elseif (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) { + $post = trim($section_text['POST']); + $this->save_text($tmp_post, $post); + $content_length = strlen($post); + + $env['REQUEST_METHOD'] = 'POST'; + $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + $env['CONTENT_LENGTH'] = $content_length; + + $cmd = "$this->_php$pass_options$ini_settings \"$temp_file\" 2>&1 < $tmp_post"; + } else { + $env['REQUEST_METHOD'] = 'GET'; + $env['CONTENT_TYPE'] = ''; + $env['CONTENT_LENGTH'] = ''; + } + + if (OS_WINDOWS && isset($section_text['RETURNS'])) { + ob_start(); + system($cmd, $return_value); + $out = ob_get_contents(); + ob_end_clean(); + $section_text['RETURNS'] = (int) trim($section_text['RETURNS']); + $returnfail = ($return_value != $section_text['RETURNS']); + } else { + $returnfail = false; + $stdin = isset($section_text['STDIN']) ? $section_text['STDIN'] : null; + $out = $this->system_with_timeout($cmd, $env, $stdin); + $return_value = $out[0]; + $out = $out[1]; + } + + $output = preg_replace('/\r\n/', "\n", trim($out)); + + if (isset($tmp_post) && realpath($tmp_post) && file_exists($tmp_post)) { + @unlink(realpath($tmp_post)); + } + chdir($cwd); // in case the test moves us around + + $this->_testCleanup($section_text, $temp_clean); + + /* when using CGI, strip the headers from the output */ + $output = $this->_stripHeadersCGI($output); + + if (isset($section_text['EXPECTHEADERS'])) { + $testheaders = $this->_processHeaders($section_text['EXPECTHEADERS']); + $missing = array_diff_assoc($testheaders, $this->_headers); + $changed = ''; + foreach ($missing as $header => $value) { + if (isset($this->_headers[$header])) { + $changed .= "-$header: $value\n+$header: "; + $changed .= $this->_headers[$header]; + } else { + $changed .= "-$header: $value\n"; + } + } + if ($missing) { + // tack on failed headers to output: + $output .= "\n====EXPECTHEADERS FAILURE====:\n$changed"; + } + } + // Does the output match what is expected? + do { + if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) { + if (isset($section_text['EXPECTF'])) { + $wanted = trim($section_text['EXPECTF']); + } else { + $wanted = trim($section_text['EXPECTREGEX']); + } + $wanted_re = preg_replace('/\r\n/', "\n", $wanted); + if (isset($section_text['EXPECTF'])) { + $wanted_re = preg_quote($wanted_re, '/'); + // Stick to basics + $wanted_re = str_replace("%s", ".+?", $wanted_re); //not greedy + $wanted_re = str_replace("%i", "[+\-]?[0-9]+", $wanted_re); + $wanted_re = str_replace("%d", "[0-9]+", $wanted_re); + $wanted_re = str_replace("%x", "[0-9a-fA-F]+", $wanted_re); + $wanted_re = str_replace("%f", "[+\-]?\.?[0-9]+\.?[0-9]*(E-?[0-9]+)?", $wanted_re); + $wanted_re = str_replace("%c", ".", $wanted_re); + // %f allows two points "-.0.0" but that is the best *simple* expression + } + + /* DEBUG YOUR REGEX HERE + var_dump($wanted_re); + print(str_repeat('=', 80) . "\n"); + var_dump($output); + */ + if (!$returnfail && preg_match("/^$wanted_re\$/s", $output)) { + if (file_exists($temp_file)) { + unlink($temp_file); + } + if (array_key_exists('FAIL', $section_text)) { + break; + } + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "PASS $test_nr$tested$info"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' - ' . $tested); + } + return 'PASSED'; + } + } else { + if (isset($section_text['EXPECTFILE'])) { + $f = $temp_dir . '/' . trim($section_text['EXPECTFILE']); + if (!($fp = @fopen($f, 'rb'))) { + return PEAR::raiseError('--EXPECTFILE-- section file ' . + $f . ' not found'); + } + fclose($fp); + $section_text['EXPECT'] = file_get_contents($f); + } + + if (isset($section_text['EXPECT'])) { + $wanted = preg_replace('/\r\n/', "\n", trim($section_text['EXPECT'])); + } else { + $wanted = ''; + } + + // compare and leave on success + if (!$returnfail && 0 == strcmp($output, $wanted)) { + if (file_exists($temp_file)) { + unlink($temp_file); + } + if (array_key_exists('FAIL', $section_text)) { + break; + } + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "PASS $test_nr$tested$info"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' - ' . $tested); + } + return 'PASSED'; + } + } + } while (false); + + if (array_key_exists('FAIL', $section_text)) { + // we expect a particular failure + // this is only used for testing PEAR_RunTest + $expectf = isset($section_text['EXPECTF']) ? $wanted_re : null; + $faildiff = $this->generate_diff($wanted, $output, null, $expectf); + $faildiff = preg_replace('/\r/', '', $faildiff); + $wanted = preg_replace('/\r/', '', trim($section_text['FAIL'])); + if ($faildiff == $wanted) { + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "PASS $test_nr$tested$info"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' - ' . $tested); + } + return 'PASSED'; + } + unset($section_text['EXPECTF']); + $output = $faildiff; + if (isset($section_text['RETURNS'])) { + return PEAR::raiseError('Cannot have both RETURNS and FAIL in the same test: ' . + $file); + } + } + + // Test failed so we need to report details. + $txt = $warn ? 'WARN ' : 'FAIL '; + $this->_logger->log(0, $txt . $test_nr . $tested . $info); + + // write .exp + $res = $this->_writeLog($exp_filename, $wanted); + if (PEAR::isError($res)) { + return $res; + } + + // write .out + $res = $this->_writeLog($output_filename, $output); + if (PEAR::isError($res)) { + return $res; + } + + // write .diff + $returns = isset($section_text['RETURNS']) ? + array(trim($section_text['RETURNS']), $return_value) : null; + $expectf = isset($section_text['EXPECTF']) ? $wanted_re : null; + $data = $this->generate_diff($wanted, $output, $returns, $expectf); + $res = $this->_writeLog($diff_filename, $data); + if (PEAR::isError($res)) { + return $res; + } + + // write .log + $data = " +---- EXPECTED OUTPUT +$wanted +---- ACTUAL OUTPUT +$output +---- FAILED +"; + + if ($returnfail) { + $data .= " +---- EXPECTED RETURN +$section_text[RETURNS] +---- ACTUAL RETURN +$return_value +"; + } + + $res = $this->_writeLog($log_filename, $data); + if (PEAR::isError($res)) { + return $res; + } + + if (isset($this->_options['tapoutput'])) { + $wanted = explode("\n", $wanted); + $wanted = "# Expected output:\n#\n#" . implode("\n#", $wanted); + $output = explode("\n", $output); + $output = "#\n#\n# Actual output:\n#\n#" . implode("\n#", $output); + return array($wanted . $output . 'not ok', ' - ' . $tested); + } + return $warn ? 'WARNED' : 'FAILED'; + } + + function generate_diff($wanted, $output, $rvalue, $wanted_re) + { + $w = explode("\n", $wanted); + $o = explode("\n", $output); + $wr = explode("\n", $wanted_re); + $w1 = array_diff_assoc($w, $o); + $o1 = array_diff_assoc($o, $w); + $o2 = $w2 = array(); + foreach ($w1 as $idx => $val) { + if (!$wanted_re || !isset($wr[$idx]) || !isset($o1[$idx]) || + !preg_match('/^' . $wr[$idx] . '\\z/', $o1[$idx])) { + $w2[sprintf("%03d<", $idx)] = sprintf("%03d- ", $idx + 1) . $val; + } + } + foreach ($o1 as $idx => $val) { + if (!$wanted_re || !isset($wr[$idx]) || + !preg_match('/^' . $wr[$idx] . '\\z/', $val)) { + $o2[sprintf("%03d>", $idx)] = sprintf("%03d+ ", $idx + 1) . $val; + } + } + $diff = array_merge($w2, $o2); + ksort($diff); + $extra = $rvalue ? "##EXPECTED: $rvalue[0]\r\n##RETURNED: $rvalue[1]" : ''; + return implode("\r\n", $diff) . $extra; + } + + // Write the given text to a temporary file, and return the filename. + function save_text($filename, $text) + { + if (!$fp = fopen($filename, 'w')) { + return PEAR::raiseError("Cannot open file '" . $filename . "' (save_text)"); + } + fwrite($fp, $text); + fclose($fp); + if (1 < DETAILED) echo " +FILE $filename {{{ +$text +}}} +"; + } + + function _cleanupOldFiles($file) + { + $temp_dir = realpath(dirname($file)); + $mainFileName = basename($file, 'phpt'); + $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'diff'; + $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'log'; + $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'exp'; + $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'out'; + $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'mem'; + $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'php'; + $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'skip.php'; + $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'clean.php'; + $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.'); + + // unlink old test results + @unlink($diff_filename); + @unlink($log_filename); + @unlink($exp_filename); + @unlink($output_filename); + @unlink($memcheck_filename); + @unlink($temp_file); + @unlink($temp_skipif); + @unlink($tmp_post); + @unlink($temp_clean); + } + + function _runSkipIf($section_text, $temp_skipif, $tested, $ini_settings) + { + $info = ''; + $warn = false; + if (array_key_exists('SKIPIF', $section_text) && trim($section_text['SKIPIF'])) { + $this->save_text($temp_skipif, $section_text['SKIPIF']); + $output = $this->system_with_timeout("$this->_php$ini_settings -f \"$temp_skipif\""); + $output = $output[1]; + $loutput = ltrim($output); + unlink($temp_skipif); + if (!strncasecmp('skip', $loutput, 4)) { + $skipreason = "SKIP $tested"; + if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $m)) { + $skipreason .= '(reason: ' . $m[1] . ')'; + } + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, $skipreason); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' # skip ' . $reason); + } + return 'SKIPPED'; + } + + if (!strncasecmp('info', $loutput, 4) + && preg_match('/^\s*info\s*(.+)\s*/i', $output, $m)) { + $info = " (info: $m[1])"; + } + + if (!strncasecmp('warn', $loutput, 4) + && preg_match('/^\s*warn\s*(.+)\s*/i', $output, $m)) { + $warn = true; /* only if there is a reason */ + $info = " (warn: $m[1])"; + } + } + + return array('warn' => $warn, 'info' => $info); + } + + function _stripHeadersCGI($output) + { + $this->headers = array(); + if (!empty($this->_options['cgi']) && + $this->_php == $this->_options['cgi'] && + preg_match("/^(.*?)(?:\n\n(.*)|\\z)/s", $output, $match)) { + $output = isset($match[2]) ? trim($match[2]) : ''; + $this->_headers = $this->_processHeaders($match[1]); + } + + return $output; + } + + /** + * Return an array that can be used with array_diff() to compare headers + * + * @param string $text + */ + function _processHeaders($text) + { + $headers = array(); + $rh = preg_split("/[\n\r]+/", $text); + foreach ($rh as $line) { + if (strpos($line, ':')!== false) { + $line = explode(':', $line, 2); + $headers[trim($line[0])] = trim($line[1]); + } + } + return $headers; + } + + function _readFile($file) + { + // Load the sections of the test file. + $section_text = array( + 'TEST' => '(unnamed test)', + 'SKIPIF' => '', + 'GET' => '', + 'COOKIE' => '', + 'POST' => '', + 'ARGS' => '', + 'INI' => '', + 'CLEAN' => '', + ); + + if (!is_file($file) || !$fp = fopen($file, "r")) { + return PEAR::raiseError("Cannot open test file: $file"); + } + + $section = ''; + while (!feof($fp)) { + $line = fgets($fp); + + // Match the beginning of a section. + if (preg_match('/^--([_A-Z]+)--/', $line, $r)) { + $section = $r[1]; + $section_text[$section] = ''; + continue; + } elseif (empty($section)) { + fclose($fp); + return PEAR::raiseError("Invalid sections formats in test file: $file"); + } + + // Add to the section text. + $section_text[$section] .= $line; + } + fclose($fp); + + return $section_text; + } + + function _writeLog($logname, $data) + { + if (!$log = fopen($logname, 'w')) { + return PEAR::raiseError("Cannot create test log - $logname"); + } + fwrite($log, $data); + fclose($log); + } + + function _resetEnv($section_text, $temp_file) + { + $env = $_ENV; + $env['REDIRECT_STATUS'] = ''; + $env['QUERY_STRING'] = ''; + $env['PATH_TRANSLATED'] = ''; + $env['SCRIPT_FILENAME'] = ''; + $env['REQUEST_METHOD'] = ''; + $env['CONTENT_TYPE'] = ''; + $env['CONTENT_LENGTH'] = ''; + if (!empty($section_text['ENV'])) { + if (strpos($section_text['ENV'], '{PWD}') !== false) { + $section_text['ENV'] = str_replace('{PWD}', dirname($temp_file), $section_text['ENV']); + } + foreach (explode("\n", trim($section_text['ENV'])) as $e) { + $e = explode('=', trim($e), 2); + if (!empty($e[0]) && isset($e[1])) { + $env[$e[0]] = $e[1]; + } + } + } + if (array_key_exists('GET', $section_text)) { + $env['QUERY_STRING'] = trim($section_text['GET']); + } else { + $env['QUERY_STRING'] = ''; + } + if (array_key_exists('COOKIE', $section_text)) { + $env['HTTP_COOKIE'] = trim($section_text['COOKIE']); + } else { + $env['HTTP_COOKIE'] = ''; + } + $env['REDIRECT_STATUS'] = '1'; + $env['PATH_TRANSLATED'] = $temp_file; + $env['SCRIPT_FILENAME'] = $temp_file; + + return $env; + } + + function _processUpload($section_text, $file) + { + if (array_key_exists('UPLOAD', $section_text) && !empty($section_text['UPLOAD'])) { + $upload_files = trim($section_text['UPLOAD']); + $upload_files = explode("\n", $upload_files); + + $request = "Content-Type: multipart/form-data; boundary=---------------------------20896060251896012921717172737\n" . + "-----------------------------20896060251896012921717172737\n"; + foreach ($upload_files as $fileinfo) { + $fileinfo = explode('=', $fileinfo); + if (count($fileinfo) != 2) { + return PEAR::raiseError("Invalid UPLOAD section in test file: $file"); + } + if (!realpath(dirname($file) . '/' . $fileinfo[1])) { + return PEAR::raiseError("File for upload does not exist: $fileinfo[1] " . + "in test file: $file"); + } + $file_contents = file_get_contents(dirname($file) . '/' . $fileinfo[1]); + $fileinfo[1] = basename($fileinfo[1]); + $request .= "Content-Disposition: form-data; name=\"$fileinfo[0]\"; filename=\"$fileinfo[1]\"\n"; + $request .= "Content-Type: text/plain\n\n"; + $request .= $file_contents . "\n" . + "-----------------------------20896060251896012921717172737\n"; + } + + if (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) { + // encode POST raw + $post = trim($section_text['POST']); + $post = explode('&', $post); + foreach ($post as $i => $post_info) { + $post_info = explode('=', $post_info); + if (count($post_info) != 2) { + return PEAR::raiseError("Invalid POST data in test file: $file"); + } + $post_info[0] = rawurldecode($post_info[0]); + $post_info[1] = rawurldecode($post_info[1]); + $post[$i] = $post_info; + } + foreach ($post as $post_info) { + $request .= "Content-Disposition: form-data; name=\"$post_info[0]\"\n\n"; + $request .= $post_info[1] . "\n" . + "-----------------------------20896060251896012921717172737\n"; + } + unset($section_text['POST']); + } + $section_text['POST_RAW'] = $request; + } + + return $section_text; + } + + function _testCleanup($section_text, $temp_clean) + { + if ($section_text['CLEAN']) { + // perform test cleanup + $this->save_text($temp_clean, $section_text['CLEAN']); + $output = $this->system_with_timeout("$this->_php $temp_clean 2>&1"); + if (strlen($output[1])) { + echo "BORKED --CLEAN-- section! output:\n", $output[1]; + } + if (file_exists($temp_clean)) { + unlink($temp_clean); + } + } + } +}PEAR-1.9.0/PEAR/Validate.php100664 764 764 53055 100664 10327 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Validate.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/**#@+ + * Constants for install stage + */ +define('PEAR_VALIDATE_INSTALLING', 1); +define('PEAR_VALIDATE_UNINSTALLING', 2); // this is not bit-mapped like the others +define('PEAR_VALIDATE_NORMAL', 3); +define('PEAR_VALIDATE_DOWNLOADING', 4); // this is not bit-mapped like the others +define('PEAR_VALIDATE_PACKAGING', 7); +/**#@-*/ +require_once 'PEAR/Common.php'; +require_once 'PEAR/Validator/PECL.php'; + +/** + * Validation class for package.xml - channel-level advanced validation + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Validate +{ + var $packageregex = _PEAR_COMMON_PACKAGE_NAME_PREG; + /** + * @var PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + var $_packagexml; + /** + * @var int one of the PEAR_VALIDATE_* constants + */ + var $_state = PEAR_VALIDATE_NORMAL; + /** + * Format: ('error' => array('field' => name, 'reason' => reason), 'warning' => same) + * @var array + * @access private + */ + var $_failures = array('error' => array(), 'warning' => array()); + + /** + * Override this method to handle validation of normal package names + * @param string + * @return bool + * @access protected + */ + function _validPackageName($name) + { + return (bool) preg_match('/^' . $this->packageregex . '\\z/', $name); + } + + /** + * @param string package name to validate + * @param string name of channel-specific validation package + * @final + */ + function validPackageName($name, $validatepackagename = false) + { + if ($validatepackagename) { + if (strtolower($name) == strtolower($validatepackagename)) { + return (bool) preg_match('/^[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*\\z/', $name); + } + } + return $this->_validPackageName($name); + } + + /** + * This validates a bundle name, and bundle names must conform + * to the PEAR naming convention, so the method is final and static. + * @param string + * @final + * @static + */ + function validGroupName($name) + { + return (bool) preg_match('/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/', $name); + } + + /** + * Determine whether $state represents a valid stability level + * @param string + * @return bool + * @static + * @final + */ + function validState($state) + { + return in_array($state, array('snapshot', 'devel', 'alpha', 'beta', 'stable')); + } + + /** + * Get a list of valid stability levels + * @return array + * @static + * @final + */ + function getValidStates() + { + return array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + } + + /** + * Determine whether a version is a properly formatted version number that can be used + * by version_compare + * @param string + * @return bool + * @static + * @final + */ + function validVersion($ver) + { + return (bool) preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver); + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function setPackageFile(&$pf) + { + $this->_packagexml = &$pf; + } + + /** + * @access private + */ + function _addFailure($field, $reason) + { + $this->_failures['errors'][] = array('field' => $field, 'reason' => $reason); + } + + /** + * @access private + */ + function _addWarning($field, $reason) + { + $this->_failures['warnings'][] = array('field' => $field, 'reason' => $reason); + } + + function getFailures() + { + $failures = $this->_failures; + $this->_failures = array('warnings' => array(), 'errors' => array()); + return $failures; + } + + /** + * @param int one of the PEAR_VALIDATE_* constants + */ + function validate($state = null) + { + if (!isset($this->_packagexml)) { + return false; + } + if ($state !== null) { + $this->_state = $state; + } + $this->_failures = array('warnings' => array(), 'errors' => array()); + $this->validatePackageName(); + $this->validateVersion(); + $this->validateMaintainers(); + $this->validateDate(); + $this->validateSummary(); + $this->validateDescription(); + $this->validateLicense(); + $this->validateNotes(); + if ($this->_packagexml->getPackagexmlVersion() == '1.0') { + $this->validateState(); + $this->validateFilelist(); + } elseif ($this->_packagexml->getPackagexmlVersion() == '2.0' || + $this->_packagexml->getPackagexmlVersion() == '2.1') { + $this->validateTime(); + $this->validateStability(); + $this->validateDeps(); + $this->validateMainFilelist(); + $this->validateReleaseFilelist(); + //$this->validateGlobalTasks(); + $this->validateChangelog(); + } + return !((bool) count($this->_failures['errors'])); + } + + /** + * @access protected + */ + function validatePackageName() + { + if ($this->_state == PEAR_VALIDATE_PACKAGING || + $this->_state == PEAR_VALIDATE_NORMAL) { + if (($this->_packagexml->getPackagexmlVersion() == '2.0' || + $this->_packagexml->getPackagexmlVersion() == '2.1') && + $this->_packagexml->getExtends()) { + $version = $this->_packagexml->getVersion() . ''; + $name = $this->_packagexml->getPackage(); + $test = array_shift($a = explode('.', $version)); + if ($test == '0') { + return true; + } + $vlen = strlen($test); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if ($majver != $test) { + $this->_addWarning('package', "package $name extends package " . + $this->_packagexml->getExtends() . ' and so the name should ' . + 'have a postfix equal to the major version like "' . + $this->_packagexml->getExtends() . $test . '"'); + return true; + } elseif (substr($name, 0, strlen($name) - $vlen) != + $this->_packagexml->getExtends()) { + $this->_addWarning('package', "package $name extends package " . + $this->_packagexml->getExtends() . ' and so the name must ' . + 'be an extension like "' . $this->_packagexml->getExtends() . + $test . '"'); + return true; + } + } + } + if (!$this->validPackageName($this->_packagexml->getPackage())) { + $this->_addFailure('name', 'package name "' . + $this->_packagexml->getPackage() . '" is invalid'); + return false; + } + } + + /** + * @access protected + */ + function validateVersion() + { + if ($this->_state != PEAR_VALIDATE_PACKAGING) { + if (!$this->validVersion($this->_packagexml->getVersion())) { + $this->_addFailure('version', + 'Invalid version number "' . $this->_packagexml->getVersion() . '"'); + } + return false; + } + $version = $this->_packagexml->getVersion(); + $versioncomponents = explode('.', $version); + if (count($versioncomponents) != 3) { + $this->_addWarning('version', + 'A version number should have 3 decimals (x.y.z)'); + return true; + } + $name = $this->_packagexml->getPackage(); + // version must be based upon state + switch ($this->_packagexml->getState()) { + case 'snapshot' : + return true; + case 'devel' : + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } else { + $this->_addWarning('version', + 'packages with devel stability must be < version 1.0.0'); + } + return true; + break; + case 'alpha' : + case 'beta' : + // check for a package that extends a package, + // like Foo and Foo2 + if ($this->_state == PEAR_VALIDATE_PACKAGING) { + if (substr($versioncomponents[2], 1, 2) == 'rc') { + $this->_addFailure('version', 'Release Candidate versions ' . + 'must have capital RC, not lower-case rc'); + return false; + } + } + if (!$this->_packagexml->getExtends()) { + if ($versioncomponents[0] == '1') { + if ($versioncomponents[2]{0} == '0') { + if ($versioncomponents[2] == '0') { + // version 1.*.0000 + $this->_addWarning('version', + 'version 1.' . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return true; + } elseif (strlen($versioncomponents[2]) > 1) { + // version 1.*.0RC1 or 1.*.0beta24 etc. + return true; + } else { + // version 1.*.0 + $this->_addWarning('version', + 'version 1.' . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return true; + } + } else { + $this->_addWarning('version', + 'bugfix versions (1.3.x where x > 0) probably should ' . + 'not be alpha or beta'); + return true; + } + } elseif ($versioncomponents[0] != '0') { + $this->_addWarning('version', + 'major versions greater than 1 are not allowed for packages ' . + 'without an tag or an identical postfix (foo2 v2.0.0)'); + return true; + } + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } + } else { + $vlen = strlen($versioncomponents[0] . ''); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) { + $this->_addWarning('version', 'first version number "' . + $versioncomponents[0] . '" must match the postfix of ' . + 'package name "' . $name . '" (' . + $majver . ')'); + return true; + } + if ($versioncomponents[0] == $majver) { + if ($versioncomponents[2]{0} == '0') { + if ($versioncomponents[2] == '0') { + // version 2.*.0000 + $this->_addWarning('version', + "version $majver." . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return false; + } elseif (strlen($versioncomponents[2]) > 1) { + // version 2.*.0RC1 or 2.*.0beta24 etc. + return true; + } else { + // version 2.*.0 + $this->_addWarning('version', + "version $majver." . $versioncomponents[1] . + '.0 cannot be alpha or beta'); + return true; + } + } else { + $this->_addWarning('version', + "bugfix versions ($majver.x.y where y > 0) should " . + 'not be alpha or beta'); + return true; + } + } elseif ($versioncomponents[0] != '0') { + $this->_addWarning('version', + "only versions 0.x.y and $majver.x.y are allowed for alpha/beta releases"); + return true; + } + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } + } + return true; + break; + case 'stable' : + if ($versioncomponents[0] == '0') { + $this->_addWarning('version', 'versions less than 1.0.0 cannot ' . + 'be stable'); + return true; + } + if (!is_numeric($versioncomponents[2])) { + if (preg_match('/\d+(rc|a|alpha|b|beta)\d*/i', + $versioncomponents[2])) { + $this->_addWarning('version', 'version "' . $version . '" or any ' . + 'RC/beta/alpha version cannot be stable'); + return true; + } + } + // check for a package that extends a package, + // like Foo and Foo2 + if ($this->_packagexml->getExtends()) { + $vlen = strlen($versioncomponents[0] . ''); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) { + $this->_addWarning('version', 'first version number "' . + $versioncomponents[0] . '" must match the postfix of ' . + 'package name "' . $name . '" (' . + $majver . ')'); + return true; + } + } elseif ($versioncomponents[0] > 1) { + $this->_addWarning('version', 'major version x in x.y.z may not be greater than ' . + '1 for any package that does not have an tag'); + } + return true; + break; + default : + return false; + break; + } + } + + /** + * @access protected + */ + function validateMaintainers() + { + // maintainers can only be truly validated server-side for most channels + // but allow this customization for those who wish it + return true; + } + + /** + * @access protected + */ + function validateDate() + { + if ($this->_state == PEAR_VALIDATE_NORMAL || + $this->_state == PEAR_VALIDATE_PACKAGING) { + + if (!preg_match('/(\d\d\d\d)\-(\d\d)\-(\d\d)/', + $this->_packagexml->getDate(), $res) || + count($res) < 4 + || !checkdate($res[2], $res[3], $res[1]) + ) { + $this->_addFailure('date', 'invalid release date "' . + $this->_packagexml->getDate() . '"'); + return false; + } + + if ($this->_state == PEAR_VALIDATE_PACKAGING && + $this->_packagexml->getDate() != date('Y-m-d')) { + $this->_addWarning('date', 'Release Date "' . + $this->_packagexml->getDate() . '" is not today'); + } + } + return true; + } + + /** + * @access protected + */ + function validateTime() + { + if (!$this->_packagexml->getTime()) { + // default of no time value set + return true; + } + + // packager automatically sets time, so only validate if pear validate is called + if ($this->_state = PEAR_VALIDATE_NORMAL) { + if (!preg_match('/\d\d:\d\d:\d\d/', + $this->_packagexml->getTime())) { + $this->_addFailure('time', 'invalid release time "' . + $this->_packagexml->getTime() . '"'); + return false; + } + + $result = preg_match('|\d{2}\:\d{2}\:\d{2}|', $this->_packagexml->getTime(), $matches); + if ($result === false || empty($matches)) { + $this->_addFailure('time', 'invalid release time "' . + $this->_packagexml->getTime() . '"'); + return false; + } + } + + return true; + } + + /** + * @access protected + */ + function validateState() + { + // this is the closest to "final" php4 can get + if (!PEAR_Validate::validState($this->_packagexml->getState())) { + if (strtolower($this->_packagexml->getState() == 'rc')) { + $this->_addFailure('state', 'RC is not a state, it is a version ' . + 'postfix, use ' . $this->_packagexml->getVersion() . 'RC1, state beta'); + } + $this->_addFailure('state', 'invalid release state "' . + $this->_packagexml->getState() . '", must be one of: ' . + implode(', ', PEAR_Validate::getValidStates())); + return false; + } + return true; + } + + /** + * @access protected + */ + function validateStability() + { + $ret = true; + $packagestability = $this->_packagexml->getState(); + $apistability = $this->_packagexml->getState('api'); + if (!PEAR_Validate::validState($packagestability)) { + $this->_addFailure('state', 'invalid release stability "' . + $this->_packagexml->getState() . '", must be one of: ' . + implode(', ', PEAR_Validate::getValidStates())); + $ret = false; + } + $apistates = PEAR_Validate::getValidStates(); + array_shift($apistates); // snapshot is not allowed + if (!in_array($apistability, $apistates)) { + $this->_addFailure('state', 'invalid API stability "' . + $this->_packagexml->getState('api') . '", must be one of: ' . + implode(', ', $apistates)); + $ret = false; + } + return $ret; + } + + /** + * @access protected + */ + function validateSummary() + { + return true; + } + + /** + * @access protected + */ + function validateDescription() + { + return true; + } + + /** + * @access protected + */ + function validateLicense() + { + return true; + } + + /** + * @access protected + */ + function validateNotes() + { + return true; + } + + /** + * for package.xml 2.0 only - channels can't use package.xml 1.0 + * @access protected + */ + function validateDependencies() + { + return true; + } + + /** + * for package.xml 1.0 only + * @access private + */ + function _validateFilelist() + { + return true; // placeholder for now + } + + /** + * for package.xml 2.0 only + * @access protected + */ + function validateMainFilelist() + { + return true; // placeholder for now + } + + /** + * for package.xml 2.0 only + * @access protected + */ + function validateReleaseFilelist() + { + return true; // placeholder for now + } + + /** + * @access protected + */ + function validateChangelog() + { + return true; + } + + /** + * @access protected + */ + function validateFilelist() + { + return true; + } + + /** + * @access protected + */ + function validateDeps() + { + return true; + } +}PEAR-1.9.0/PEAR/XMLParser.php100664 764 764 16001 100664 10401 + * @author Stephan Schmidt (original XML_Unserializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: XMLParser.php 282970 2009-06-28 23:10:07Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Parser for any xml file + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Stephan Schmidt (original XML_Unserializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_XMLParser +{ + /** + * unserilialized data + * @var string $_serializedData + */ + var $_unserializedData = null; + + /** + * name of the root tag + * @var string $_root + */ + var $_root = null; + + /** + * stack for all data that is found + * @var array $_dataStack + */ + var $_dataStack = array(); + + /** + * stack for all values that are generated + * @var array $_valStack + */ + var $_valStack = array(); + + /** + * current tag depth + * @var int $_depth + */ + var $_depth = 0; + + /** + * The XML encoding to use + * @var string $encoding + */ + var $encoding = 'ISO-8859-1'; + + /** + * @return array + */ + function getData() + { + return $this->_unserializedData; + } + + /** + * @param string xml content + * @return true|PEAR_Error + */ + function parse($data) + { + if (!extension_loaded('xml')) { + include_once 'PEAR.php'; + return PEAR::raiseError("XML Extension not found", 1); + } + $this->_dataStack = $this->_valStack = array(); + $this->_depth = 0; + + if ( + strpos($data, 'encoding="UTF-8"') + || strpos($data, 'encoding="utf-8"') + || strpos($data, "encoding='UTF-8'") + || strpos($data, "encoding='utf-8'") + ) { + $this->encoding = 'UTF-8'; + } + + if (version_compare(phpversion(), '5.0.0', 'lt') && $this->encoding == 'UTF-8') { + $data = utf8_decode($data); + $this->encoding = 'ISO-8859-1'; + } + + $xp = xml_parser_create($this->encoding); + xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, 0); + xml_set_object($xp, $this); + xml_set_element_handler($xp, 'startHandler', 'endHandler'); + xml_set_character_data_handler($xp, 'cdataHandler'); + if (!xml_parse($xp, $data)) { + $msg = xml_error_string(xml_get_error_code($xp)); + $line = xml_get_current_line_number($xp); + xml_parser_free($xp); + include_once 'PEAR.php'; + return PEAR::raiseError("XML Error: '$msg' on line '$line'", 2); + } + xml_parser_free($xp); + return true; + } + + /** + * Start element handler for XML parser + * + * @access private + * @param object $parser XML parser object + * @param string $element XML element + * @param array $attribs attributes of XML tag + * @return void + */ + function startHandler($parser, $element, $attribs) + { + $this->_depth++; + $this->_dataStack[$this->_depth] = null; + + $val = array( + 'name' => $element, + 'value' => null, + 'type' => 'string', + 'childrenKeys' => array(), + 'aggregKeys' => array() + ); + + if (count($attribs) > 0) { + $val['children'] = array(); + $val['type'] = 'array'; + $val['children']['attribs'] = $attribs; + } + + array_push($this->_valStack, $val); + } + + /** + * post-process data + * + * @param string $data + * @param string $element element name + */ + function postProcess($data, $element) + { + return trim($data); + } + + /** + * End element handler for XML parser + * + * @access private + * @param object XML parser object + * @param string + * @return void + */ + function endHandler($parser, $element) + { + $value = array_pop($this->_valStack); + $data = $this->postProcess($this->_dataStack[$this->_depth], $element); + + // adjust type of the value + switch (strtolower($value['type'])) { + // unserialize an array + case 'array': + if ($data !== '') { + $value['children']['_content'] = $data; + } + + $value['value'] = isset($value['children']) ? $value['children'] : array(); + break; + + /* + * unserialize a null value + */ + case 'null': + $data = null; + break; + + /* + * unserialize any scalar value + */ + default: + settype($data, $value['type']); + $value['value'] = $data; + break; + } + + $parent = array_pop($this->_valStack); + if ($parent === null) { + $this->_unserializedData = &$value['value']; + $this->_root = &$value['name']; + return true; + } + + // parent has to be an array + if (!isset($parent['children']) || !is_array($parent['children'])) { + $parent['children'] = array(); + if ($parent['type'] != 'array') { + $parent['type'] = 'array'; + } + } + + if (!empty($value['name'])) { + // there already has been a tag with this name + if (in_array($value['name'], $parent['childrenKeys'])) { + // no aggregate has been created for this tag + if (!in_array($value['name'], $parent['aggregKeys'])) { + if (isset($parent['children'][$value['name']])) { + $parent['children'][$value['name']] = array($parent['children'][$value['name']]); + } else { + $parent['children'][$value['name']] = array(); + } + array_push($parent['aggregKeys'], $value['name']); + } + array_push($parent['children'][$value['name']], $value['value']); + } else { + $parent['children'][$value['name']] = &$value['value']; + array_push($parent['childrenKeys'], $value['name']); + } + } else { + array_push($parent['children'],$value['value']); + } + array_push($this->_valStack, $parent); + + $this->_depth--; + } + + /** + * Handler for character data + * + * @access private + * @param object XML parser object + * @param string CDATA + * @return void + */ + function cdataHandler($parser, $cdata) + { + $this->_dataStack[$this->_depth] .= $cdata; + } +}PEAR-1.9.0/scripts/pear.bat100775 764 764 11102 100775 10435 @ECHO OFF + +REM ---------------------------------------------------------------------- +REM PHP version 5 +REM ---------------------------------------------------------------------- +REM Copyright (c) 1997-2004 The PHP Group +REM ---------------------------------------------------------------------- +REM This source file is subject to version 3.0 of the PHP license, +REM that is bundled with this package in the file LICENSE, and is +REM available at through the world-wide-web at +REM http://www.php.net/license/3_0.txt. +REM If you did not receive a copy of the PHP license and are unable to +REM obtain it through the world-wide-web, please send a note to +REM license@php.net so we can mail you a copy immediately. +REM ---------------------------------------------------------------------- +REM Authors: Alexander Merz (alexmerz@php.net) +REM ---------------------------------------------------------------------- +REM +REM Last updated 12/29/2004 ($Id$ is not replaced if the file is binary) + +REM change this lines to match the paths of your system +REM ------------------- + + +REM Test to see if this is a raw pear.bat (uninstalled version) +SET TMPTMPTMPTMPT=@includ +SET PMTPMTPMT=%TMPTMPTMPTMPT%e_path@ +FOR %%x IN ("@include_path@") DO (if %%x=="%PMTPMTPMT%" GOTO :NOTINSTALLED) + +REM Check PEAR global ENV, set them if they do not exist +IF "%PHP_PEAR_INSTALL_DIR%"=="" SET "PHP_PEAR_INSTALL_DIR=@include_path@" +IF "%PHP_PEAR_BIN_DIR%"=="" SET "PHP_PEAR_BIN_DIR=@bin_dir@" +IF "%PHP_PEAR_PHP_BIN%"=="" SET "PHP_PEAR_PHP_BIN=@php_bin@" +GOTO :INSTALLED + +:NOTINSTALLED +ECHO WARNING: This is a raw, uninstalled pear.bat + +REM Check to see if we can grab the directory of this file (Windows NT+) +IF %~n0 == pear ( +FOR %%x IN (cli\php.exe php.exe) DO (if "%%~$PATH:x" NEQ "" ( +SET "PHP_PEAR_PHP_BIN=%%~$PATH:x" +echo Using PHP Executable "%PHP_PEAR_PHP_BIN%" +"%PHP_PEAR_PHP_BIN%" -v +GOTO :NEXTTEST +)) +GOTO :FAILAUTODETECT +:NEXTTEST +IF "%PHP_PEAR_PHP_BIN%" NEQ "" ( + +REM We can use this PHP to run a temporary php file to get the dirname of pear + +echo ^ > ~~getloc.php +"%PHP_PEAR_PHP_BIN%" ~~getloc.php +set /p PHP_PEAR_BIN_DIR=fakeprompt < ~a.a +DEL ~a.a +DEL ~~getloc.php +set "PHP_PEAR_INSTALL_DIR=%PHP_PEAR_BIN_DIR%pear" + +REM Make sure there is a pearcmd.php at our disposal + +IF NOT EXIST %PHP_PEAR_INSTALL_DIR%\pearcmd.php ( +IF EXIST %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php COPY %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +IF EXIST pearcmd.php COPY pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +IF EXIST %~dp0\scripts\pearcmd.php COPY %~dp0\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +) +) +GOTO :INSTALLED +) ELSE ( +REM Windows Me/98 cannot succeed, so allow the batch to fail +) +:FAILAUTODETECT +echo WARNING: failed to auto-detect pear information +:INSTALLED + +REM Check Folders and files +IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%" GOTO PEAR_INSTALL_ERROR +IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" GOTO PEAR_INSTALL_ERROR2 +IF NOT EXIST "%PHP_PEAR_BIN_DIR%" GOTO PEAR_BIN_ERROR +IF NOT EXIST "%PHP_PEAR_PHP_BIN%" GOTO PEAR_PHPBIN_ERROR +REM launch pearcmd +GOTO RUN +:PEAR_INSTALL_ERROR +ECHO PHP_PEAR_INSTALL_DIR is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_INSTALL_DIR% +GOTO END +:PEAR_INSTALL_ERROR2 +ECHO PHP_PEAR_INSTALL_DIR is not set correctly. +ECHO pearcmd.php could not be found there. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_INSTALL_DIR% +GOTO END +:PEAR_BIN_ERROR +ECHO PHP_PEAR_BIN_DIR is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_BIN_DIR% +GOTO END +:PEAR_PHPBIN_ERROR +ECHO PHP_PEAR_PHP_BIN is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_PHP_BIN% +GOTO END +:RUN +"%PHP_PEAR_PHP_BIN%" -C -d output_buffering=1 -d safe_mode=0 -d open_basedir="" -d auto_prepend_file="" -d auto_append_file="" -d variables_order=EGPCS -d register_argc_argv="On" -d "include_path='%PHP_PEAR_INSTALL_DIR%'" -f "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" -- %1 %2 %3 %4 %5 %6 %7 %8 %9 +:END +@ECHO ONPEAR-1.9.0/scripts/peardev.bat100664 764 764 11112 100664 11127 @ECHO OFF + +REM ---------------------------------------------------------------------- +REM PHP version 5 +REM ---------------------------------------------------------------------- +REM Copyright (c) 1997-2004 The PHP Group +REM ---------------------------------------------------------------------- +REM This source file is subject to version 3.0 of the PHP license, +REM that is bundled with this package in the file LICENSE, and is +REM available at through the world-wide-web at +REM http://www.php.net/license/3_0.txt. +REM If you did not receive a copy of the PHP license and are unable to +REM obtain it through the world-wide-web, please send a note to +REM license@php.net so we can mail you a copy immediately. +REM ---------------------------------------------------------------------- +REM Authors: Alexander Merz (alexmerz@php.net) +REM ---------------------------------------------------------------------- +REM +REM $Id: peardev.bat,v 1.6 2007-09-03 03:00:17 cellog Exp $ + +REM change this lines to match the paths of your system +REM ------------------- + + +REM Test to see if this is a raw pear.bat (uninstalled version) +SET TMPTMPTMPTMPT=@includ +SET PMTPMTPMT=%TMPTMPTMPTMPT%e_path@ +FOR %%x IN ("@include_path@") DO (if %%x=="%PMTPMTPMT%" GOTO :NOTINSTALLED) + +REM Check PEAR global ENV, set them if they do not exist +IF "%PHP_PEAR_INSTALL_DIR%"=="" SET "PHP_PEAR_INSTALL_DIR=@include_path@" +IF "%PHP_PEAR_BIN_DIR%"=="" SET "PHP_PEAR_BIN_DIR=@bin_dir@" +IF "%PHP_PEAR_PHP_BIN%"=="" SET "PHP_PEAR_PHP_BIN=@php_bin@" +GOTO :INSTALLED + +:NOTINSTALLED +ECHO WARNING: This is a raw, uninstalled pear.bat + +REM Check to see if we can grab the directory of this file (Windows NT+) +IF %~n0 == pear ( +FOR %%x IN (cli\php.exe php.exe) DO (if "%%~$PATH:x" NEQ "" ( +SET "PHP_PEAR_PHP_BIN=%%~$PATH:x" +echo Using PHP Executable "%PHP_PEAR_PHP_BIN%" +"%PHP_PEAR_PHP_BIN%" -v +GOTO :NEXTTEST +)) +GOTO :FAILAUTODETECT +:NEXTTEST +IF "%PHP_PEAR_PHP_BIN%" NEQ "" ( + +REM We can use this PHP to run a temporary php file to get the dirname of pear + +echo ^ > ~~getloc.php +"%PHP_PEAR_PHP_BIN%" ~~getloc.php +set /p PHP_PEAR_BIN_DIR=fakeprompt < ~a.a +DEL ~a.a +DEL ~~getloc.php +set "PHP_PEAR_INSTALL_DIR=%PHP_PEAR_BIN_DIR%pear" + +REM Make sure there is a pearcmd.php at our disposal + +IF NOT EXIST %PHP_PEAR_INSTALL_DIR%\pearcmd.php ( +IF EXIST %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php COPY %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +IF EXIST pearcmd.php COPY pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +IF EXIST %~dp0\scripts\pearcmd.php COPY %~dp0\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +) +) +GOTO :INSTALLED +) ELSE ( +REM Windows Me/98 cannot succeed, so allow the batch to fail +) +:FAILAUTODETECT +echo WARNING: failed to auto-detect pear information +:INSTALLED + +REM Check Folders and files +IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%" GOTO PEAR_INSTALL_ERROR +IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" GOTO PEAR_INSTALL_ERROR2 +IF NOT EXIST "%PHP_PEAR_BIN_DIR%" GOTO PEAR_BIN_ERROR +IF NOT EXIST "%PHP_PEAR_PHP_BIN%" GOTO PEAR_PHPBIN_ERROR +REM launch pearcmd +GOTO RUN +:PEAR_INSTALL_ERROR +ECHO PHP_PEAR_INSTALL_DIR is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_INSTALL_DIR% +GOTO END +:PEAR_INSTALL_ERROR2 +ECHO PHP_PEAR_INSTALL_DIR is not set correctly. +ECHO pearcmd.php could not be found there. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_INSTALL_DIR% +GOTO END +:PEAR_BIN_ERROR +ECHO PHP_PEAR_BIN_DIR is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_BIN_DIR% +GOTO END +:PEAR_PHPBIN_ERROR +ECHO PHP_PEAR_PHP_BIN is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_PHP_BIN% +GOTO END +:RUN +"%PHP_PEAR_PHP_BIN%" -C -d memory_limit="-1" -d safe_mode=0 -d register_argc_argv="On" -d auto_prepend_file="" -d auto_append_file="" -d variables_order=EGPCS -d open_basedir="" -d output_buffering=1 -d include_path="%PHP_PEAR_INSTALL_DIR%" -f "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" -- %1 %2 %3 %4 %5 %6 %7 %8 %9 +:END +@ECHO ONPEAR-1.9.0/scripts/pecl.bat100664 764 764 11003 100664 10423 @ECHO OFF + +REM ---------------------------------------------------------------------- +REM PHP version 5 +REM ---------------------------------------------------------------------- +REM Copyright (c) 1997-2004 The PHP Group +REM ---------------------------------------------------------------------- +REM This source file is subject to version 3.0 of the PHP license, +REM that is bundled with this package in the file LICENSE, and is +REM available at through the world-wide-web at +REM http://www.php.net/license/3_0.txt. +REM If you did not receive a copy of the PHP license and are unable to +REM obtain it through the world-wide-web, please send a note to +REM license@php.net so we can mail you a copy immediately. +REM ---------------------------------------------------------------------- +REM Authors: Alexander Merz (alexmerz@php.net) +REM ---------------------------------------------------------------------- +REM +REM Last updated 02/08/2004 ($Id$ is not replaced if the file is binary) + +REM change this lines to match the paths of your system +REM ------------------- + + +REM Test to see if this is a raw pear.bat (uninstalled version) +SET TMPTMPTMPTMPT=@includ +SET PMTPMTPMT=%TMPTMPTMPTMPT%e_path@ +FOR %%x IN ("@include_path@") DO (if %%x=="%PMTPMTPMT%" GOTO :NOTINSTALLED) + +REM Check PEAR global ENV, set them if they do not exist +IF "%PHP_PEAR_INSTALL_DIR%"=="" SET "PHP_PEAR_INSTALL_DIR=@include_path@" +IF "%PHP_PEAR_BIN_DIR%"=="" SET "PHP_PEAR_BIN_DIR=@bin_dir@" +IF "%PHP_PEAR_PHP_BIN%"=="" SET "PHP_PEAR_PHP_BIN=@php_bin@" +GOTO :INSTALLED + +:NOTINSTALLED +ECHO WARNING: This is a raw, uninstalled pear.bat + +REM Check to see if we can grab the directory of this file (Windows NT+) +IF %~n0 == pear ( +FOR %%x IN (cli\php.exe php.exe) DO (if "%%~$PATH:x" NEQ "" ( +SET "PHP_PEAR_PHP_BIN=%%~$PATH:x" +echo Using PHP Executable "%PHP_PEAR_PHP_BIN%" +"%PHP_PEAR_PHP_BIN%" -v +GOTO :NEXTTEST +)) +GOTO :FAILAUTODETECT +:NEXTTEST +IF "%PHP_PEAR_PHP_BIN%" NEQ "" ( + +REM We can use this PHP to run a temporary php file to get the dirname of pear + +echo ^ > ~~getloc.php +"%PHP_PEAR_PHP_BIN%" ~~getloc.php +set /p PHP_PEAR_BIN_DIR=fakeprompt < ~a.a +DEL ~a.a +DEL ~~getloc.php +set "PHP_PEAR_INSTALL_DIR=%PHP_PEAR_BIN_DIR%pear" + +REM Make sure there is a pearcmd.php at our disposal + +IF NOT EXIST %PHP_PEAR_INSTALL_DIR%\pearcmd.php ( +IF EXIST %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php COPY %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +IF EXIST pearcmd.php COPY pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +IF EXIST %~dp0\scripts\pearcmd.php COPY %~dp0\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +) +) +GOTO :INSTALLED +) ELSE ( +REM Windows Me/98 cannot succeed, so allow the batch to fail +) +:FAILAUTODETECT +echo WARNING: failed to auto-detect pear information +:INSTALLED + +REM Check Folders and files +IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%" GOTO PEAR_INSTALL_ERROR +IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" GOTO PEAR_INSTALL_ERROR2 +IF NOT EXIST "%PHP_PEAR_BIN_DIR%" GOTO PEAR_BIN_ERROR +IF NOT EXIST "%PHP_PEAR_PHP_BIN%" GOTO PEAR_PHPBIN_ERROR +REM launch pearcmd +GOTO RUN +:PEAR_INSTALL_ERROR +ECHO PHP_PEAR_INSTALL_DIR is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_INSTALL_DIR% +GOTO END +:PEAR_INSTALL_ERROR2 +ECHO PHP_PEAR_INSTALL_DIR is not set correctly. +ECHO pearcmd.php could not be found there. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_INSTALL_DIR% +GOTO END +:PEAR_BIN_ERROR +ECHO PHP_PEAR_BIN_DIR is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_BIN_DIR% +GOTO END +:PEAR_PHPBIN_ERROR +ECHO PHP_PEAR_PHP_BIN is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_PHP_BIN% +GOTO END +:RUN +"%PHP_PEAR_PHP_BIN%" -C -n -d output_buffering=1 -d safe_mode=0 -d include_path="%PHP_PEAR_INSTALL_DIR%" -d register_argc_argv="On" -d variables_order=EGPCS -f "%PHP_PEAR_INSTALL_DIR%\peclcmd.php" -- %1 %2 %3 %4 %5 %6 %7 %8 %9 +:END +@ECHO ONPEAR-1.9.0/scripts/pear.sh100664 764 764 1362 100664 10262 #!/bin/sh + +# first find which PHP binary to use +if test "x$PHP_PEAR_PHP_BIN" != "x"; then + PHP="$PHP_PEAR_PHP_BIN" +else + if test "@php_bin@" = '@'php_bin'@'; then + PHP=php + else + PHP="@php_bin@" + fi +fi + +# then look for the right pear include dir +if test "x$PHP_PEAR_INSTALL_DIR" != "x"; then + INCDIR=$PHP_PEAR_INSTALL_DIR + INCARG="-d include_path=$PHP_PEAR_INSTALL_DIR" +else + if test "@php_dir@" = '@'php_dir'@'; then + INCDIR=`dirname $0` + INCARG="" + else + INCDIR="@php_dir@" + INCARG="-d include_path=@php_dir@" + fi +fi + +exec $PHP -C -q $INCARG -d output_buffering=1 -d variables_order=EGPCS -d open_basedir="" -d safe_mode=0 -d register_argc_argv="On" -d auto_prepend_file="" -d auto_append_file="" $INCDIR/pearcmd.php "$@" +PEAR-1.9.0/scripts/peardev.sh100664 764 764 1407 100664 10761 #!/bin/sh + +# first find which PHP binary to use +if test "x$PHP_PEAR_PHP_BIN" != "x"; then + PHP="$PHP_PEAR_PHP_BIN" +else + if test "@php_bin@" = '@'php_bin'@'; then + PHP=php + else + PHP="@php_bin@" + fi +fi + +# then look for the right pear include dir +if test "x$PHP_PEAR_INSTALL_DIR" != "x"; then + INCDIR=$PHP_PEAR_INSTALL_DIR + INCARG="-d include_path=$PHP_PEAR_INSTALL_DIR" +else + if test "@php_dir@" = '@'php_dir'@'; then + INCDIR=`dirname $0` + INCARG="" + else + INCDIR="@php_dir@" + INCARG="-d include_path=@php_dir@" + fi +fi + +exec $PHP -d memory_limit="-1" -C -q $INCARG -d output_buffering=1 -d open_basedir="" -d safe_mode=0 -d register_argc_argv="On" -d auto_prepend_file="" -d variables_order=EGPCS -d auto_append_file="" $INCDIR/pearcmd.php "$@" +PEAR-1.9.0/scripts/pecl.sh100664 764 764 1263 100664 10256 #!/bin/sh + +# first find which PHP binary to use +if test "x$PHP_PEAR_PHP_BIN" != "x"; then + PHP="$PHP_PEAR_PHP_BIN" +else + if test "@php_bin@" = '@'php_bin'@'; then + PHP=php + else + PHP="@php_bin@" + fi +fi + +# then look for the right pear include dir +if test "x$PHP_PEAR_INSTALL_DIR" != "x"; then + INCDIR=$PHP_PEAR_INSTALL_DIR + INCARG="-d include_path=$PHP_PEAR_INSTALL_DIR" +else + if test "@php_dir@" = '@'php_dir'@'; then + INCDIR=`dirname $0` + INCARG="" + else + INCDIR="@php_dir@" + INCARG="-d include_path=@php_dir@" + fi +fi + +exec $PHP -C -n -q $INCARG -d output_buffering=1 -d variables_order=EGPCS -d safe_mode=0 -d register_argc_argv="On" $INCDIR/peclcmd.php "$@" +PEAR-1.9.0/scripts/pearcmd.php100664 764 764 34076 100664 11153 + * @author Tomas V.V.Cox + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: pearcmd.php 286487 2009-07-29 05:57:28Z dufuz $ + * @link http://pear.php.net/package/PEAR + */ + +ob_end_clean(); +if (!defined('PEAR_RUNTYPE')) { + // this is defined in peclcmd.php as 'pecl' + define('PEAR_RUNTYPE', 'pear'); +} +define('PEAR_IGNORE_BACKTRACE', 1); +/** + * @nodep Gtk + */ +if ('@include_path@' != '@'.'include_path'.'@') { + ini_set('include_path', '@include_path@'); + $raw = false; +} else { + // this is a raw, uninstalled pear, either a cvs checkout, or php distro + $raw = true; +} +@ini_set('allow_url_fopen', true); +if (!ini_get('safe_mode')) { + @set_time_limit(0); +} +ob_implicit_flush(true); +@ini_set('track_errors', true); +@ini_set('html_errors', false); +@ini_set('magic_quotes_runtime', false); +$_PEAR_PHPDIR = '#$%^&*'; +set_error_handler('error_handler'); + +$pear_package_version = "@pear_version@"; + +require_once 'PEAR.php'; +require_once 'PEAR/Frontend.php'; +require_once 'PEAR/Config.php'; +require_once 'PEAR/Command.php'; +require_once 'Console/Getopt.php'; + + +PEAR_Command::setFrontendType('CLI'); +$all_commands = PEAR_Command::getCommands(); + +// remove this next part when we stop supporting that crap-ass PHP 4.2 +if (!isset($_SERVER['argv']) && !isset($argv) && !isset($HTTP_SERVER_VARS['argv'])) { + echo 'ERROR: either use the CLI php executable, or set register_argc_argv=On in php.ini'; + exit(1); +} + +$argv = Console_Getopt::readPHPArgv(); +// fix CGI sapi oddity - the -- in pear.bat/pear is not removed +if (php_sapi_name() != 'cli' && isset($argv[1]) && $argv[1] == '--') { + unset($argv[1]); + $argv = array_values($argv); +} +$progname = PEAR_RUNTYPE; +array_shift($argv); +$options = Console_Getopt::getopt2($argv, "c:C:d:D:Gh?sSqu:vV"); +if (PEAR::isError($options)) { + usage($options); +} + +$opts = $options[0]; + +$fetype = 'CLI'; +if ($progname == 'gpear' || $progname == 'pear-gtk') { + $fetype = 'Gtk'; +} else { + foreach ($opts as $opt) { + if ($opt[0] == 'G') { + $fetype = 'Gtk'; + } + } +} +//Check if Gtk and PHP >= 5.1.0 +if ($fetype == 'Gtk' && version_compare(phpversion(), '5.1.0', '>=')) { + $fetype = 'Gtk2'; +} + +$pear_user_config = ''; +$pear_system_config = ''; +$store_user_config = false; +$store_system_config = false; +$verbose = 1; + +foreach ($opts as $opt) { + switch ($opt[0]) { + case 'c': + $pear_user_config = $opt[1]; + break; + case 'C': + $pear_system_config = $opt[1]; + break; + } +} + +PEAR_Command::setFrontendType($fetype); +$ui = &PEAR_Command::getFrontendObject(); +$config = &PEAR_Config::singleton($pear_user_config, $pear_system_config); + +if (PEAR::isError($config)) { + $_file = ''; + if ($pear_user_config !== false) { + $_file .= $pear_user_config; + } + if ($pear_system_config !== false) { + $_file .= '/' . $pear_system_config; + } + if ($_file == '/') { + $_file = 'The default config file'; + } + $config->getMessage(); + $ui->outputData("ERROR: $_file is not a valid config file or is corrupted."); + // We stop, we have no idea where we are :) + exit(1); +} + +// this is used in the error handler to retrieve a relative path +$_PEAR_PHPDIR = $config->get('php_dir'); +$ui->setConfig($config); +PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array($ui, "displayFatalError")); +if (ini_get('safe_mode')) { + $ui->outputData('WARNING: running in safe mode requires that all files created ' . + 'be the same uid as the current script. PHP reports this script is uid: ' . + @getmyuid() . ', and current user is: ' . @get_current_user()); +} + +$verbose = $config->get("verbose"); +$cmdopts = array(); + +if ($raw) { + if (!$config->isDefinedLayer('user') && !$config->isDefinedLayer('system')) { + $found = false; + foreach ($opts as $opt) { + if ($opt[0] == 'd' || $opt[0] == 'D') { + $found = true; // the user knows what they are doing, and are setting config values + } + } + if (!$found) { + // no prior runs, try to install PEAR + if (strpos(dirname(__FILE__), 'scripts')) { + $packagexml = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'package2.xml'; + $pearbase = dirname(dirname(__FILE__)); + } else { + $packagexml = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'package2.xml'; + $pearbase = dirname(__FILE__); + } + if (file_exists($packagexml)) { + $options[1] = array( + 'install', + $packagexml + ); + $config->set('php_dir', $pearbase . DIRECTORY_SEPARATOR . 'php'); + $config->set('data_dir', $pearbase . DIRECTORY_SEPARATOR . 'data'); + $config->set('doc_dir', $pearbase . DIRECTORY_SEPARATOR . 'docs'); + $config->set('test_dir', $pearbase . DIRECTORY_SEPARATOR . 'tests'); + $config->set('ext_dir', $pearbase . DIRECTORY_SEPARATOR . 'extensions'); + $config->set('bin_dir', $pearbase); + $config->mergeConfigFile($pearbase . 'pear.ini', false); + $config->store(); + $config->set('auto_discover', 1); + } + } + } +} +foreach ($opts as $opt) { + $param = !empty($opt[1]) ? $opt[1] : true; + switch ($opt[0]) { + case 'd': + if ($param === true) { + die('Invalid usage of "-d" option, expected -d config_value=value, ' . + 'received "-d"' . "\n"); + } + $possible = explode('=', $param); + if (count($possible) != 2) { + die('Invalid usage of "-d" option, expected -d config_value=value, received "' . + $param . '"' . "\n"); + } + list($key, $value) = explode('=', $param); + $config->set($key, $value, 'user'); + break; + case 'D': + if ($param === true) { + die('Invalid usage of "-d" option, expected -d config_value=value, ' . + 'received "-d"' . "\n"); + } + $possible = explode('=', $param); + if (count($possible) != 2) { + die('Invalid usage of "-d" option, expected -d config_value=value, received "' . + $param . '"' . "\n"); + } + list($key, $value) = explode('=', $param); + $config->set($key, $value, 'system'); + break; + case 's': + $store_user_config = true; + break; + case 'S': + $store_system_config = true; + break; + case 'u': + $config->remove($param, 'user'); + break; + case 'v': + $config->set('verbose', $config->get('verbose') + 1); + break; + case 'q': + $config->set('verbose', $config->get('verbose') - 1); + break; + case 'V': + usage(null, 'version'); + case 'c': + case 'C': + break; + default: + // all non pear params goes to the command + $cmdopts[$opt[0]] = $param; + break; + } +} + +if ($store_system_config) { + $config->store('system'); +} + +if ($store_user_config) { + $config->store('user'); +} + +$command = (isset($options[1][0])) ? $options[1][0] : null; +if (empty($command) && ($store_user_config || $store_system_config)) { + exit; +} + +if ($fetype == 'Gtk' || $fetype == 'Gtk2') { + if (!$config->validConfiguration()) { + PEAR::raiseError('CRITICAL ERROR: no existing valid configuration files found in files ' . + "'$pear_user_config' or '$pear_system_config', please copy an existing configuration" . + 'file to one of these locations, or use the -c and -s options to create one'); + } + Gtk::main(); +} else do { + if ($command == 'help') { + usage(null, @$options[1][1]); + } + + if (!$config->validConfiguration()) { + PEAR::raiseError('CRITICAL ERROR: no existing valid configuration files found in files ' . + "'$pear_user_config' or '$pear_system_config', please copy an existing configuration" . + 'file to one of these locations, or use the -c and -s options to create one'); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $cmd = PEAR_Command::factory($command, $config); + PEAR::popErrorHandling(); + if (PEAR::isError($cmd)) { + usage(null, @$options[1][0]); + } + + $short_args = $long_args = null; + PEAR_Command::getGetoptArgs($command, $short_args, $long_args); + array_shift($options[1]); + $tmp = Console_Getopt::getopt2($options[1], $short_args, $long_args); + + if (PEAR::isError($tmp)) { + break; + } + + list($tmpopt, $params) = $tmp; + $opts = array(); + foreach ($tmpopt as $foo => $tmp2) { + list($opt, $value) = $tmp2; + if ($value === null) { + $value = true; // options without args + } + + if (strlen($opt) == 1) { + $cmdoptions = $cmd->getOptions($command); + foreach ($cmdoptions as $o => $d) { + if (isset($d['shortopt']) && $d['shortopt'] == $opt) { + $opts[$o] = $value; + } + } + } else { + if (substr($opt, 0, 2) == '--') { + $opts[substr($opt, 2)] = $value; + } + } + } + + $ok = $cmd->run($command, $opts, $params); + if ($ok === false) { + PEAR::raiseError("unknown command `$command'"); + } + + if (PEAR::isError($ok)) { + PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array($ui, "displayFatalError")); + PEAR::raiseError($ok); + } +} while (false); + +// {{{ usage() + +function usage($error = null, $helpsubject = null) +{ + global $progname, $all_commands; + $stderr = fopen('php://stderr', 'w'); + if (PEAR::isError($error)) { + fputs($stderr, $error->getMessage() . "\n"); + } elseif ($error !== null) { + fputs($stderr, "$error\n"); + } + + if ($helpsubject != null) { + $put = cmdHelp($helpsubject); + } else { + $put = + "Commands:\n"; + $maxlen = max(array_map("strlen", $all_commands)); + $formatstr = "%-{$maxlen}s %s\n"; + ksort($all_commands); + foreach ($all_commands as $cmd => $class) { + $put .= sprintf($formatstr, $cmd, PEAR_Command::getDescription($cmd)); + } + $put .= + "Usage: $progname [options] command [command-options] \n". + "Type \"$progname help options\" to list all options.\n". + "Type \"$progname help shortcuts\" to list all command shortcuts.\n". + "Type \"$progname help \" to get the help for the specified command."; + } + fputs($stderr, "$put\n"); + fclose($stderr); + exit(1); +} + +function cmdHelp($command) +{ + global $progname, $all_commands, $config; + if ($command == "options") { + return + "Options:\n". + " -v increase verbosity level (default 1)\n". + " -q be quiet, decrease verbosity level\n". + " -c file find user configuration in `file'\n". + " -C file find system configuration in `file'\n". + " -d foo=bar set user config variable `foo' to `bar'\n". + " -D foo=bar set system config variable `foo' to `bar'\n". + " -G start in graphical (Gtk) mode\n". + " -s store user configuration\n". + " -S store system configuration\n". + " -u foo unset `foo' in the user configuration\n". + " -h, -? display help/usage (this message)\n". + " -V version information\n"; + } elseif ($command == "shortcuts") { + $sc = PEAR_Command::getShortcuts(); + $ret = "Shortcuts:\n"; + foreach ($sc as $s => $c) { + $ret .= sprintf(" %-8s %s\n", $s, $c); + } + return $ret; + + } elseif ($command == "version") { + return "PEAR Version: ".$GLOBALS['pear_package_version']. + "\nPHP Version: ".phpversion(). + "\nZend Engine Version: ".zend_version(). + "\nRunning on: ".php_uname(); + + } elseif ($help = PEAR_Command::getHelp($command)) { + if (is_string($help)) { + return "$progname $command [options] $help\n"; + } + + if ($help[1] === null) { + return "$progname $command $help[0]"; + } + + return "$progname $command [options] $help[0]\n$help[1]"; + } + + return "Command '$command' is not valid, try '$progname help'"; +} + +// }}} + +function error_handler($errno, $errmsg, $file, $line, $vars) { + if ((defined('E_STRICT') && $errno & E_STRICT) || (defined('E_DEPRECATED') && + $errno & E_DEPRECATED) || !error_reporting()) { + if (defined('E_STRICT') && $errno & E_STRICT) { + return; // E_STRICT + } + if (defined('E_DEPRECATED') && $errno & E_DEPRECATED) { + return; // E_DEPRECATED + } + if ($GLOBALS['config']->get('verbose') < 4) { + return false; // @silenced error, show all if debug is high enough + } + } + $errortype = array ( + E_ERROR => "Error", + E_WARNING => "Warning", + E_PARSE => "Parsing Error", + E_NOTICE => "Notice", + E_CORE_ERROR => "Core Error", + E_CORE_WARNING => "Core Warning", + E_COMPILE_ERROR => "Compile Error", + E_COMPILE_WARNING => "Compile Warning", + E_USER_ERROR => "User Error", + E_USER_WARNING => "User Warning", + E_USER_NOTICE => "User Notice" + ); + $prefix = $errortype[$errno]; + global $_PEAR_PHPDIR; + if (stristr($file, $_PEAR_PHPDIR)) { + $file = substr($file, strlen($_PEAR_PHPDIR) + 1); + } else { + $file = basename($file); + } + print "\n$prefix: $errmsg in $file on line $line\n"; + return false; +} + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: nil + * mode: php + * End: + */ +// vim600:syn=phpPEAR-1.9.0/scripts/peclcmd.php100664 764 764 1636 100664 11123 + * @author Tomas V.V.Cox + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: peclcmd.php 276392 2009-02-25 00:06:23Z dufuz $ + * @link http://pear.php.net/package/PEAR + */ + +/** + * @nodep Gtk + */ +if ('@include_path@' != '@'.'include_path'.'@') { + ini_set('include_path', '@include_path@'); + $raw = false; +} else { + // this is a raw, uninstalled pear, either a cvs checkout, or php distro + $raw = true; +} +define('PEAR_RUNTYPE', 'pecl'); +require_once 'pearcmd.php'; +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: nil + * mode: php + * End: + */ +// vim600:syn=php + +?> +PEAR-1.9.0/LICENSE100664 764 764 2705 100664 6317 Copyright (c) 1997-2009, + Stig Bakken , + Gregory Beaver , + Helgi Þormar Þorbjörnsson , + Tomas V.V.Cox , + Martin Jansen . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +PEAR-1.9.0/INSTALL100664 764 764 3641 100664 6343 PEAR - The PEAR Installer +========================= +Installing the PEAR Installer. + +You should install PEAR on a local development machine first. Installing +PEAR on a remote production machine should only be done after you are +familiar with PEAR and have tested code using PEAR on your development +machine. + +There are two methods of installing PEAR + - PEAR bundled in PHP + - go-pear + +We will first examine how to install PEAR that is bundled with PHP. + +Microsoft Windows +================= +If you are running PHP 5.2.0 or newer, simply download and +run the windows installer (.msi) and PEAR can be automatically +installed. + +Otherwise, for older PHP versions, download the .zip of windows, +there is a script included with your PHP distribution that is called +"go-pear". You must open a command box in order to run it. Click +"start" then click "Run..." and type "cmd.exe" to open a command box. +Use "cd" to change directory to the location of PHP where you unzipped it, +and run the go-pear command. + +Unix +==== +make sure you have enabled default extensions, and if you want faster +downloads, enable the zlib extension. You must also enable the CLI +SAPI with the --enable-cli extension directive. After this, simply run: + +make install-pear + +and PEAR will be automatically configured for you. + +go-pear +======= +For users who cannot perform the above steps, or who wish to obtain the +latest PEAR with a slightly higher risk of failure, use go-pear. go-pear +is obtained by downloading http://go-pear.org and saving it as go-pear.php. +After downloading, simply run "php go-pear.php" or open it in a web browser +(windows only) to download and install PEAR. + +You can always ask general installation questions on pear-general@lists.php.net, +a public mailing list devoted to support for PEAR packages and installation- +related issues. + +Happy PHPing, we hope PEAR will be a great tool for your development work! + +$Id: INSTALL 220345 2006-09-22 03:31:36Z cellog $PEAR-1.9.0/package.dtd100664 764 764 6477 100664 7414 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PEAR-1.9.0/PEAR5.php100664 764 764 2077 100664 6641 + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: PEAR.php 286670 2009-08-02 14:16:06Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/**#@+ + * ERROR constants + */ +define('PEAR_ERROR_RETURN', 1); +define('PEAR_ERROR_PRINT', 2); +define('PEAR_ERROR_TRIGGER', 4); +define('PEAR_ERROR_DIE', 8); +define('PEAR_ERROR_CALLBACK', 16); +/** + * WARNING: obsolete + * @deprecated + */ +define('PEAR_ERROR_EXCEPTION', 32); +/**#@-*/ +define('PEAR_ZE2', (function_exists('version_compare') && + version_compare(zend_version(), "2-dev", "ge"))); + +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); + define('OS_UNIX', false); + define('PEAR_OS', 'Windows'); +} else { + define('OS_WINDOWS', false); + define('OS_UNIX', true); + define('PEAR_OS', 'Unix'); // blatant assumption +} + +$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; +$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; +$GLOBALS['_PEAR_destructor_object_list'] = array(); +$GLOBALS['_PEAR_shutdown_funcs'] = array(); +$GLOBALS['_PEAR_error_handler_stack'] = array(); + +@ini_set('track_errors', true); + +/** + * Base class for other PEAR classes. Provides rudimentary + * emulation of destructors. + * + * If you want a destructor in your class, inherit PEAR and make a + * destructor method called _yourclassname (same name as the + * constructor, but with a "_" prefix). Also, in your constructor you + * have to call the PEAR constructor: $this->PEAR();. + * The destructor method will be called without parameters. Note that + * at in some SAPI implementations (such as Apache), any output during + * the request shutdown (in which destructors are called) seems to be + * discarded. If you need to get any debug information from your + * destructor, use error_log(), syslog() or something similar. + * + * IMPORTANT! To use the emulated destructors you need to create the + * objects by reference: $obj =& new PEAR_child; + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Greg Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @see PEAR_Error + * @since Class available since PHP 4.0.2 + * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear + */ +class PEAR +{ + // {{{ properties + + /** + * Whether to enable internal debug messages. + * + * @var bool + * @access private + */ + var $_debug = false; + + /** + * Default error mode for this object. + * + * @var int + * @access private + */ + var $_default_error_mode = null; + + /** + * Default error options used for this object when error mode + * is PEAR_ERROR_TRIGGER. + * + * @var int + * @access private + */ + var $_default_error_options = null; + + /** + * Default error handler (callback) for this object, if error mode is + * PEAR_ERROR_CALLBACK. + * + * @var string + * @access private + */ + var $_default_error_handler = ''; + + /** + * Which class to use for error objects. + * + * @var string + * @access private + */ + var $_error_class = 'PEAR_Error'; + + /** + * An array of expected errors. + * + * @var array + * @access private + */ + var $_expected_errors = array(); + + // }}} + + // {{{ constructor + + /** + * Constructor. Registers this object in + * $_PEAR_destructor_object_list for destructor emulation if a + * destructor object exists. + * + * @param string $error_class (optional) which class to use for + * error objects, defaults to PEAR_Error. + * @access public + * @return void + */ + function PEAR($error_class = null) + { + $classname = strtolower(get_class($this)); + if ($this->_debug) { + print "PEAR constructor called, class=$classname\n"; + } + if ($error_class !== null) { + $this->_error_class = $error_class; + } + while ($classname && strcasecmp($classname, "pear")) { + $destructor = "_$classname"; + if (method_exists($this, $destructor)) { + global $_PEAR_destructor_object_list; + $_PEAR_destructor_object_list[] = &$this; + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + break; + } else { + $classname = get_parent_class($classname); + } + } + } + + // }}} + // {{{ destructor + + /** + * Destructor (the emulated type of...). Does nothing right now, + * but is included for forward compatibility, so subclass + * destructors should always call it. + * + * See the note in the class desciption about output from + * destructors. + * + * @access public + * @return void + */ + function _PEAR() { + if ($this->_debug) { + printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); + } + } + + // }}} + // {{{ getStaticProperty() + + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @access public + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + function &getStaticProperty($class, $var) + { + static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + + return $properties[$class][$var]; + } + + // }}} + // {{{ registerShutdownFunc() + + /** + * Use this function to register a shutdown method for static + * classes. + * + * @access public + * @param mixed $func The function name (or array of class/method) to call + * @param mixed $args The arguments to pass to the function + * @return void + */ + function registerShutdownFunc($func, $args = array()) + { + // if we are called statically, there is a potential + // that no shutdown func is registered. Bug #6445 + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); + } + + // }}} + // {{{ isError() + + /** + * Tell whether a value is a PEAR error. + * + * @param mixed $data the value to test + * @param int $code if $data is an error object, return true + * only if $code is a string and + * $obj->getMessage() == $code or + * $code is an integer and $obj->getCode() == $code + * @access public + * @return bool true if parameter is an error + */ + function isError($data, $code = null) + { + if (!is_a($data, 'PEAR_Error')) { + return false; + } + + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() == $code; + } + + return $data->getCode() == $code; + } + + // }}} + // {{{ setErrorHandling() + + /** + * Sets how errors generated by this object should be handled. + * Can be invoked both in objects and statically. If called + * statically, setErrorHandling sets the default behaviour for all + * PEAR objects. If called in an object, setErrorHandling sets + * the default behaviour for that object. + * + * @param int $mode + * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. + * + * @param mixed $options + * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one + * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * + * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected + * to be the callback function or method. A callback + * function is a string with the name of the function, a + * callback method is an array of two elements: the element + * at index 0 is the object, and the element at index 1 is + * the name of the method to call in the object. + * + * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is + * a printf format string used when printing the error + * message. + * + * @access public + * @return void + * @see PEAR_ERROR_RETURN + * @see PEAR_ERROR_PRINT + * @see PEAR_ERROR_TRIGGER + * @see PEAR_ERROR_DIE + * @see PEAR_ERROR_CALLBACK + * @see PEAR_ERROR_EXCEPTION + * + * @since PHP 4.0.5 + */ + + function setErrorHandling($mode = null, $options = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $setmode = &$this->_default_error_mode; + $setoptions = &$this->_default_error_options; + } else { + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + } + + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + } + + // }}} + // {{{ expectError() + + /** + * This method is used to tell which errors you expect to get. + * Expected errors are always returned with error mode + * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, + * and this method pushes a new element onto it. The list of + * expected errors are in effect until they are popped off the + * stack with the popExpect() method. + * + * Note that this method can not be called statically + * + * @param mixed $code a single error code or an array of error codes to expect + * + * @return int the new depth of the "expected errors" stack + * @access public + */ + function expectError($code = '*') + { + if (is_array($code)) { + array_push($this->_expected_errors, $code); + } else { + array_push($this->_expected_errors, array($code)); + } + return sizeof($this->_expected_errors); + } + + // }}} + // {{{ popExpect() + + /** + * This method pops one element off the expected error codes + * stack. + * + * @return array the list of error codes that were popped + */ + function popExpect() + { + return array_pop($this->_expected_errors); + } + + // }}} + // {{{ _checkDelExpect() + + /** + * This method checks unsets an error code if available + * + * @param mixed error code + * @return bool true if the error code was unset, false otherwise + * @access private + * @since PHP 4.3.0 + */ + function _checkDelExpect($error_code) + { + $deleted = false; + + foreach ($this->_expected_errors AS $key => $error_array) { + if (in_array($error_code, $error_array)) { + unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); + $deleted = true; + } + + // clean up empty arrays + if (0 == count($this->_expected_errors[$key])) { + unset($this->_expected_errors[$key]); + } + } + return $deleted; + } + + // }}} + // {{{ delExpect() + + /** + * This method deletes all occurences of the specified element from + * the expected error codes stack. + * + * @param mixed $error_code error code that should be deleted + * @return mixed list of error codes that were deleted or error + * @access public + * @since PHP 4.3.0 + */ + function delExpect($error_code) + { + $deleted = false; + if ((is_array($error_code) && (0 != count($error_code)))) { + // $error_code is a non-empty array here; + // we walk through it trying to unset all + // values + foreach($error_code as $key => $error) { + if ($this->_checkDelExpect($error)) { + $deleted = true; + } else { + $deleted = false; + } + } + return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } elseif (!empty($error_code)) { + // $error_code comes alone, trying to unset it + if ($this->_checkDelExpect($error_code)) { + return true; + } else { + return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } + } + + // $error_code is empty + return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME + } + + // }}} + // {{{ raiseError() + + /** + * This method is a wrapper that returns an instance of the + * configured error class with this object's default error + * handling applied. If the $mode and $options parameters are not + * specified, the object's defaults are used. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. + * + * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter + * specifies the PHP-internal error level (one of + * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * If $mode is PEAR_ERROR_CALLBACK, this + * parameter specifies the callback function or + * method. In other error modes this parameter + * is ignored. + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @param string $error_class The returned error object will be + * instantiated from this class, if specified. + * + * @param bool $skipmsg If true, raiseError will only pass error codes, + * the error message parameter will be dropped. + * + * @access public + * @return object a PEAR error object + * @see PEAR::setErrorHandling + * @since PHP 4.0.5 + */ + function &raiseError($message = null, + $code = null, + $mode = null, + $options = null, + $userinfo = null, + $error_class = null, + $skipmsg = false) + { + // The error is yet a PEAR error object + if (is_object($message)) { + $code = $message->getCode(); + $userinfo = $message->getUserInfo(); + $error_class = $message->getType(); + $message->error_message_prefix = ''; + $message = $message->getMessage(); + } + + if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) { + if ($exp[0] == "*" || + (is_int(reset($exp)) && in_array($code, $exp)) || + (is_string(reset($exp)) && in_array($message, $exp))) { + $mode = PEAR_ERROR_RETURN; + } + } + + // No mode given, try global ones + if ($mode === null) { + // Class error handler + if (isset($this) && isset($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + // Global error handler + } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { + $mode = $GLOBALS['_PEAR_default_error_mode']; + $options = $GLOBALS['_PEAR_default_error_options']; + } + } + + if ($error_class !== null) { + $ec = $error_class; + } elseif (isset($this) && isset($this->_error_class)) { + $ec = $this->_error_class; + } else { + $ec = 'PEAR_Error'; + } + + if (intval(PHP_VERSION) < 5) { + // little non-eval hack to fix bug #12147 + include 'PEAR/FixPHP5PEARWarnings.php'; + return $a; + } + + if ($skipmsg) { + $a = new $ec($code, $mode, $options, $userinfo); + } else { + $a = new $ec($message, $code, $mode, $options, $userinfo); + } + + return $a; + } + + // }}} + // {{{ throwError() + + /** + * Simpler form of raiseError with fewer options. In most cases + * message, code and userinfo are enough. + * + * @param string $message + * + */ + function &throwError($message = null, + $code = null, + $userinfo = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $a = &$this->raiseError($message, $code, null, null, $userinfo); + return $a; + } + + $a = &PEAR::raiseError($message, $code, null, null, $userinfo); + return $a; + } + + // }}} + function staticPushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + $stack[] = array($def_mode, $def_options); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $def_mode = $mode; + $def_options = $options; + break; + + case PEAR_ERROR_CALLBACK: + $def_mode = $mode; + // class/object method callback + if (is_callable($options)) { + $def_options = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + $stack[] = array($mode, $options); + return true; + } + + function staticPopErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + return true; + } + + // {{{ pushErrorHandling() + + /** + * Push a new error handler on top of the error handler options stack. With this + * you can easily override the actual error handler for some code and restore + * it later with popErrorHandling. + * + * @param mixed $mode (same as setErrorHandling) + * @param mixed $options (same as setErrorHandling) + * + * @return bool Always true + * + * @see PEAR::setErrorHandling + */ + function pushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + if (isset($this) && is_a($this, 'PEAR')) { + $def_mode = &$this->_default_error_mode; + $def_options = &$this->_default_error_options; + } else { + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + } + $stack[] = array($def_mode, $def_options); + + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + $stack[] = array($mode, $options); + return true; + } + + // }}} + // {{{ popErrorHandling() + + /** + * Pop the last error handler used + * + * @return bool Always true + * + * @see PEAR::pushErrorHandling + */ + function popErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + return true; + } + + // }}} + // {{{ loadExtension() + + /** + * OS independant PHP extension load. Remember to take care + * on the correct extension name for case sensitive OSes. + * + * @param string $ext The extension name + * @return bool Success or not on the dl() call + */ + function loadExtension($ext) + { + if (!extension_loaded($ext)) { + // if either returns true dl() will produce a FATAL error, stop that + if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { + return false; + } + + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; + } + + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + } + + return true; + } + + // }}} +} + +if (PEAR_ZE2) { + include_once 'PEAR5.php'; +} + +// {{{ _PEAR_call_destructors() + +function _PEAR_call_destructors() +{ + global $_PEAR_destructor_object_list; + if (is_array($_PEAR_destructor_object_list) && + sizeof($_PEAR_destructor_object_list)) + { + reset($_PEAR_destructor_object_list); + if (PEAR_ZE2) { + $destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo'); + } else { + $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo'); + } + + if ($destructLifoExists) { + $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); + } + + while (list($k, $objref) = each($_PEAR_destructor_object_list)) { + $classname = get_class($objref); + while ($classname) { + $destructor = "_$classname"; + if (method_exists($objref, $destructor)) { + $objref->$destructor(); + break; + } else { + $classname = get_parent_class($classname); + } + } + } + // Empty the object list to ensure that destructors are + // not called more than once. + $_PEAR_destructor_object_list = array(); + } + + // Now call the shutdown functions + if (isset($GLOBALS['_PEAR_shutdown_funcs']) AND is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) { + foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { + call_user_func_array($value[0], $value[1]); + } + } +} + +// }}} +/** + * Standard PEAR error class for PHP 4 + * + * This class is supserseded by {@link PEAR_Exception} in PHP 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Gregory Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/manual/en/core.pear.pear-error.php + * @see PEAR::raiseError(), PEAR::throwError() + * @since Class available since PHP 4.0.2 + */ +class PEAR_Error +{ + // {{{ properties + + var $error_message_prefix = ''; + var $mode = PEAR_ERROR_RETURN; + var $level = E_USER_NOTICE; + var $code = -1; + var $message = ''; + var $userinfo = ''; + var $backtrace = null; + + // }}} + // {{{ constructor + + /** + * PEAR_Error constructor + * + * @param string $message message + * + * @param int $code (optional) error code + * + * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, + * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION + * + * @param mixed $options (optional) error level, _OR_ in the case of + * PEAR_ERROR_CALLBACK, the callback function or object/method + * tuple. + * + * @param string $userinfo (optional) additional user/debug info + * + * @access public + * + */ + function PEAR_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + if ($mode === null) { + $mode = PEAR_ERROR_RETURN; + } + $this->message = $message; + $this->code = $code; + $this->mode = $mode; + $this->userinfo = $userinfo; + + if (PEAR_ZE2) { + $skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace'); + } else { + $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace'); + } + + if (!$skiptrace) { + $this->backtrace = debug_backtrace(); + if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) { + unset($this->backtrace[0]['object']); + } + } + + if ($mode & PEAR_ERROR_CALLBACK) { + $this->level = E_USER_NOTICE; + $this->callback = $options; + } else { + if ($options === null) { + $options = E_USER_NOTICE; + } + + $this->level = $options; + $this->callback = null; + } + + if ($this->mode & PEAR_ERROR_PRINT) { + if (is_null($options) || is_int($options)) { + $format = "%s"; + } else { + $format = $options; + } + + printf($format, $this->getMessage()); + } + + if ($this->mode & PEAR_ERROR_TRIGGER) { + trigger_error($this->getMessage(), $this->level); + } + + if ($this->mode & PEAR_ERROR_DIE) { + $msg = $this->getMessage(); + if (is_null($options) || is_int($options)) { + $format = "%s"; + if (substr($msg, -1) != "\n") { + $msg .= "\n"; + } + } else { + $format = $options; + } + die(sprintf($format, $msg)); + } + + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_callable($this->callback)) { + call_user_func($this->callback, $this); + } + } + + if ($this->mode & PEAR_ERROR_EXCEPTION) { + trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); + eval('$e = new Exception($this->message, $this->code);throw($e);'); + } + } + + // }}} + // {{{ getMode() + + /** + * Get the error mode from an error object. + * + * @return int error mode + * @access public + */ + function getMode() { + return $this->mode; + } + + // }}} + // {{{ getCallback() + + /** + * Get the callback function/method from an error object. + * + * @return mixed callback function or object/method array + * @access public + */ + function getCallback() { + return $this->callback; + } + + // }}} + // {{{ getMessage() + + + /** + * Get the error message from an error object. + * + * @return string full error message + * @access public + */ + function getMessage() + { + return ($this->error_message_prefix . $this->message); + } + + + // }}} + // {{{ getCode() + + /** + * Get error code from an error object + * + * @return int error code + * @access public + */ + function getCode() + { + return $this->code; + } + + // }}} + // {{{ getType() + + /** + * Get the name of this error/exception. + * + * @return string error/exception name (type) + * @access public + */ + function getType() + { + return get_class($this); + } + + // }}} + // {{{ getUserInfo() + + /** + * Get additional user-supplied information. + * + * @return string user-supplied information + * @access public + */ + function getUserInfo() + { + return $this->userinfo; + } + + // }}} + // {{{ getDebugInfo() + + /** + * Get additional debug information supplied by the application. + * + * @return string debug information + * @access public + */ + function getDebugInfo() + { + return $this->getUserInfo(); + } + + // }}} + // {{{ getBacktrace() + + /** + * Get the call backtrace from where the error was generated. + * Supported with PHP 4.3.0 or newer. + * + * @param int $frame (optional) what frame to fetch + * @return array Backtrace, or NULL if not available. + * @access public + */ + function getBacktrace($frame = null) + { + if (defined('PEAR_IGNORE_BACKTRACE')) { + return null; + } + if ($frame === null) { + return $this->backtrace; + } + return $this->backtrace[$frame]; + } + + // }}} + // {{{ addUserInfo() + + function addUserInfo($info) + { + if (empty($this->userinfo)) { + $this->userinfo = $info; + } else { + $this->userinfo .= " ** $info"; + } + } + + // }}} + // {{{ toString() + function __toString() + { + return $this->getMessage(); + } + // }}} + // {{{ toString() + + /** + * Make a string representation of this object. + * + * @return string a string with an object summary + * @access public + */ + function toString() { + $modes = array(); + $levels = array(E_USER_NOTICE => 'notice', + E_USER_WARNING => 'warning', + E_USER_ERROR => 'error'); + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_array($this->callback)) { + $callback = (is_object($this->callback[0]) ? + strtolower(get_class($this->callback[0])) : + $this->callback[0]) . '::' . + $this->callback[1]; + } else { + $callback = $this->callback; + } + return sprintf('[%s: message="%s" code=%d mode=callback '. + 'callback=%s prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + $callback, $this->error_message_prefix, + $this->userinfo); + } + if ($this->mode & PEAR_ERROR_PRINT) { + $modes[] = 'print'; + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + $modes[] = 'trigger'; + } + if ($this->mode & PEAR_ERROR_DIE) { + $modes[] = 'die'; + } + if ($this->mode & PEAR_ERROR_RETURN) { + $modes[] = 'return'; + } + return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. + 'prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + implode("|", $modes), $levels[$this->level], + $this->error_message_prefix, + $this->userinfo); + } + + // }}} +} + +/* + * Local Variables: + * mode: php + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +PEAR-1.9.0/README100664 764 764 2246 100664 6172 PEAR - The PEAR Installer +========================= + +What is the PEAR Installer? What is PEAR? + +PEAR is the PHP Extension and Application Repository, found at +http://pear.php.net. The PEAR Installer is this software, which +contains executable files and PHP code that is used to download +and install PEAR code from pear.php.net. + +PEAR contains useful software libraries and applications such as +MDB2 (database abstraction), HTML_QuickForm (HTML forms management), +PhpDocumentor (auto-documentation generator), DB_DataObject +(Data Access Abstraction), and many hundreds more. Browse all +available packages at http://pear.php.net, the list is constantly +growing and updating to reflect improvements in the PHP language. + +DOCUMENTATION +============= + +Documentation for PEAR can be found at http://pear.php.net/manual/. +Installation documentation can be found in the INSTALL file included +in this tarball. + +WARNING: DO NOT RUN PEAR WITHOUT INSTALLING IT - if you downloaded this +tarball manually, you MUST install it. Read the instructions in INSTALL +prior to use. + + +Happy PHPing, we hope PEAR will be a great tool for your development work! + +$Id: README 220345 2006-09-22 03:31:36Z cellog $PEAR-1.9.0/System.php100664 764 764 46777 100664 7350 + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: System.php 276386 2009-02-24 23:52:56Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR.php'; +require_once 'Console/Getopt.php'; + +$GLOBALS['_System_temp_files'] = array(); + +/** +* System offers cross plattform compatible system functions +* +* Static functions for different operations. Should work under +* Unix and Windows. The names and usage has been taken from its respectively +* GNU commands. The functions will return (bool) false on error and will +* trigger the error with the PHP trigger_error() function (you can silence +* the error by prefixing a '@' sign after the function call, but this +* is not recommended practice. Instead use an error handler with +* {@link set_error_handler()}). +* +* Documentation on this class you can find in: +* http://pear.php.net/manual/ +* +* Example usage: +* if (!@System::rm('-r file1 dir1')) { +* print "could not delete file1 or dir1"; +* } +* +* In case you need to to pass file names with spaces, +* pass the params as an array: +* +* System::rm(array('-r', $file1, $dir1)); +* +* @category pear +* @package System +* @author Tomas V.V. Cox +* @copyright 1997-2006 The PHP Group +* @license http://opensource.org/licenses/bsd-license.php New BSD License +* @version Release: 1.9.0 +* @link http://pear.php.net/package/PEAR +* @since Class available since Release 0.1 +* @static +*/ +class System +{ + /** + * returns the commandline arguments of a function + * + * @param string $argv the commandline + * @param string $short_options the allowed option short-tags + * @param string $long_options the allowed option long-tags + * @return array the given options and there values + * @static + * @access private + */ + function _parseArgs($argv, $short_options, $long_options = null) + { + if (!is_array($argv) && $argv !== null) { + $argv = preg_split('/\s+/', $argv, -1, PREG_SPLIT_NO_EMPTY); + } + return Console_Getopt::getopt2($argv, $short_options); + } + + /** + * Output errors with PHP trigger_error(). You can silence the errors + * with prefixing a "@" sign to the function call: @System::mkdir(..); + * + * @param mixed $error a PEAR error or a string with the error message + * @return bool false + * @static + * @access private + */ + function raiseError($error) + { + if (PEAR::isError($error)) { + $error = $error->getMessage(); + } + trigger_error($error, E_USER_WARNING); + return false; + } + + /** + * Creates a nested array representing the structure of a directory + * + * System::_dirToStruct('dir1', 0) => + * Array + * ( + * [dirs] => Array + * ( + * [0] => dir1 + * ) + * + * [files] => Array + * ( + * [0] => dir1/file2 + * [1] => dir1/file3 + * ) + * ) + * @param string $sPath Name of the directory + * @param integer $maxinst max. deep of the lookup + * @param integer $aktinst starting deep of the lookup + * @param bool $silent if true, do not emit errors. + * @return array the structure of the dir + * @static + * @access private + */ + function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false) + { + $struct = array('dirs' => array(), 'files' => array()); + if (($dir = @opendir($sPath)) === false) { + if (!$silent) { + System::raiseError("Could not open dir $sPath"); + } + return $struct; // XXX could not open error + } + + $struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ? + $list = array(); + while (false !== ($file = readdir($dir))) { + if ($file != '.' && $file != '..') { + $list[] = $file; + } + } + + closedir($dir); + natsort($list); + if ($aktinst < $maxinst || $maxinst == 0) { + foreach ($list as $val) { + $path = $sPath . DIRECTORY_SEPARATOR . $val; + if (is_dir($path) && !is_link($path)) { + $tmp = System::_dirToStruct($path, $maxinst, $aktinst+1, $silent); + $struct = array_merge_recursive($struct, $tmp); + } else { + $struct['files'][] = $path; + } + } + } + + return $struct; + } + + /** + * Creates a nested array representing the structure of a directory and files + * + * @param array $files Array listing files and dirs + * @return array + * @static + * @see System::_dirToStruct() + */ + function _multipleToStruct($files) + { + $struct = array('dirs' => array(), 'files' => array()); + settype($files, 'array'); + foreach ($files as $file) { + if (is_dir($file) && !is_link($file)) { + $tmp = System::_dirToStruct($file, 0); + $struct = array_merge_recursive($tmp, $struct); + } else { + if (!in_array($file, $struct['files'])) { + $struct['files'][] = $file; + } + } + } + return $struct; + } + + /** + * The rm command for removing files. + * Supports multiple files and dirs and also recursive deletes + * + * @param string $args the arguments for rm + * @return mixed PEAR_Error or true for success + * @static + * @access public + */ + function rm($args) + { + $opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-) + if (PEAR::isError($opts)) { + return System::raiseError($opts); + } + foreach ($opts[0] as $opt) { + if ($opt[0] == 'r') { + $do_recursive = true; + } + } + $ret = true; + if (isset($do_recursive)) { + $struct = System::_multipleToStruct($opts[1]); + foreach ($struct['files'] as $file) { + if (!@unlink($file)) { + $ret = false; + } + } + + rsort($struct['dirs']); + foreach ($struct['dirs'] as $dir) { + if (!@rmdir($dir)) { + $ret = false; + } + } + } else { + foreach ($opts[1] as $file) { + $delete = (is_dir($file)) ? 'rmdir' : 'unlink'; + if (!@$delete($file)) { + $ret = false; + } + } + } + return $ret; + } + + /** + * Make directories. + * + * The -p option will create parent directories + * @param string $args the name of the director(y|ies) to create + * @return bool True for success + * @static + * @access public + */ + function mkDir($args) + { + $opts = System::_parseArgs($args, 'pm:'); + if (PEAR::isError($opts)) { + return System::raiseError($opts); + } + + $mode = 0777; // default mode + foreach ($opts[0] as $opt) { + if ($opt[0] == 'p') { + $create_parents = true; + } elseif ($opt[0] == 'm') { + // if the mode is clearly an octal number (starts with 0) + // convert it to decimal + if (strlen($opt[1]) && $opt[1]{0} == '0') { + $opt[1] = octdec($opt[1]); + } else { + // convert to int + $opt[1] += 0; + } + $mode = $opt[1]; + } + } + + $ret = true; + if (isset($create_parents)) { + foreach ($opts[1] as $dir) { + $dirstack = array(); + while ((!file_exists($dir) || !is_dir($dir)) && + $dir != DIRECTORY_SEPARATOR) { + array_unshift($dirstack, $dir); + $dir = dirname($dir); + } + + while ($newdir = array_shift($dirstack)) { + if (!is_writeable(dirname($newdir))) { + $ret = false; + break; + } + + if (!mkdir($newdir, $mode)) { + $ret = false; + } + } + } + } else { + foreach($opts[1] as $dir) { + if ((@file_exists($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) { + $ret = false; + } + } + } + + return $ret; + } + + /** + * Concatenate files + * + * Usage: + * 1) $var = System::cat('sample.txt test.txt'); + * 2) System::cat('sample.txt test.txt > final.txt'); + * 3) System::cat('sample.txt test.txt >> final.txt'); + * + * Note: as the class use fopen, urls should work also (test that) + * + * @param string $args the arguments + * @return boolean true on success + * @static + * @access public + */ + function &cat($args) + { + $ret = null; + $files = array(); + if (!is_array($args)) { + $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY); + } + + $count_args = count($args); + for ($i = 0; $i < $count_args; $i++) { + if ($args[$i] == '>') { + $mode = 'wb'; + $outputfile = $args[$i+1]; + break; + } elseif ($args[$i] == '>>') { + $mode = 'ab+'; + $outputfile = $args[$i+1]; + break; + } else { + $files[] = $args[$i]; + } + } + $outputfd = false; + if (isset($mode)) { + if (!$outputfd = fopen($outputfile, $mode)) { + $err = System::raiseError("Could not open $outputfile"); + return $err; + } + $ret = true; + } + foreach ($files as $file) { + if (!$fd = fopen($file, 'r')) { + System::raiseError("Could not open $file"); + continue; + } + while ($cont = fread($fd, 2048)) { + if (is_resource($outputfd)) { + fwrite($outputfd, $cont); + } else { + $ret .= $cont; + } + } + fclose($fd); + } + if (is_resource($outputfd)) { + fclose($outputfd); + } + return $ret; + } + + /** + * Creates temporary files or directories. This function will remove + * the created files when the scripts finish its execution. + * + * Usage: + * 1) $tempfile = System::mktemp("prefix"); + * 2) $tempdir = System::mktemp("-d prefix"); + * 3) $tempfile = System::mktemp(); + * 4) $tempfile = System::mktemp("-t /var/tmp prefix"); + * + * prefix -> The string that will be prepended to the temp name + * (defaults to "tmp"). + * -d -> A temporary dir will be created instead of a file. + * -t -> The target dir where the temporary (file|dir) will be created. If + * this param is missing by default the env vars TMP on Windows or + * TMPDIR in Unix will be used. If these vars are also missing + * c:\windows\temp or /tmp will be used. + * + * @param string $args The arguments + * @return mixed the full path of the created (file|dir) or false + * @see System::tmpdir() + * @static + * @access public + */ + function mktemp($args = null) + { + static $first_time = true; + $opts = System::_parseArgs($args, 't:d'); + if (PEAR::isError($opts)) { + return System::raiseError($opts); + } + + foreach ($opts[0] as $opt) { + if ($opt[0] == 'd') { + $tmp_is_dir = true; + } elseif ($opt[0] == 't') { + $tmpdir = $opt[1]; + } + } + + $prefix = (isset($opts[1][0])) ? $opts[1][0] : 'tmp'; + if (!isset($tmpdir)) { + $tmpdir = System::tmpdir(); + } + + if (!System::mkDir(array('-p', $tmpdir))) { + return false; + } + + $tmp = tempnam($tmpdir, $prefix); + if (isset($tmp_is_dir)) { + unlink($tmp); // be careful possible race condition here + if (!mkdir($tmp, 0700)) { + return System::raiseError("Unable to create temporary directory $tmpdir"); + } + } + + $GLOBALS['_System_temp_files'][] = $tmp; + if (isset($tmp_is_dir)) { + //$GLOBALS['_System_temp_files'][] = dirname($tmp); + } + + if ($first_time) { + PEAR::registerShutdownFunc(array('System', '_removeTmpFiles')); + $first_time = false; + } + + return $tmp; + } + + /** + * Remove temporary files created my mkTemp. This function is executed + * at script shutdown time + * + * @static + * @access private + */ + function _removeTmpFiles() + { + if (count($GLOBALS['_System_temp_files'])) { + $delete = $GLOBALS['_System_temp_files']; + array_unshift($delete, '-r'); + System::rm($delete); + $GLOBALS['_System_temp_files'] = array(); + } + } + + /** + * Get the path of the temporal directory set in the system + * by looking in its environments variables. + * Note: php.ini-recommended removes the "E" from the variables_order setting, + * making unavaible the $_ENV array, that s why we do tests with _ENV + * + * @static + * @return string The temporary directory on the system + */ + function tmpdir() + { + if (OS_WINDOWS) { + if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) { + return $var; + } + if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) { + return $var; + } + if ($var = isset($_ENV['USERPROFILE']) ? $_ENV['USERPROFILE'] : getenv('USERPROFILE')) { + return $var; + } + if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) { + return $var; + } + return getenv('SystemRoot') . '\temp'; + } + if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) { + return $var; + } + return realpath('/tmp'); + } + + /** + * The "which" command (show the full path of a command) + * + * @param string $program The command to search for + * @param mixed $fallback Value to return if $program is not found + * + * @return mixed A string with the full path or false if not found + * @static + * @author Stig Bakken + */ + function which($program, $fallback = false) + { + // enforce API + if (!is_string($program) || '' == $program) { + return $fallback; + } + + // full path given + if (basename($program) != $program) { + $path_elements[] = dirname($program); + $program = basename($program); + } else { + // Honor safe mode + if (!ini_get('safe_mode') || !$path = ini_get('safe_mode_exec_dir')) { + $path = getenv('PATH'); + if (!$path) { + $path = getenv('Path'); // some OSes are just stupid enough to do this + } + } + $path_elements = explode(PATH_SEPARATOR, $path); + } + + if (OS_WINDOWS) { + $exe_suffixes = getenv('PATHEXT') + ? explode(PATH_SEPARATOR, getenv('PATHEXT')) + : array('.exe','.bat','.cmd','.com'); + // allow passing a command.exe param + if (strpos($program, '.') !== false) { + array_unshift($exe_suffixes, ''); + } + // is_executable() is not available on windows for PHP4 + $pear_is_executable = (function_exists('is_executable')) ? 'is_executable' : 'is_file'; + } else { + $exe_suffixes = array(''); + $pear_is_executable = 'is_executable'; + } + + foreach ($exe_suffixes as $suff) { + foreach ($path_elements as $dir) { + $file = $dir . DIRECTORY_SEPARATOR . $program . $suff; + if (@$pear_is_executable($file)) { + return $file; + } + } + } + return $fallback; + } + + /** + * The "find" command + * + * Usage: + * + * System::find($dir); + * System::find("$dir -type d"); + * System::find("$dir -type f"); + * System::find("$dir -name *.php"); + * System::find("$dir -name *.php -name *.htm*"); + * System::find("$dir -maxdepth 1"); + * + * Params implmented: + * $dir -> Start the search at this directory + * -type d -> return only directories + * -type f -> return only files + * -maxdepth -> max depth of recursion + * -name -> search pattern (bash style). Multiple -name param allowed + * + * @param mixed Either array or string with the command line + * @return array Array of found files + * @static + * + */ + function find($args) + { + if (!is_array($args)) { + $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY); + } + $dir = realpath(array_shift($args)); + if (!$dir) { + return array(); + } + $patterns = array(); + $depth = 0; + $do_files = $do_dirs = true; + $args_count = count($args); + for ($i = 0; $i < $args_count; $i++) { + switch ($args[$i]) { + case '-type': + if (in_array($args[$i+1], array('d', 'f'))) { + if ($args[$i+1] == 'd') { + $do_files = false; + } else { + $do_dirs = false; + } + } + $i++; + break; + case '-name': + $name = preg_quote($args[$i+1], '#'); + // our magic characters ? and * have just been escaped, + // so now we change the escaped versions to PCRE operators + $name = strtr($name, array('\?' => '.', '\*' => '.*')); + $patterns[] = '('.$name.')'; + $i++; + break; + case '-maxdepth': + $depth = $args[$i+1]; + break; + } + } + $path = System::_dirToStruct($dir, $depth, 0, true); + if ($do_files && $do_dirs) { + $files = array_merge($path['files'], $path['dirs']); + } elseif ($do_dirs) { + $files = $path['dirs']; + } else { + $files = $path['files']; + } + if (count($patterns)) { + $dsq = preg_quote(DIRECTORY_SEPARATOR, '#'); + $pattern = '#(^|'.$dsq.')'.implode('|', $patterns).'($|'.$dsq.')#'; + $ret = array(); + $files_count = count($files); + for ($i = 0; $i < $files_count; $i++) { + // only search in the part of the file below the current directory + $filepart = basename($files[$i]); + if (preg_match($pattern, $filepart)) { + $ret[] = $files[$i]; + } + } + return $ret; + } + return $files; + } +}PEAR-1.9.0/template.spec100664 764 764 3725 100664 10004 Summary: PEAR: @summary@ +Name: @rpm_package@ +Version: @version@ +Release: 1 +License: @release_license@ +Group: Development/Libraries +Source: http://@master_server@/get/@package@-%{version}.tgz +BuildRoot: %{_tmppath}/%{name}-root +URL: http://@master_server@/package/@package@ +Prefix: %{_prefix} +BuildArchitectures: @arch@ +@extra_headers@ + +%description +@description@ + +%prep +rm -rf %{buildroot}/* +%setup -c -T +# XXX Source files location is missing here in pear cmd +pear -v -c %{buildroot}/pearrc \ + -d php_dir=%{_libdir}/php/pear \ + -d doc_dir=/docs \ + -d bin_dir=%{_bindir} \ + -d data_dir=%{_libdir}/php/pear/data \ + -d test_dir=%{_libdir}/php/pear/tests \ + -d ext_dir=%{_libdir} \@extra_config@ + -s + +%build +echo BuildRoot=%{buildroot} + +%postun +# if refcount = 0 then package has been removed (not upgraded) +if [ "$1" -eq "0" ]; then + pear uninstall --nodeps -r @possible_channel@@package@ + rm @rpm_xml_dir@/@package@.xml +fi + + +%post +# if refcount = 2 then package has been upgraded +if [ "$1" -ge "2" ]; then + pear upgrade --nodeps -r @rpm_xml_dir@/@package@.xml +else + pear install --nodeps -r @rpm_xml_dir@/@package@.xml +fi + +%install +pear -c %{buildroot}/pearrc install --nodeps -R %{buildroot} \ + $RPM_SOURCE_DIR/@package@-%{version}.tgz +rm %{buildroot}/pearrc +rm %{buildroot}/%{_libdir}/php/pear/.filemap +rm %{buildroot}/%{_libdir}/php/pear/.lock +rm -rf %{buildroot}/%{_libdir}/php/pear/.registry +if [ "@doc_files@" != "" ]; then + mv %{buildroot}/docs/@package@/* . + rm -rf %{buildroot}/docs +fi +mkdir -p %{buildroot}@rpm_xml_dir@ +tar -xzf $RPM_SOURCE_DIR/@package@-%{version}.tgz package@package2xml@.xml +cp -p package@package2xml@.xml %{buildroot}@rpm_xml_dir@/@package@.xml + +#rm -rf %{buildroot}/* +#pear -q install -R %{buildroot} -n package@package2xml@.xml +#mkdir -p %{buildroot}@rpm_xml_dir@ +#cp -p package@package2xml@.xml %{buildroot}@rpm_xml_dir@/@package@.xml + +%files + %defattr(-,root,root) + %doc @doc_files@ + / +package.xml100664 764 764 76000 100664 6254 + + + PEAR + PEAR Base System + The PEAR package contains: + * the PEAR installer, for creating, distributing + and installing packages + * the PEAR_Exception PHP5 error handling mechanism + * the PEAR_ErrorStack advanced error handling mechanism + * the PEAR_Error error handling mechanism + * the OS_Guess class for retrieving info about the OS + where PHP is running on + * the System class for quick handling of common operations + with files and directories + * the PEAR base class + + Features in a nutshell: + * full support for channels + * pre-download dependency validation + * new package.xml 2.0 format allows tremendous flexibility while maintaining BC + * support for optional dependency groups and limited support for sub-packaging + * robust dependency support + * full dependency validation on uninstall + * remote install for hosts with only ftp access - no more problems with + restricted host installation + * full support for mirroring + * support for bundling several packages into a single tarball + * support for static dependencies on a url-based package + * support for custom file roles and installation tasks + + + + cellog + Greg Beaver + cellog@php.net + lead + + + pajoye + Pierre-Alain Joye + pierre@php.net + lead + + + ssb + Stig Bakken + stig@php.net + lead + + + cox + Tomas V.V.Cox + cox@idecnet.com + lead + + + dufuz + Helgi Thormar + dufuz@php.net + lead + + + tias + Tias Guns + tias@php.net + developer + + + timj + Tim Jackson + timj@php.net + helper + + + toggg + Bertrand Gugger + toggg@php.net + helper + + + mj + Martin Jansen + mj@php.net + helper + + + + 1.9.0 + 2009-09-03 + New BSD License + stable + * Fix Bug #16547: The phar for PEAR installer uses ereg() which is deprecated [dufuz] + + + + PEAR + Archive_Tar + Console_Getopt + Structures_Graph + PEAR_Frontend_Web + PEAR_Frontend_Gtk + xml + pcre + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.8.0alpha1 + 2009-03-09 + New BSD License + alpha + * Implement Request #10373: if pref_state=stable and installed package=beta, allow up to latest beta version [dufuz] +* Implement Request #10581: login / logout should map to channel-login / channel-logout [dufuz] +* Implement Request #10825: Only display the "invalid or missing package file"-error if it makes sense [dufuz] +* Implement Request #11170: script to generate Command/[command].xml [dufuz] +* Implement Request #11176: improve channel ... has updated its protocols message [dufuz] +* Implement Request #12706: pear list -a hard to read [dufuz] +* Implement Request #11353: upgrade-all and upgrade commands to upgrade within the same stability level [dufuz] +* Implement Request #13015: Add https discovery for channel.xml [dufuz / initial patch by Martin Roos] +* Implement Request #13927: install-pear.php should have option to set www_dir [timj] +* Implement Request #14324: Make the pear install command behave similar to apt-get [dufuz] +* Implement Request #14325: make pear upgrade with no params behave like pear upgrade-all [dufuz] + - upgrade-all can be considered deprecated in favor of calling upgrade with no parameters to replicate + better what other package managers are doing. upgrade-all will still work as intended. +* Implement Request #14504: add a channel parameter support to the upgrade function [dufuz] + - Options -c ezc and --channel=ezc got added to upgrade and upgrade-all to allow for + channel specific upgrades +* Implement Request #14556: install-pear-nozlib.phar should get download_dir config and other options [cweiske] +* Implement Request #15566: Add doc.php.net as a default channel [dufuz / saltybeagle] +* Fix PHP Bug #43857: --program-suffix not always reflected everywhere [cellog] +* Fix PHP Bug #47323: strotime warnings in make install [dufuz] +* Fix Bug #13908: pear info command and maintainers inactive not mentioned [dufuz] +* Fix Bug #13926: install-pear.php does not set cfg_dir if -d option set with no -c option [timj] +* Fix Bug #13943: tests fail when php.exe path contains spaces [dufuz / jorrit] +* Fix Bug #13953: config-set/config-show with channel alias fail [cellog] +* Fix Bug #13958: When a phpt tests exit() or die() xdebug coverage is not generated, patch by izi (David Jean Louis) [izi / dufuz] +* Fix Bug #14041: Unpredictable unit test processing sequence [dufuz] +* Fix Bug #14140: Strict warning not suppressed in the shutdown function [dufuz] +* Fix Bug #14210: pear list -ia brings warnings [dufuz] +* Fix Bug #14274: PEAR packager mangles package.xml encoding, then complains about it [dufuz] +* Fix Bug #14287: cannot upgrade from stable to beta via -beta when config is set to stable [dufuz] +* Fix Bug #14300: Package files themselves can not be served over https [dufuz / initial patch by Martin Roos] +* Fix Bug #14437: openbasedir warning when loading config [dufuz] +* Fix Bug #14558: PackageFile.php creates tmp directory outside configured temp_dir [cweiske] +* Fix Bug #14947: downloadHttp() is missing Host part of the HTTP Request when using Proxy [ifeghali] +* Fix Bug #14977: PEAR/Frontend.php doesn't require_once PEAR.php [dufuz] +* Fix Bug #15750: Unreachable code in PEAR_Downloader [dufuz] +* Fix Bug #15979: Package files incorrectly removed when splitting a package into multiple pkgs [dufuz] +* Fix Bug #15914: pear upgrade installs different version if desired version not found [dufuz] +NOTE! +Functions that have been deprecated for 3+ years in PEAR_Common, please take a moment +to migrate over to one of the alternatives that have ben provided: +* PEAR_Common->downloadHttp (use PEAR_Downloader->downloadHttp instead) +* PEAR_Common->infoFromTgzFile (use PEAR_PackageFile->fromTgzFile instead) +* PEAR_Common->infoFromDescriptionFile (use PEAR_PackageFile->fromPackageFile instead) +* PEAR_Common->infoFromString (use PEAR_PackageFile->fromXmlstring instead) +* PEAR_Common->infoFromArray (use PEAR_PackageFile->fromAnyFile instead) +* PEAR_Common->xmlFromInfo (use a PEAR_PackageFile_v* object's generator instead) +* PEAR_Common->validatePackageInfo (use the validation of PEAR_PackageFile objects) +* PEAR_Common->analyzeSourceCode (use a PEAR_PackageFile_v* object instead) +* PEAR_Common->detectDependencies (use PEAR_Downloader_Package->detectDependencies instead) +* PEAR_Common->buildProvidesArray (use PEAR_PackageFile_v1->_buildProvidesArray or + PEAR_PackageFile_v2_Validator->_buildProvidesArray) +PHP 4.4 and 5.1.6 are now the minimum PHP requirements, for brave souls +pear upgrade -f PEAR will allow people with lower versions +to upgrade to this release but no guarantees will be made that it will work properly. +Support for XML RPC channels has been dropped - The only ones that used it +(pear.php.net and pecl.php.net) have used the REST interface for years now. +SOAP support also removed as it was only proof of concept. +Move codebase from the PHP License to New BSD 2 clause license + + + + 1.8.0RC1 + 2009-03-27 + New BSD License + beta + * Fix Bug #14331: pear cvstag only works from inside the package directory [dufuz] +* Fix Bug #16045: E_Notice: Undefined index: channel in PEAR/DependencyDB.php [dufuz] +* Implemented Request #11230: better error message when mirror not in channel.xml file [dufuz] +* Implemented Request #13150: Add support for following HTTP 302 redirects [dufuz] + + + + + + New BSD License + Changes since RC1: + * Fix Bug #14792: Bad md5sum for files with replaced content [dufuz] + * Fix Bug #16057:-r is limited to 4 directories in depth [dufuz] + * Fix Bug #16077: PEAR5::getStaticProperty does not return a reference to the property [dufuz] + + Remove custom XML_Util class in favor of using upstream XML_Util package as dependency + +RC1 Release Notes: + * Fix Bug #14331: pear cvstag only works from inside the package directory [dufuz] + * Fix Bug #16045: E_Notice: Undefined index: channel in PEAR/DependencyDB.php [dufuz] + + * Implemented Request #11230: better error message when mirror not in channel.xml file [dufuz] + * Implemented Request #13150: Add support for following HTTP 302 redirects [dufuz] + +Alpha1 Release Notes: + * Implement Request #10373: if pref_state=stable and installed package=beta, allow up to latest beta version [dufuz] + * Implement Request #10581: login / logout should map to channel-login / channel-logout [dufuz] + * Implement Request #10825: Only display the "invalid or missing package file"-error if it makes sense [dufuz] + * Implement Request #11170: script to generate Command/[command].xml [dufuz] + * Implement Request #11176: improve channel ... has updated its protocols message [dufuz] + * Implement Request #12706: pear list -a hard to read [dufuz] + * Implement Request #11353: upgrade-all and upgrade commands to upgrade within the same stability level [dufuz] + * Implement Request #13015: Add https discovery for channel.xml [dufuz / initial patch by Martin Roos] + * Implement Request #13927: install-pear.php should have option to set www_dir [timj] + * Implement Request #14324: Make the pear install command behave similar to apt-get [dufuz] + * Implement Request #14325: make pear upgrade with no params behave like pear upgrade-all [dufuz] + - upgrade-all can be considered deprecated in favor of calling upgrade with no parameters to replicate + better what other package managers are doing. upgrade-all will still work as intended. + * Implement Request #14504: add a channel parameter support to the upgrade function [dufuz] + - Options -c ezc and --channel=ezc got added to upgrade and upgrade-all to allow for + channel specific upgrades + * Implement Request #14556: install-pear-nozlib.phar should get download_dir config and other options [cweiske] + * Implement Request #15566: Add doc.php.net as a default channel [dufuz / saltybeagle] + + * Fix PHP Bug #43857: --program-suffix not always reflected everywhere [cellog] + * Fix PHP Bug #47323: strotime warnings in make install [dufuz] + + * Fix Bug #13908: pear info command and maintainers inactive not mentioned [dufuz] + * Fix Bug #13926: install-pear.php does not set cfg_dir if -d option set with no -c option [timj] + * Fix Bug #13943: tests fail when php.exe path contains spaces [dufuz / jorrit] + * Fix Bug #13953: config-set/config-show with channel alias fail [cellog] + * Fix Bug #13958: When a phpt tests exit() or die() xdebug coverage is not generated, patch by izi (David Jean Louis) [izi / dufuz] + * Fix Bug #14041: Unpredictable unit test processing sequence [dufuz] + * Fix Bug #14140: Strict warning not suppressed in the shutdown function [dufuz] + * Fix Bug #14210: pear list -ia brings warnings [dufuz] + * Fix Bug #14274: PEAR packager mangles package.xml encoding, then complains about it [dufuz] + * Fix Bug #14287: cannot upgrade from stable to beta via -beta when config is set to stable [dufuz] + * Fix Bug #14300: Package files themselves can not be served over https [dufuz / initial patch by Martin Roos] + * Fix Bug #14437: openbasedir warning when loading config [dufuz] + * Fix Bug #14558: PackageFile.php creates tmp directory outside configured temp_dir [cweiske] + * Fix Bug #14947: downloadHttp() is missing Host part of the HTTP Request when using Proxy [ifeghali] + * Fix Bug #14977: PEAR/Frontend.php doesn't require_once PEAR.php [dufuz] + * Fix Bug #15750: Unreachable code in PEAR_Downloader [dufuz] + * Fix Bug #15979: Package files incorrectly removed when splitting a package into multiple pkgs [dufuz] + * Fix Bug #15914: pear upgrade installs different version if desired version not found [dufuz] + + NOTE! + Functions that have been deprecated for 3+ years in PEAR_Common, please take a moment + to migrate over to one of the alternatives that have ben provided: + * PEAR_Common->downloadHttp (use PEAR_Downloader->downloadHttp instead) + * PEAR_Common->infoFromTgzFile (use PEAR_PackageFile->fromTgzFile instead) + * PEAR_Common->infoFromDescriptionFile (use PEAR_PackageFile->fromPackageFile instead) + * PEAR_Common->infoFromString (use PEAR_PackageFile->fromXmlstring instead) + * PEAR_Common->infoFromArray (use PEAR_PackageFile->fromAnyFile instead) + * PEAR_Common->xmlFromInfo (use a PEAR_PackageFile_v* object's generator instead) + * PEAR_Common->validatePackageInfo (use the validation of PEAR_PackageFile objects) + * PEAR_Common->analyzeSourceCode (use a PEAR_PackageFile_v* object instead) + * PEAR_Common->detectDependencies (use PEAR_Downloader_Package->detectDependencies instead) + * PEAR_Common->buildProvidesArray (use PEAR_PackageFile_v1->_buildProvidesArray or + PEAR_PackageFile_v2_Validator->_buildProvidesArray) + + PHP 4.4 and 5.1.6 are now the minimum PHP requirements, for brave souls + pear upgrade -f PEAR will allow people with lower versions + to upgrade to this release but no guarantees will be made that it will work properly. + + Support for XML RPC channels has been dropped - The only ones that used it + (pear.php.net and pecl.php.net) have used the REST interface for years now. + SOAP support also removed as it was only proof of concept. + + Move codebase from the PHP License to New BSD 2 clause license + + + + 1.8.1 + 2009-04-15 + New BSD License + stable + * Fix Bug #16099 PEAR crash on PHP4 (parse error) [dufuz] + + + + 1.9.0RC1 + 2009-08-18 + New BSD License + beta + * Implement Request #16213: add alias to list-channels output [dufuz] +* Implement Request #16378: pear svntag [dufuz] +* Implement Request #16386: PEAR_Config::remove() does not support specifying a channel [timj] +* Implement Request #16396: package-dependencies should allow package names [dufuz] +* Fix Bug #11181: pear requests channel.xml from main server instead from mirror [dufuz] +* Fix Bug #14493: pear install --offline doesn't print out errors [dufuz] +* Fix Bug #11348: pear package-dependencies isn't well explained [dufuz] +* Fix Bug #16108: PEAR_PackageFile_Generator_v2 PHP4 parse error when running upgrade-all [dufuz] +* Fix Bug #16113: Installing certain packages fails due incorrect encoding handling [dufuz] +* Fix Bug #16122: PEAR RunTest failed to run as expected [dufuz] +* Fix Bug #16366: compiling 5.2.10 leads to non-functioning pear [dufuz] +* Fix Bug #16387: channel-logout does not support logging out from a non-default channel [timj] +* Fix Bug #16444: Setting preferred mirror fails [dufuz] +* Fix the shutdown functions where a index might not exist and thus raise a notice [derick] + + + + 1.9.0RC2 + 2009-08-20 + New BSD License + beta + * REST 1.4 file was occasionally being included but REST 1.4 is not intended for this release cycle [dufuz] + + + + 1.9.0RC3 + 2009-08-21 + New BSD License + beta + * Improved svntag support to handle packages like PEAR it self [dufuz] + + + + 1.9.0RC4 + 2009-08-23 + New BSD License + beta + * Fixed a problem where the original channel could not be set as a preferred_mirror again [dufuz] +* Make sure channel aliases can't be made to start with - [dufuz] +* Output issues with pear search [dufuz] +* Fixed couple of stray notices [dufuz] + + + + 1.9.0 + 2009-09-03 + New BSD License + stable + * Fix Bug #16547: The phar for PEAR installer uses ereg() which is deprecated [dufuz] + + + + +package2.xml100644 1750 1750 14156 10560475634 6540 + + Structures_Graph + pear.php.net + Graph datastructure manipulation library + Structures_Graph is a package for creating and manipulating graph datastructures. It allows building of directed +and undirected graphs, with data and metadata stored in nodes. The library provides functions for graph traversing +as well as for characteristic extraction from the graph topology. + + Sérgio Carvalho + sergiosgc + sergio.carvalho@portugalmail.com + yes + + 2007-02-01 + + + 1.0.2 + 1.0.0 + + + stable + stable + + LGPL + - Bug #9682 only variables can be returned by reference +- fix Bug #9661 notice in Structures_Graph_Manipulator_Topological::sort() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.2.0 + + + 1.4.3 + + + + + + + + 1.0.2 + 1.0.0 + + + stable + stable + + 2007-01-07 + LGPL + - Bug #9682 only variables can be returned by reference +- fix Bug #9661 notice in Structures_Graph_Manipulator_Topological::sort() + + + +Structures_Graph-1.0.2/docs/html/media/banner.css100644 1750 1750 651 10560475634 15166 body +{ + background-color: #CCCCFF; + margin: 0px; + padding: 0px; +} + +/* Banner (top bar) classes */ + +.banner { } + +.banner-menu +{ + clear: both; + padding: .5em; + border-top: 2px solid #6666AA; +} + +.banner-title +{ + text-align: right; + font-size: 20pt; + font-weight: bold; + margin: .2em; +} + +.package-selector +{ + background-color: #AAAADD; + border: 1px solid black; + color: yellow; +} +Structures_Graph-1.0.2/docs/html/media/stylesheet.css100644 1750 1750 12012 10560475634 16144 a { color: #336699; text-decoration: none; } +a:hover { color: #6699CC; text-decoration: underline; } +a:active { color: #6699CC; text-decoration: underline; } + +body { background : #FFFFFF; } +body, table { font-family: Georgia, Times New Roman, Times, serif; font-size: 10pt } +p, li { line-height: 140% } +a img { border: 0px; } +dd { margin-left: 0px; padding-left: 1em; } + +/* Page layout/boxes */ + +.info-box {} +.info-box-title { margin: 1em 0em 0em 0em; padding: .25em; font-weight: normal; font-size: 14pt; border: 2px solid #999999; background-color: #CCCCFF } +.info-box-body { border: 1px solid #999999; padding: .5em; } +.nav-bar { font-size: 8pt; white-space: nowrap; text-align: right; padding: .2em; margin: 0em 0em 1em 0em; } + +.oddrow { background-color: #F8F8F8; border: 1px solid #AAAAAA; padding: .5em; margin-bottom: 1em} +.evenrow { border: 1px solid #AAAAAA; padding: .5em; margin-bottom: 1em} + +.page-body { max-width: 800px; margin: auto; } +.tree dl { margin: 0px } + +/* Index formatting classes */ + +.index-item-body { margin-top: .5em; margin-bottom: .5em} +.index-item-description { margin-top: .25em } +.index-item-details { font-weight: normal; font-style: italic; font-size: 8pt } +.index-letter-section { background-color: #EEEEEE; border: 1px dotted #999999; padding: .5em; margin-bottom: 1em} +.index-letter-title { font-size: 12pt; font-weight: bold } +.index-letter-menu { text-align: center; margin: 1em } +.index-letter { font-size: 12pt } + +/* Docbook classes */ + +.description {} +.short-description { font-weight: bold; color: #666666; } +.tags { padding-left: 0em; margin-left: 3em; color: #666666; list-style-type: square; } +.parameters { padding-left: 0em; margin-left: 3em; font-style: italic; list-style-type: square; } +.redefinitions { font-size: 8pt; padding-left: 0em; margin-left: 2em; } +.package { } +.package-title { font-weight: bold; font-size: 14pt; border-bottom: 1px solid black } +.package-details { font-size: 85%; } +.sub-package { font-weight: bold; font-size: 120% } +.tutorial { border-width: thin; border-color: #0066ff } +.tutorial-nav-box { width: 100%; border: 1px solid #999999; background-color: #F8F8F8; } +.nav-button-disabled { color: #999999; } +.nav-button:active, +.nav-button:focus, +.nav-button:hover { background-color: #DDDDDD; outline: 1px solid #999999; text-decoration: none } +.folder-title { font-style: italic } + +/* Generic formatting */ + +.field { font-weight: bold; } +.detail { font-size: 8pt; } +.notes { font-style: italic; font-size: 8pt; } +.separator { background-color: #999999; height: 2px; } +.warning { color: #FF6600; } +.disabled { font-style: italic; color: #999999; } + +/* Code elements */ + +.line-number { } + +.class-table { width: 100%; } +.class-table-header { border-bottom: 1px dotted #666666; text-align: left} +.class-name { color: #000000; font-weight: bold; } + +.method-summary { padding-left: 1em; font-size: 8pt } +.method-header { } +.method-definition { margin-bottom: .3em } +.method-title { font-weight: bold; } +.method-name { font-weight: bold; } +.method-signature { font-size: 85%; color: #666666; margin: .5em 0em } +.method-result { font-style: italic; } + +.var-summary { padding-left: 1em; font-size: 8pt; } +.var-header { } +.var-title { margin-bottom: .3em } +.var-type { font-style: italic; } +.var-name { font-weight: bold; } +.var-default {} +.var-description { font-weight: normal; color: #000000; } + +.include-title { } +.include-type { font-style: italic; } +.include-name { font-weight: bold; } + +.const-title { } +.const-name { font-weight: bold; } + +/* Syntax highlighting */ + +.src-code { border: 1px solid #336699; padding: 1em; background-color: #EEEEEE; } + +.src-comm { color: green; } +.src-id { } +.src-inc { color: #0000FF; } +.src-key { color: #0000FF; } +.src-num { color: #CC0000; } +.src-str { color: #66cccc; } +.src-sym { font-weight: bold; } +.src-var { } + +.src-php { font-weight: bold; } + +.src-doc { color: #009999 } +.src-doc-close-template { color: #0000FF } +.src-doc-coretag { color: #0099FF; font-weight: bold } +.src-doc-inlinetag { color: #0099FF } +.src-doc-internal { color: #6699cc } +.src-doc-tag { color: #0080CC } +.src-doc-template { color: #0000FF } +.src-doc-type { font-style: italic } +.src-doc-var { font-style: italic } + +/* tutorial */ + +.authors { } +.author { font-style: italic; font-weight: bold } +.author-blurb { margin: .5em 0em .5em 2em; font-size: 85%; font-weight: normal; font-style: normal } +.example { border: 1px dashed #999999; background-color: #EEEEEE; padding: .5em } +.listing { border: 1px dashed #999999; background-color: #EEEEEE; padding: .5em; white-space: nowrap } +.release-info { font-size: 85%; font-style: italic; margin: 1em 0em } +.ref-title-box { } +.ref-title { } +.ref-purpose { font-style: italic; color: #666666 } +.ref-synopsis { } +.title { font-weight: bold; margin: 1em 0em 0em 0em; padding: .25em; border: 2px solid #999999; background-color: #CCCCFF } +.cmd-synopsis { margin: 1em 0em } +.cmd-title { font-weight: bold } +.toc { margin-left: 2em; padding-left: 0em } + +Structures_Graph-1.0.2/docs/html/Structures_Graph/Structures_Graph.html100644 1750 1750 21655 10560475634 21675 + + + + + Docs For Class Structures_Graph + + + + +
    +

    Class Structures_Graph

    + + +
    +
    Description
    + +
    + +

    The Structures_Graph class represents a graph data structure.

    +

    A Graph is a data structure composed by a set of nodes, connected by arcs. Graphs may either be directed or undirected. In a directed graph, arcs are directional, and can be traveled only one way. In an undirected graph, arcs are bidirectional, and can be traveled both ways.

    + +

    + Located in /Structures/Graph.php (line 56) +

    + + +
    
    +	
    +			
    +
    + + + + +
    +
    Method Summary
    + +
    +
    + +
    + Structures_Graph + Structures_Graph + ([boolean $directed = true]) +
    + +
    + void + addNode + (Structures_Graph_Node &$newNode) +
    + +
    + array + &getNodes + () +
    + +
    + boolean + isDirected + () +
    + +
    + void + removeNode + (Structures_Graph_Node &$node) +
    +
    +
    +
    + + + +
    +
    Methods
    + +
    + + +
    + +
    + Constructor Structures_Graph (line 76) +
    + + +

    Constructor

    +
      +
    • access: public
    • +
    + +
    + Structures_Graph + + Structures_Graph + + ([boolean $directed = true]) +
    + +
      +
    • + boolean + $directed: Set to true if the graph is directed. Set to false if it is not directed. (Optional, defaults to true)
    • +
    + + +
    + +
    + +
    + addNode (line 102) +
    + + +

    Add a Node to the Graph

    +
      +
    • access: public
    • +
    + +
    + void + + addNode + + (Structures_Graph_Node &$newNode) +
    + + + + +
    + +
    + +
    + getNodes (line 151) +
    + + +

    Return the node set, in no particular order. For ordered node sets, use a Graph Manipulator insted.

    + + +
    + array + + &getNodes + + () +
    + + + +
    + +
    + +
    + isDirected (line 89) +
    + + +

    Return true if a graph is directed

    +
      +
    • return: true if the graph is directed
    • +
    • access: public
    • +
    + +
    + boolean + + isDirected + + () +
    + + + +
    + +
    + +
    + removeNode (line 138) +
    + + +

    Remove a Node from the Graph

    +
      +
    • access: public
    • +
    • todo: This is unimplemented
    • +
    + +
    + void + + removeNode + + (Structures_Graph_Node &$node) +
    + + + + +
    + +
    +
    + +

    + Documentation generated on Fri, 30 Jan 2004 16:37:28 +0000 by phpDocumentor 1.2.3 +

    +
    +Structures_Graph-1.0.2/docs/html/Structures_Graph/Structures_Graph_Manipulator_AcyclicTest.html100644 1750 1750 7560 10560475634 26516 + + + + + Docs For Class Structures_Graph_Manipulator_AcyclicTest + + + + +
    +

    Class Structures_Graph_Manipulator_AcyclicTest

    + + +
    +
    Description
    + +
    + +

    The Structures_Graph_Manipulator_AcyclicTest is a graph manipulator which tests whether a graph contains a cycle.

    +

    The definition of an acyclic graph used in this manipulator is that of a DAG. The graph must be directed, or else it is considered cyclic, even when there are no arcs.

    + +

    + Located in /Structures/Graph/Manipulator/AcyclicTest.php (line 55) +

    + + +
    
    +	
    +			
    +
    + + + + +
    +
    Method Summary
    + +
    +
    + +
    + boolean + isAcyclic + (mixed &$graph) +
    +
    +
    +
    + + + +
    +
    Methods
    + +
    + + +
    + +
    + isAcyclic (line 126) +
    + + +

    isAcyclic returns true if a graph contains no cycles, false otherwise.

    +
      +
    • return: true iff graph is acyclic
    • +
    • access: public
    • +
    + +
    + boolean + + isAcyclic + + (mixed &$graph) +
    + + + +
    + +
    +
    + +

    + Documentation generated on Fri, 30 Jan 2004 16:37:28 +0000 by phpDocumentor 1.2.3 +

    +
    +././@LongLink000 145 0 4476 LStructures_Graph-1.0.2/docs/html/Structures_Graph/Structures_Graph_Manipulator_TopologicalSorter.htmlStructures_Graph-1.0.2/docs/html/Structures_Graph/Structures_Graph_Manipulator_TopologicalSorter.htm100644 1750 1750 10313 10560475634 27614 + + + + + Docs For Class Structures_Graph_Manipulator_TopologicalSorter + + + + +
    +

    Class Structures_Graph_Manipulator_TopologicalSorter

    + + +
    +
    Description
    + +
    + +

    The Structures_Graph_Manipulator_TopologicalSorter is a manipulator which is able to return the set of nodes in a graph, sorted by topological order.

    +

    A graph may only be sorted topologically iff it's a DAG. You can test it with the Structures_Graph_Manipulator_AcyclicTest.

    + +

    + Located in /Structures/Graph/Manipulator/TopologicalSorter.php (line 58) +

    + + +
    
    +	
    +			
    +
    + + + + +
    +
    Method Summary
    + +
    +
    + +
    + array + sort + (mixed &$graph) +
    +
    +
    +
    + + + +
    +
    Methods
    + +
    + + +
    + +
    + sort (line 133) +
    + + +

    sort returns the graph's nodes, sorted by topological order.

    +

    The result is an array with as many entries as topological levels. Each entry in this array is an array of nodes within the given topological level.

    +
      +
    • return: The graph's nodes, sorted by topological order.
    • +
    • access: public
    • +
    + +
    + array + + sort + + (mixed &$graph) +
    + + + +
    + +
    +
    + +

    + Documentation generated on Fri, 30 Jan 2004 16:37:29 +0000 by phpDocumentor 1.2.3 +

    +
    +Structures_Graph-1.0.2/docs/html/Structures_Graph/Structures_Graph_Node.html100644 1750 1750 51135 10560475634 22636 + + + + + Docs For Class Structures_Graph_Node + + + + +
    +

    Class Structures_Graph_Node

    + + +
    +
    Description
    + +
    + +

    The Structures_Graph_Node class represents a Node that can be member of a graph node set.

    +

    A graph node can contain data. Under this API, the node contains default data, and key index data. It behaves, thus, both as a regular data node, and as a dictionary (or associative array) node.

    Regular data is accessed via getData and setData. Key indexed data is accessed via getMetadata and setMetadata.

    + +

    + Located in /Structures/Graph/Node.php (line 57) +

    + + +
    
    +	
    +			
    +
    + + + + +
    +
    Method Summary
    + +
    +
    + +
    + Structures_Graph_Node + Structures_Graph_Node + () +
    + +
    + boolean + connectsTo + (mixed &$target) +
    + +
    + void + connectTo + (Structures_Graph &$destinationNode) +
    + +
    + mixed + &getData + () +
    + + + +
    + mixed + &getMetadata + (string $key, [boolean $nullIfNonexistent = false]) +
    + +
    + array + getNeighbours + () +
    + +
    + integer + inDegree + () +
    + +
    + boolean + metadataKeyExists + (string $key) +
    + +
    + integer + outDegree + () +
    + +
    + mixed + setData + (mixed $data) +
    + +
    + void + setGraph + (Structures_Graph &$graph) +
    + +
    + void + setMetadata + (string $key, mixed $data) +
    + +
    + void + unsetMetadata + (string $key) +
    +
    +
    +
    + + + +
    +
    Methods
    + +
    + + +
    + +
    + Constructor Structures_Graph_Node (line 78) +
    + + +

    Constructor

    +
      +
    • access: public
    • +
    + +
    + Structures_Graph_Node + + Structures_Graph_Node + + () +
    + + + +
    + +
    + +
    + connectsTo (line 275) +
    + + +

    Test wether this node has an arc to the target node

    +
      +
    • return: True if the two nodes are connected
    • +
    • access: public
    • +
    + +
    + boolean + + connectsTo + + (mixed &$target) +
    + + + +
    + +
    + +
    + connectTo (line 236) +
    + + +

    Connect this node to another one.

    +

    If the graph is not directed, the reverse arc, connecting $destinationNode to $this is also created.

    +
      +
    • access: public
    • +
    + +
    + void + + connectTo + + (Structures_Graph &$destinationNode) +
    + + + + +
    + +
    + +
    + getData (line 119) +
    + + +

    Node data getter.

    +

    Each graph node can contain a reference to one variable. This is the getter for that reference.

    +
      +
    • return: Data stored in node
    • +
    • access: public
    • +
    + +
    + mixed + + &getData + + () +
    + + + +
    + +
    + +
    + getGraph (line 90) +
    + + +

    Node graph getter

    +
      +
    • return: Graph where node is stored
    • +
    • access: public
    • +
    + +
    + Structures_Graph + + &getGraph + + () +
    + + + +
    + +
    + +
    + getMetadata (line 171) +
    + + +

    Node metadata getter

    +

    Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an associative array or in a dictionary. This method gets the data under the given key. If the key does not exist, an error will be thrown, so testing using metadataKeyExists might be needed.

    + + +
    + mixed + + &getMetadata + + (string $key, [boolean $nullIfNonexistent = false]) +
    + +
      +
    • + string + $key: Key
    • +
    • + boolean + $nullIfNonexistent: nullIfNonexistent (defaults to false).
    • +
    + + +
    + +
    + +
    + getNeighbours (line 262) +
    + + +

    Return nodes connected to this one.

    +
      +
    • return: Array of nodes
    • +
    • access: public
    • +
    + +
    + array + + getNeighbours + + () +
    + + + +
    + +
    + +
    + inDegree (line 309) +
    + + +

    Calculate the in degree of the node.

    +

    The indegree for a node is the number of arcs entering the node. For non directed graphs, the indegree is equal to the outdegree.

    +
      +
    • return: In degree of the node
    • +
    • access: public
    • +
    + +
    + integer + + inDegree + + () +
    + + + +
    + +
    + +
    + metadataKeyExists (line 151) +
    + + +

    Test for existence of metadata under a given key.

    +

    Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an associative array or in a dictionary. This method tests whether a given metadata key exists for this node.

    +
      +
    • access: public
    • +
    + +
    + boolean + + metadataKeyExists + + (string $key) +
    + +
      +
    • + string + $key: Key to test
    • +
    + + +
    + +
    + +
    + outDegree (line 333) +
    + + +

    Calculate the out degree of the node.

    +

    The outdegree for a node is the number of arcs exiting the node. For non directed graphs, the outdegree is always equal to the indegree.

    +
      +
    • return: Out degree of the node
    • +
    • access: public
    • +
    + +
    + integer + + outDegree + + () +
    + + + +
    + +
    + +
    + setData (line 134) +
    + + +

    Node data setter

    +

    Each graph node can contain a reference to one variable. This is the setter for that reference.

    +
      +
    • return: Data to store in node
    • +
    • access: public
    • +
    + +
    + mixed + + setData + + (mixed $data) +
    + + + +
    + +
    + +
    + setGraph (line 104) +
    + + +

    Node graph setter. This method should not be called directly. Use Graph::addNode instead.

    + + +
    + void + + setGraph + + (Structures_Graph &$graph) +
    + + + + +
    + +
    + +
    + setMetadata (line 214) +
    + + +

    Node metadata setter

    +

    Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an associative array or in a dictionary. This method stores data under the given key. If the key already exists, previously stored data is discarded.

    +
      +
    • access: public
    • +
    + +
    + void + + setMetadata + + (string $key, mixed $data) +
    + +
      +
    • + string + $key: Key
    • +
    • + mixed + $data: Data
    • +
    + + +
    + +
    + +
    + unsetMetadata (line 196) +
    + + +

    Delete metadata by key

    +

    Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an associative array or in a dictionary. This method removes any data that might be stored under the provided key. If the key does not exist, no error is thrown, so it is safe using this method without testing for key existence.

    +
      +
    • access: public
    • +
    + +
    + void + + unsetMetadata + + (string $key) +
    + +
      +
    • + string + $key: Key
    • +
    + + +
    + +
    +
    + +

    + Documentation generated on Fri, 30 Jan 2004 16:37:29 +0000 by phpDocumentor 1.2.3 +

    +
    +Structures_Graph-1.0.2/docs/html/Structures_Graph/tutorial_Structures_Graph.pkg.html100644 1750 1750 11131 10560475634 24364 + + + + + Structures_Graph Tutorial + + + + +
    + +

    Structures_Graph Tutorial

    +

    A first tour of graph datastructure manipulation

    +

    Introduction

    Structures_Graph is a package for creating and manipulating graph datastructures. A graph is a set of objects, called nodes, connected by arcs. When used as a datastructure, usually nodes contain data, and arcs represent relationships between nodes. When arcs have a direction, and can be travelled only one way, graphs are said to be directed. When arcs have no direction, and can always be travelled both ways, graphs are said to be non directed.

    +

    Structures_Graph provides an object oriented API to create and directly query a graph, as well as a set of Manipulator classes to extract information from the graph.

    +

    Creating a Graph

    Creating a graph is done using the simple constructor: +

    +require_once 'Structures/Graph.php';
    +
    +$directedGraph =& new Structures_Graph(true);
    +$nonDirectedGraph =& new Structures_Graph(false);
    +    
    + and passing the constructor a flag telling it whether the graph should be directed. A directed graph will always be directed during its lifetime. It's a permanent characteristic.

    +

    To fill out the graph, we'll need to create some nodes, and then call Graph::addNode. +

    +require_once 'Structures/Graph/Node.php';
    +
    +$nodeOne =& new Structures_Graph_Node();
    +$nodeTwo =& new Structures_Graph_Node();
    +$nodeThree =& new Structures_Graph_Node();
    +
    +$directedGraph->addNode(&$nodeOne);
    +$directedGraph->addNode(&$nodeTwo);
    +$directedGraph->addNode(&$nodeThree);
    +    
    + and then setup the arcs: +
    +$nodeOne->connectTo($nodeTwo);
    +$nodeOne->connectTo($nodeThree);
    +    
    + Note that arcs can only be created after the nodes have been inserted into the graph.

    +

    Associating Data

    Graphs are only useful as datastructures if they can hold data. Structure_Graph stores data in nodes. Each node contains a setter and a getter for its data. +

    +$nodeOne->setData("Node One's Data is a String");
    +$nodeTwo->setData(1976);
    +$nodeThree->setData('Some other string');
    +
    +print("NodeTwo's Data is an integer: " . $nodeTwo->getData());
    +    

    +

    Structure_Graph nodes can also store metadata, alongside with the main data. Metadata differs from regular data just because it is stored under a key, making it possible to store more than one data reference per node. The metadata getter and setter need the key to perform the operation: +

    +$nodeOne->setMetadata('example key', "Node One's Sample Metadata");
    +print("Metadata stored under key 'example key' in node one: " . $nodeOne->getMetadata('example key'));
    +$nodeOne->unsetMetadata('example key');
    +    

    +

    Querying a Graph

    Structures_Graph provides for basic querying of the graph: +

    +// Nodes are able to calculate their indegree and outdegree
    +print("NodeOne's inDegree: " . $nodeOne->inDegree());
    +print("NodeOne's outDegree: " . $nodeOne->outDegree());
    +
    +// and naturally, nodes can report on their arcs
    +$arcs = $nodeOne->getNeighbours();
    +for ($i=0;$i<sizeof($arcs);$i++) {
    +    print("NodeOne has an arc to " . $arcs[$i]->getData());
    +}
    +    

    + + +

    + Documentation generated on Fri, 30 Jan 2004 16:37:28 +0000 by phpDocumentor 1.2.3 +

    +
    +././@LongLink000 144 0 4475 LStructures_Graph-1.0.2/docs/html/Structures_Graph/_Structures_Graph_Manipulator_AcyclicTest_php.htmlStructures_Graph-1.0.2/docs/html/Structures_Graph/_Structures_Graph_Manipulator_AcyclicTest_php.html100644 1750 1750 7650 10560475634 27524 + + + + + Docs for page AcyclicTest.php + + + + +
    +

    /Structures/Graph/Manipulator/AcyclicTest.php

    + + +
    +
    Description
    + +
    + +

    This file contains the definition of the Structures_Graph_Manipulator_AcyclicTest graph manipulator.

    + + +
    +
    + + +
    +
    Classes
    + +
    + + + + + + + + + +
    ClassDescription
    + Structures_Graph_Manipulator_AcyclicTest + + The Structures_Graph_Manipulator_AcyclicTest is a graph manipulator which tests whether a graph contains a cycle. +
    +
    +
    + + +
    +
    Includes
    + +
    + +
    + +
    + + require_once + ('PEAR.php') + (line 35) + +
    + + + +
    + +
    + +
    + + require_once + ('Structures/Graph.php') + (line 37) + +
    + + + +
    + +
    + +
    + + require_once + ('Structures/Graph/Node.php') + (line 39) + +
    + + + +
    +
    +
    + + + + +

    + Documentation generated on Fri, 30 Jan 2004 16:37:28 +0000 by phpDocumentor 1.2.3 +

    +
    +././@LongLink000 152 0 4474 LStructures_Graph-1.0.2/docs/html/Structures_Graph/_Structures_Graph_Manipulator_TopologicalSorter_php.htmlStructures_Graph-1.0.2/docs/html/Structures_Graph/_Structures_Graph_Manipulator_TopologicalSorter_ph100644 1750 1750 10731 10560475634 27657 + + + + + Docs for page TopologicalSorter.php + + + + +
    +

    /Structures/Graph/Manipulator/TopologicalSorter.php

    + + +
    +
    Description
    + +
    + +

    This file contains the definition of the Structures_Graph_Manipulator_TopologicalSorter class.

    + + +
    +
    + + +
    +
    Classes
    + +
    + + + + + + + + + +
    ClassDescription
    + Structures_Graph_Manipulator_TopologicalSorter + + The Structures_Graph_Manipulator_TopologicalSorter is a manipulator which is able to return the set of nodes in a graph, sorted by topological order. +
    +
    +
    + + +
    +
    Includes
    + +
    + +
    + +
    + + require_once + ('PEAR.php') + (line 35) + +
    + + + +
    + +
    + +
    + + require_once + ('Structures/Graph.php') + (line 37) + +
    + + + +
    + +
    + +
    + + require_once + ('Structures/Graph/Node.php') + (line 39) + +
    + + + +
    + +
    + +
    + + require_once + ('Structures/Graph/Manipulator/AcyclicTest.php') + (line 41) + +
    + + + +
    +
    +
    + + + + +

    + Documentation generated on Fri, 30 Jan 2004 16:37:29 +0000 by phpDocumentor 1.2.3 +

    +
    +Structures_Graph-1.0.2/docs/html/Structures_Graph/_Structures_Graph_Node_php.html100644 1750 1750 6526 10560475634 23630 + + + + + Docs for page Node.php + + + + +
    +

    /Structures/Graph/Node.php

    + + +
    +
    Description
    + +
    + +

    This file contains the definition of the Structures_Graph_Node class

    + + +
    +
    + + +
    +
    Classes
    + +
    + + + + + + + + + +
    ClassDescription
    + Structures_Graph_Node + + The Structures_Graph_Node class represents a Node that can be member of a graph node set. +
    +
    +
    + + +
    +
    Includes
    + +
    + +
    + +
    + + require_once + ('PEAR.php') + (line 35) + +
    + + + +
    + +
    + +
    + + require_once + ('Structures/Graph.php') + (line 37) + +
    + + + +
    +
    +
    + + + + +

    + Documentation generated on Fri, 30 Jan 2004 16:37:29 +0000 by phpDocumentor 1.2.3 +

    +
    +Structures_Graph-1.0.2/docs/html/Structures_Graph/_Structures_Graph_php.html100644 1750 1750 10416 10560475634 22674 + + + + + Docs for page Graph.php + + + + +
    +

    /Structures/Graph.php

    + + +
    +
    Description
    + +
    + +

    The Graph.php file contains the definition of the Structures_Graph class

    + + +
    +
    + + +
    +
    Classes
    + +
    + + + + + + + + + +
    ClassDescription
    + Structures_Graph + + The Structures_Graph class represents a graph data structure. +
    +
    +
    + + +
    +
    Includes
    + +
    + +
    + +
    + + require_once + ('Structures/Graph/Node.php') + (line 37) + +
    + + +

    Graph Node

    + +
    + +
    + +
    + + require_once + ('PEAR.php') + (line 35) + +
    + + +

    PEAR base classes

    + +
    +
    +
    + + +
    +
    Constants
    + +
    + +
    + +
    + + STRUCTURES_GRAPH_ERROR_GENERIC = 100 + (line 40) + +
    + + + + +
    +
    +
    + + + +

    + Documentation generated on Fri, 30 Jan 2004 16:37:28 +0000 by phpDocumentor 1.2.3 +

    +
    +Structures_Graph-1.0.2/docs/html/classtrees_Structures_Graph.html100644 1750 1750 2563 10560475634 20576 + + + + + + + + + + + +

    + +

    +

    Root class Structures_Graph

    + + +

    Root class Structures_Graph_Manipulator_AcyclicTest

    + + +

    Root class Structures_Graph_Manipulator_TopologicalSorter

    + + +

    Root class Structures_Graph_Node

    + + +

    + Documentation generated on Fri, 30 Jan 2004 16:37:28 +0000 by phpDocumentor 1.2.3 +

    + +Structures_Graph-1.0.2/docs/html/elementindex.html100644 1750 1750 37131 10560475634 15542 + + + + + + + + + + +

    Full index

    +

    Package indexes

    + +
    +
    + a + c + g + i + m + n + o + r + s + t + u +
    + + +
    +
    a
    + +
    +
    +
    +
    + addNode +
    +
    + +
    Add a Node to the Graph
    +
    +
    + AcyclicTest.php +
    +
    +
    AcyclicTest.php in AcyclicTest.php
    +
    +
    + +
    +
    c
    + +
    +
    +
    +
    + connectsTo +
    +
    + +
    Test wether this node has an arc to the target node
    +
    +
    + connectTo +
    +
    + +
    Connect this node to another one.
    +
    +
    + +
    +
    g
    + +
    +
    +
    +
    + getData +
    +
    + +
    Node data getter.
    +
    +
    + getGraph +
    +
    + +
    Node graph getter
    +
    +
    + getMetadata +
    +
    + +
    Node metadata getter
    +
    +
    + getNeighbours +
    +
    + +
    Return nodes connected to this one.
    +
    +
    + getNodes +
    +
    + +
    Return the node set, in no particular order. For ordered node sets, use a Graph Manipulator insted.
    +
    +
    + Graph.php +
    +
    +
    Graph.php in Graph.php
    +
    +
    + +
    +
    i
    + +
    +
    +
    +
    + inDegree +
    +
    + +
    Calculate the in degree of the node.
    +
    +
    + isAcyclic +
    +
    + +
    isAcyclic returns true if a graph contains no cycles, false otherwise.
    +
    +
    + isDirected +
    +
    + +
    Return true if a graph is directed
    +
    +
    + +
    +
    m
    + +
    +
    +
    +
    + metadataKeyExists +
    +
    + +
    Test for existence of metadata under a given key.
    +
    +
    + +
    +
    n
    + +
    +
    +
    +
    + Node.php +
    +
    +
    Node.php in Node.php
    +
    +
    + +
    +
    o
    + +
    +
    +
    +
    + outDegree +
    +
    + +
    Calculate the out degree of the node.
    +
    +
    + +
    +
    r
    + +
    +
    +
    +
    + removeNode +
    +
    + +
    Remove a Node from the Graph
    +
    +
    + +
    +
    s
    + +
    +
    +
    +
    + setData +
    +
    + +
    Node data setter
    +
    +
    + setGraph +
    +
    + +
    Node graph setter. This method should not be called directly. Use Graph::addNode instead.
    +
    +
    + setMetadata +
    +
    + +
    Node metadata setter
    +
    +
    + sort +
    +
    + +
    sort returns the graph's nodes, sorted by topological order.
    +
    +
    + Structures_Graph +
    +
    +
    Structures_Graph in Graph.php
    +
    The Structures_Graph class represents a graph data structure.
    +
    +
    + Structures_Graph +
    +
    + +
    Constructor
    +
    +
    + STRUCTURES_GRAPH_ERROR_GENERIC +
    +
    + +
    +
    + Structures_Graph_Manipulator_AcyclicTest +
    +
    + +
    The Structures_Graph_Manipulator_AcyclicTest is a graph manipulator which tests whether a graph contains a cycle.
    +
    +
    + Structures_Graph_Manipulator_TopologicalSorter +
    +
    + +
    The Structures_Graph_Manipulator_TopologicalSorter is a manipulator which is able to return the set of nodes in a graph, sorted by topological order.
    +
    +
    + Structures_Graph_Node +
    +
    + +
    Constructor
    +
    +
    + Structures_Graph_Node +
    +
    + +
    The Structures_Graph_Node class represents a Node that can be member of a graph node set.
    +
    +
    + +
    +
    t
    + +
    +
    +
    +
    + TopologicalSorter.php +
    +
    +
    TopologicalSorter.php in TopologicalSorter.php
    +
    +
    + +
    +
    u
    + +
    +
    +
    +
    + unsetMetadata +
    +
    + +
    Delete metadata by key
    +
    +
    + +
    + a + c + g + i + m + n + o + r + s + t + u +
    +Structures_Graph-1.0.2/docs/html/elementindex_Structures_Graph.html100644 1750 1750 37637 10560475634 21141 + + + + + + + + + + +

    [Structures_Graph] element index

    +All elements +
    +
    + a + c + g + i + m + n + o + r + s + t + u +
    + + +
    +
    a
    + +
    +
    +
    +
    + addNode +
    +
    + +
    Add a Node to the Graph
    +
    +
    + AcyclicTest.php +
    +
    +
    AcyclicTest.php in AcyclicTest.php
    +
    +
    + +
    +
    c
    + +
    +
    +
    +
    + connectsTo +
    +
    + +
    Test wether this node has an arc to the target node
    +
    +
    + connectTo +
    +
    + +
    Connect this node to another one.
    +
    +
    + +
    +
    g
    + +
    +
    +
    +
    + getData +
    +
    + +
    Node data getter.
    +
    +
    + getGraph +
    +
    + +
    Node graph getter
    +
    +
    + getMetadata +
    +
    + +
    Node metadata getter
    +
    +
    + getNeighbours +
    +
    + +
    Return nodes connected to this one.
    +
    +
    + getNodes +
    +
    + +
    Return the node set, in no particular order. For ordered node sets, use a Graph Manipulator insted.
    +
    +
    + Graph.php +
    +
    +
    Graph.php in Graph.php
    +
    +
    + +
    +
    i
    + +
    +
    +
    +
    + inDegree +
    +
    + +
    Calculate the in degree of the node.
    +
    +
    + isAcyclic +
    +
    + +
    isAcyclic returns true if a graph contains no cycles, false otherwise.
    +
    +
    + isDirected +
    +
    + +
    Return true if a graph is directed
    +
    +
    + +
    +
    m
    + +
    +
    +
    +
    + metadataKeyExists +
    +
    + +
    Test for existence of metadata under a given key.
    +
    +
    + +
    +
    n
    + +
    +
    +
    +
    + Node.php +
    +
    +
    Node.php in Node.php
    +
    +
    + +
    +
    o
    + +
    +
    +
    +
    + outDegree +
    +
    + +
    Calculate the out degree of the node.
    +
    +
    + +
    +
    r
    + +
    +
    +
    +
    + removeNode +
    +
    + +
    Remove a Node from the Graph
    +
    +
    + +
    +
    s
    + +
    +
    +
    +
    + setData +
    +
    + +
    Node data setter
    +
    +
    + setGraph +
    +
    + +
    Node graph setter. This method should not be called directly. Use Graph::addNode instead.
    +
    +
    + setMetadata +
    +
    + +
    Node metadata setter
    +
    +
    + sort +
    +
    + +
    sort returns the graph's nodes, sorted by topological order.
    +
    +
    + Structures_Graph +
    +
    +
    Structures_Graph in Graph.php
    +
    The Structures_Graph class represents a graph data structure.
    +
    +
    + Structures_Graph +
    +
    + +
    Constructor
    +
    +
    + STRUCTURES_GRAPH_ERROR_GENERIC +
    +
    + +
    +
    + Structures_Graph_Manipulator_AcyclicTest +
    +
    + +
    The Structures_Graph_Manipulator_AcyclicTest is a graph manipulator which tests whether a graph contains a cycle.
    +
    +
    + Structures_Graph_Manipulator_TopologicalSorter +
    +
    + +
    The Structures_Graph_Manipulator_TopologicalSorter is a manipulator which is able to return the set of nodes in a graph, sorted by topological order.
    +
    +
    + Structures_Graph_Node +
    +
    + +
    Constructor
    +
    +
    + Structures_Graph_Node +
    +
    + +
    The Structures_Graph_Node class represents a Node that can be member of a graph node set.
    +
    +
    + +
    +
    t
    + +
    +
    +
    +
    + TopologicalSorter.php +
    +
    +
    TopologicalSorter.php in TopologicalSorter.php
    +
    +
    + +
    +
    u
    + +
    +
    +
    +
    + unsetMetadata +
    +
    + +
    Delete metadata by key
    +
    +
    + +
    + a + c + g + i + m + n + o + r + s + t + u +
    +Structures_Graph-1.0.2/docs/html/errors.html100644 1750 1750 1344 10560475634 14352 + + + + + phpDocumentor Parser Errors and Warnings + + + + + Post-parsing
    +

    + Documentation generated on Fri, 30 Jan 2004 16:37:29 +0000 by phpDocumentor 1.2.3 +

    + +Structures_Graph-1.0.2/docs/html/index.html100644 1750 1750 2010 10560475634 14134 + + + + + Structures_Graph Documentation + + + + + + + + + + + <H2>Frame Alert</H2> + <P>This document is designed to be viewed using the frames feature. + If you see this message, you are using a non-frame-capable web client.</P> + + +Structures_Graph-1.0.2/docs/html/li_Structures_Graph.html100644 1750 1750 5044 10560475634 17027 + + + + + + + + + +
    Structures_Graph
    + +

    phpDocumentor v 1.2.3

    + +Structures_Graph-1.0.2/docs/html/packages.html100644 1750 1750 1673 10560475634 14621 + + + + + + + + + + + + + Structures_Graph-1.0.2/docs/html/todolist.html100644 1750 1750 1600 10560475634 14672 + + + + + Todo List + + + + +

    Todo List

    +

    Structures_Graph

    +

    Structures_Graph::removeNode()

    +
      +
    • This is unimplemented
    • +
    +

    + Documentation generated on Fri, 30 Jan 2004 16:37:29 +0000 by phpDocumentor 1.2.3 +

    + +Structures_Graph-1.0.2/docs/tutorials/Structures_Graph/Structures_Graph.pkg100644 1750 1750 7714 10560475634 22554 + + Structures_Graph Tutorial + A first tour of graph datastructure manipulation + + + Introduction + + Structures_Graph is a package for creating and manipulating graph datastructures. A graph is a set of objects, called nodes, connected by arcs. When used as a datastructure, usually nodes contain data, and arcs represent relationships between nodes. When arcs have a direction, and can be travelled only one way, graphs are said to be directed. When arcs have no direction, and can always be travelled both ways, graphs are said to be non directed. + + + Structures_Graph provides an object oriented API to create and directly query a graph, as well as a set of Manipulator classes to extract information from the graph. + + + + Creating a Graph + + Creating a graph is done using the simple constructor: + + + + and passing the constructor a flag telling it whether the graph should be directed. A directed graph will always be directed during its lifetime. It's a permanent characteristic. + + + To fill out the graph, we'll need to create some nodes, and then call Graph::addNode. + + addNode(&$nodeOne); +$directedGraph->addNode(&$nodeTwo); +$directedGraph->addNode(&$nodeThree); + ]]> + + and then setup the arcs: + + connectTo($nodeTwo); +$nodeOne->connectTo($nodeThree); + ]]> + + Note that arcs can only be created after the nodes have been inserted into the graph. + + + + Associating Data + + Graphs are only useful as datastructures if they can hold data. Structure_Graph stores data in nodes. Each node contains a setter and a getter for its data. + + setData("Node One's Data is a String"); +$nodeTwo->setData(1976); +$nodeThree->setData('Some other string'); + +print("NodeTwo's Data is an integer: " . $nodeTwo->getData()); + ]]> + + + + Structure_Graph nodes can also store metadata, alongside with the main data. Metadata differs from regular data just because it is stored under a key, making it possible to store more than one data reference per node. The metadata getter and setter need the key to perform the operation: + + setMetadata('example key', "Node One's Sample Metadata"); +print("Metadata stored under key 'example key' in node one: " . $nodeOne->getMetadata('example key')); +$nodeOne->unsetMetadata('example key'); + ]]> + + + + + Querying a Graph + + Structures_Graph provides for basic querying of the graph: + + inDegree()); +print("NodeOne's outDegree: " . $nodeOne->outDegree()); + +// and naturally, nodes can report on their arcs +$arcs = $nodeOne->getNeighbours(); +for ($i=0;$igetData()); +} + ]]> + + + + +Structures_Graph-1.0.2/docs/generate.sh100644 1750 1750 555 10560475634 13315 #!/bin/sh +(cd ..; tar czf docs/arch.tgz "{arch}") +rm -Rf "../{arch}" +rm -Rf ./html +mkdir -p ./html +phpdoc --directory ../Structures,./tutorials --target ./html --title "Structures_Graph Documentation" --output "HTML:frames" --defaultpackagename structures_graph --defaultcategoryname structures --pear +(cd ..; tar --absolute-names -xzf docs/arch.tgz) +#rm arch.tgz +Structures_Graph-1.0.2/Structures/Graph/Manipulator/AcyclicTest.php100644 1750 1750 13160 10560475634 20732 | +// +-----------------------------------------------------------------------------+ +// +/** + * This file contains the definition of the Structures_Graph_Manipulator_AcyclicTest graph manipulator. + * + * @see Structures_Graph_Manipulator_AcyclicTest + * @package Structures_Graph + */ + +/* dependencies {{{ */ +/** */ +require_once 'PEAR.php'; +/** */ +require_once 'Structures/Graph.php'; +/** */ +require_once 'Structures/Graph/Node.php'; +/* }}} */ + +/* class Structures_Graph_Manipulator_AcyclicTest {{{ */ +/** + * The Structures_Graph_Manipulator_AcyclicTest is a graph manipulator + * which tests whether a graph contains a cycle. + * + * The definition of an acyclic graph used in this manipulator is that of a + * DAG. The graph must be directed, or else it is considered cyclic, even when + * there are no arcs. + * + * @author Sérgio Carvalho + * @copyright (c) 2004 by Sérgio Carvalho + * @package Structures_Graph + */ +class Structures_Graph_Manipulator_AcyclicTest { + /* _nonVisitedInDegree {{{ */ + /** + * + * This is a variant of Structures_Graph::inDegree which does + * not count nodes marked as visited. + * + * @access private + * @return integer Number of non-visited nodes that link to this one + */ + function _nonVisitedInDegree(&$node) { + $result = 0; + $graphNodes =& $node->_graph->getNodes(); + foreach (array_keys($graphNodes) as $key) { + if ((!$graphNodes[$key]->getMetadata('acyclic-test-visited')) && $graphNodes[$key]->connectsTo($node)) $result++; + } + return $result; + + } + /* }}} */ + + /* _isAcyclic {{{ */ + /** + * @access private + */ + function _isAcyclic(&$graph) { + // Mark every node as not visited + $nodes =& $graph->getNodes(); + $nodeKeys = array_keys($nodes); + $refGenerator = array(); + foreach($nodeKeys as $key) { + $refGenerator[] = false; + $nodes[$key]->setMetadata('acyclic-test-visited', $refGenerator[sizeof($refGenerator) - 1]); + } + + // Iteratively peel off leaf nodes + do { + // Find out which nodes are leafs (excluding visited nodes) + $leafNodes = array(); + foreach($nodeKeys as $key) { + if ((!$nodes[$key]->getMetadata('acyclic-test-visited')) && Structures_Graph_Manipulator_AcyclicTest::_nonVisitedInDegree($nodes[$key]) == 0) { + $leafNodes[] =& $nodes[$key]; + } + } + // Mark leafs as visited + for ($i=sizeof($leafNodes) - 1; $i>=0; $i--) { + $visited =& $leafNodes[$i]->getMetadata('acyclic-test-visited'); + $visited = true; + $leafNodes[$i]->setMetadata('acyclic-test-visited', $visited); + } + } while (sizeof($leafNodes) > 0); + + // If graph is a DAG, there should be no non-visited nodes. Let's try to prove otherwise + $result = true; + foreach($nodeKeys as $key) if (!$nodes[$key]->getMetadata('acyclic-test-visited')) $result = false; + + // Cleanup visited marks + foreach($nodeKeys as $key) $nodes[$key]->unsetMetadata('acyclic-test-visited'); + + return $result; + } + /* }}} */ + + /* isAcyclic {{{ */ + /** + * + * isAcyclic returns true if a graph contains no cycles, false otherwise. + * + * @return boolean true iff graph is acyclic + * @access public + */ + function isAcyclic(&$graph) { + // We only test graphs + if (!is_a($graph, 'Structures_Graph')) return Pear::raiseError('Structures_Graph_Manipulator_AcyclicTest::isAcyclic received an object that is not a Structures_Graph', STRUCTURES_GRAPH_ERROR_GENERIC); + if (!$graph->isDirected()) return false; // Only directed graphs may be acyclic + + return Structures_Graph_Manipulator_AcyclicTest::_isAcyclic($graph); + } + /* }}} */ +} +/* }}} */ +?> +Structures_Graph-1.0.2/Structures/Graph/Manipulator/TopologicalSorter.php100644 1750 1750 15046 10560475634 22203 | +// +-----------------------------------------------------------------------------+ +// +/** + * This file contains the definition of the Structures_Graph_Manipulator_TopologicalSorter class. + * + * @see Structures_Graph_Manipulator_TopologicalSorter + * @package Structures_Graph + */ + +/* dependencies {{{ */ +/** */ +require_once 'PEAR.php'; +/** */ +require_once 'Structures/Graph.php'; +/** */ +require_once 'Structures/Graph/Node.php'; +/** */ +require_once 'Structures/Graph/Manipulator/AcyclicTest.php'; +/* }}} */ + +/* class Structures_Graph_Manipulator_TopologicalSorter {{{ */ +/** + * The Structures_Graph_Manipulator_TopologicalSorter is a manipulator + * which is able to return the set of nodes in a graph, sorted by topological + * order. + * + * A graph may only be sorted topologically iff it's a DAG. You can test it + * with the Structures_Graph_Manipulator_AcyclicTest. + * + * @author Sérgio Carvalho + * @copyright (c) 2004 by Sérgio Carvalho + * @see Structures_Graph_Manipulator_AcyclicTest + * @package Structures_Graph + */ +class Structures_Graph_Manipulator_TopologicalSorter { + /* _nonVisitedInDegree {{{ */ + /** + * + * This is a variant of Structures_Graph::inDegree which does + * not count nodes marked as visited. + * + * @access private + * @return integer Number of non-visited nodes that link to this one + */ + function _nonVisitedInDegree(&$node) { + $result = 0; + $graphNodes =& $node->_graph->getNodes(); + foreach (array_keys($graphNodes) as $key) { + if ((!$graphNodes[$key]->getMetadata('topological-sort-visited')) && $graphNodes[$key]->connectsTo($node)) $result++; + } + return $result; + + } + /* }}} */ + + /* _sort {{{ */ + /** + * @access private + */ + function _sort(&$graph) { + // Mark every node as not visited + $nodes =& $graph->getNodes(); + $nodeKeys = array_keys($nodes); + $refGenerator = array(); + foreach($nodeKeys as $key) { + $refGenerator[] = false; + $nodes[$key]->setMetadata('topological-sort-visited', $refGenerator[sizeof($refGenerator) - 1]); + } + + // Iteratively peel off leaf nodes + $topologicalLevel = 0; + do { + // Find out which nodes are leafs (excluding visited nodes) + $leafNodes = array(); + foreach($nodeKeys as $key) { + if ((!$nodes[$key]->getMetadata('topological-sort-visited')) && Structures_Graph_Manipulator_TopologicalSorter::_nonVisitedInDegree($nodes[$key]) == 0) { + $leafNodes[] =& $nodes[$key]; + } + } + // Mark leafs as visited + $refGenerator[] = $topologicalLevel; + for ($i=sizeof($leafNodes) - 1; $i>=0; $i--) { + $visited =& $leafNodes[$i]->getMetadata('topological-sort-visited'); + $visited = true; + $leafNodes[$i]->setMetadata('topological-sort-visited', $visited); + $leafNodes[$i]->setMetadata('topological-sort-level', $refGenerator[sizeof($refGenerator) - 1]); + } + $topologicalLevel++; + } while (sizeof($leafNodes) > 0); + + // Cleanup visited marks + foreach($nodeKeys as $key) $nodes[$key]->unsetMetadata('topological-sort-visited'); + } + /* }}} */ + + /* sort {{{ */ + /** + * + * sort returns the graph's nodes, sorted by topological order. + * + * The result is an array with + * as many entries as topological levels. Each entry in this array is an array of nodes within + * the given topological level. + * + * @return array The graph's nodes, sorted by topological order. + * @access public + */ + function sort(&$graph) { + // We only sort graphs + if (!is_a($graph, 'Structures_Graph')) return Pear::raiseError('Structures_Graph_Manipulator_TopologicalSorter::sort received an object that is not a Structures_Graph', STRUCTURES_GRAPH_ERROR_GENERIC); + if (!Structures_Graph_Manipulator_AcyclicTest::isAcyclic($graph)) return Pear::raiseError('Structures_Graph_Manipulator_TopologicalSorter::sort received an graph that has cycles', STRUCTURES_GRAPH_ERROR_GENERIC); + + Structures_Graph_Manipulator_TopologicalSorter::_sort($graph); + $result = array(); + + // Fill out result array + $nodes =& $graph->getNodes(); + $nodeKeys = array_keys($nodes); + foreach($nodeKeys as $key) { + if (!array_key_exists($nodes[$key]->getMetadata('topological-sort-level'), $result)) $result[$nodes[$key]->getMetadata('topological-sort-level')] = array(); + $result[$nodes[$key]->getMetadata('topological-sort-level')][] =& $nodes[$key]; + $nodes[$key]->unsetMetadata('topological-sort-level'); + } + + return $result; + } + /* }}} */ +} +/* }}} */ +?> +Structures_Graph-1.0.2/Structures/Graph/Node.php100644 1750 1750 25232 10560475634 15120 | +// +-----------------------------------------------------------------------------+ +// +/** + * This file contains the definition of the Structures_Graph_Node class + * + * @see Structures_Graph_Node + * @package Structures_Graph + */ + +/* dependencies {{{ */ +/** */ +require_once 'PEAR.php'; +/** */ +require_once 'Structures/Graph.php'; +/* }}} */ + +/* class Structures_Graph_Node {{{ */ +/** + * The Structures_Graph_Node class represents a Node that can be member of a + * graph node set. + * + * A graph node can contain data. Under this API, the node contains default data, + * and key index data. It behaves, thus, both as a regular data node, and as a + * dictionary (or associative array) node. + * + * Regular data is accessed via getData and setData. Key indexed data is accessed + * via getMetadata and setMetadata. + * + * @author Sérgio Carvalho + * @copyright (c) 2004 by Sérgio Carvalho + * @package Structures_Graph + */ +/* }}} */ +class Structures_Graph_Node { + /* fields {{{ */ + /** + * @access private + */ + var $_data = null; + /** @access private */ + var $_metadata = array(); + /** @access private */ + var $_arcs = array(); + /** @access private */ + var $_graph = null; + /* }}} */ + + /* Constructor {{{ */ + /** + * + * Constructor + * + * @access public + */ + function Structures_Graph_Node() { + } + /* }}} */ + + /* getGraph {{{ */ + /** + * + * Node graph getter + * + * @return Structures_Graph Graph where node is stored + * @access public + */ + function &getGraph() { + return $this->_graph; + } + /* }}} */ + + /* setGraph {{{ */ + /** + * + * Node graph setter. This method should not be called directly. Use Graph::addNode instead. + * + * @param Structures_Graph Set the graph for this node. + * @see Structures_Graph::addNode() + * @access public + */ + function setGraph(&$graph) { + $this->_graph =& $graph; + } + /* }}} */ + + /* getData {{{ */ + /** + * + * Node data getter. + * + * Each graph node can contain a reference to one variable. This is the getter for that reference. + * + * @return mixed Data stored in node + * @access public + */ + function &getData() { + return $this->_data; + } + /* }}} */ + + /* setData {{{ */ + /** + * + * Node data setter + * + * Each graph node can contain a reference to one variable. This is the setter for that reference. + * + * @return mixed Data to store in node + * @access public + */ + function setData($data) { + $this->_data =& $data; + } + /* }}} */ + + /* metadataKeyExists {{{ */ + /** + * + * Test for existence of metadata under a given key. + * + * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an + * associative array or in a dictionary. This method tests whether a given metadata key exists for this node. + * + * @param string Key to test + * @return boolean + * @access public + */ + function metadataKeyExists($key) { + return array_key_exists($key, $this->_metadata); + } + /* }}} */ + + /* getMetadata {{{ */ + /** + * + * Node metadata getter + * + * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an + * associative array or in a dictionary. This method gets the data under the given key. If the key does + * not exist, an error will be thrown, so testing using metadataKeyExists might be needed. + * + * @param string Key + * @param boolean nullIfNonexistent (defaults to false). + * @return mixed Metadata Data stored in node under given key + * @see metadataKeyExists + * @access public + */ + function &getMetadata($key, $nullIfNonexistent = false) { + if (array_key_exists($key, $this->_metadata)) { + return $this->_metadata[$key]; + } else { + if ($nullIfNonexistent) { + $a = null; + return $a; + } else { + $a = Pear::raiseError('Structures_Graph_Node::getMetadata: Requested key does not exist', STRUCTURES_GRAPH_ERROR_GENERIC); + return $a; + } + } + } + /* }}} */ + + /* unsetMetadata {{{ */ + /** + * + * Delete metadata by key + * + * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an + * associative array or in a dictionary. This method removes any data that might be stored under the provided key. + * If the key does not exist, no error is thrown, so it is safe using this method without testing for key existence. + * + * @param string Key + * @access public + */ + function unsetMetadata($key) { + if (array_key_exists($key, $this->_metadata)) unset($this->_metadata[$key]); + } + /* }}} */ + + /* setMetadata {{{ */ + /** + * + * Node metadata setter + * + * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an + * associative array or in a dictionary. This method stores data under the given key. If the key already exists, + * previously stored data is discarded. + * + * @param string Key + * @param mixed Data + * @access public + */ + function setMetadata($key, $data) { + $this->_metadata[$key] =& $data; + } + /* }}} */ + + /* _connectTo {{{ */ + /** @access private */ + function _connectTo(&$destinationNode) { + $this->_arcs[] =& $destinationNode; + } + /* }}} */ + + /* connectTo {{{ */ + /** + * + * Connect this node to another one. + * + * If the graph is not directed, the reverse arc, connecting $destinationNode to $this is also created. + * + * @param Structures_Graph Node to connect to + * @access public + */ + function connectTo(&$destinationNode) { + // We only connect to nodes + if (!is_a($destinationNode, 'Structures_Graph_Node')) return Pear::raiseError('Structures_Graph_Node::connectTo received an object that is not a Structures_Graph_Node', STRUCTURES_GRAPH_ERROR_GENERIC); + // Nodes must already be in graphs to be connected + if ($this->_graph == null) return Pear::raiseError('Structures_Graph_Node::connectTo Tried to connect a node that is not in a graph', STRUCTURES_GRAPH_ERROR_GENERIC); + if ($destinationNode->getGraph() == null) return Pear::raiseError('Structures_Graph_Node::connectTo Tried to connect to a node that is not in a graph', STRUCTURES_GRAPH_ERROR_GENERIC); + // Connect here + $this->_connectTo($destinationNode); + // If graph is undirected, connect back + if (!$this->_graph->isDirected()) { + $destinationNode->_connectTo($this); + } + } + /* }}} */ + + /* getNeighbours {{{ */ + /** + * + * Return nodes connected to this one. + * + * @return array Array of nodes + * @access public + */ + function getNeighbours() { + return $this->_arcs; + } + /* }}} */ + + /* connectsTo {{{ */ + /** + * + * Test wether this node has an arc to the target node + * + * @return boolean True if the two nodes are connected + * @access public + */ + function connectsTo(&$target) { + $copy = $target; + $arcKeys = array_keys($this->_arcs); + foreach($arcKeys as $key) { + /* ZE1 chokes on this expression: + if ($target === $arc) return true; + so, we'll use more convoluted stuff + */ + $arc =& $this->_arcs[$key]; + $target = true; + if ($arc === true) { + $target = false; + if ($arc === false) { + $target = $copy; + return true; + } + } + } + $target = $copy; + return false; + } + /* }}} */ + + /* inDegree {{{ */ + /** + * + * Calculate the in degree of the node. + * + * The indegree for a node is the number of arcs entering the node. For non directed graphs, + * the indegree is equal to the outdegree. + * + * @return integer In degree of the node + * @access public + */ + function inDegree() { + if ($this->_graph == null) return 0; + if (!$this->_graph->isDirected()) return $this->outDegree(); + $result = 0; + $graphNodes =& $this->_graph->getNodes(); + foreach (array_keys($graphNodes) as $key) { + if ($graphNodes[$key]->connectsTo($this)) $result++; + } + return $result; + + } + /* }}} */ + + /* outDegree {{{ */ + /** + * + * Calculate the out degree of the node. + * + * The outdegree for a node is the number of arcs exiting the node. For non directed graphs, + * the outdegree is always equal to the indegree. + * + * @return integer Out degree of the node + * @access public + */ + function outDegree() { + if ($this->_graph == null) return 0; + return sizeof($this->_arcs); + } + /* }}} */ +} +?> +Structures_Graph-1.0.2/Structures/Graph.php100644 1750 1750 13163 10560475634 14233 | +// +-----------------------------------------------------------------------------+ +// +/** + * The Graph.php file contains the definition of the Structures_Graph class + * + * @see Structures_Graph + * @package Structures_Graph + */ + +/* dependencies {{{ */ +/** PEAR base classes */ +require_once 'PEAR.php'; +/** Graph Node */ +require_once 'Structures/Graph/Node.php'; +/* }}} */ + +define('STRUCTURES_GRAPH_ERROR_GENERIC', 100); + +/* class Structures_Graph {{{ */ +/** + * The Structures_Graph class represents a graph data structure. + * + * A Graph is a data structure composed by a set of nodes, connected by arcs. + * Graphs may either be directed or undirected. In a directed graph, arcs are + * directional, and can be traveled only one way. In an undirected graph, arcs + * are bidirectional, and can be traveled both ways. + * + * @author Sérgio Carvalho + * @copyright (c) 2004 by Sérgio Carvalho + * @package Structures_Graph + */ +/* }}} */ +class Structures_Graph { + /* fields {{{ */ + /** + * @access private + */ + var $_nodes = array(); + /** + * @access private + */ + var $_directed = false; + /* }}} */ + + /* Constructor {{{ */ + /** + * + * Constructor + * + * @param boolean Set to true if the graph is directed. Set to false if it is not directed. (Optional, defaults to true) + * @access public + */ + function Structures_Graph($directed = true) { + $this->_directed = $directed; + } + /* }}} */ + + /* isDirected {{{ */ + /** + * + * Return true if a graph is directed + * + * @return boolean true if the graph is directed + * @access public + */ + function isDirected() { + return (boolean) $this->_directed; + } + /* }}} */ + + /* addNode {{{ */ + /** + * + * Add a Node to the Graph + * + * @param Structures_Graph_Node The node to be added. + * @access public + */ + function addNode(&$newNode) { + // We only add nodes + if (!is_a($newNode, 'Structures_Graph_Node')) return Pear::raiseError('Structures_Graph::addNode received an object that is not a Structures_Graph_Node', STRUCTURES_GRAPH_ERROR_GENERIC); + // Graphs are node *sets*, so duplicates are forbidden. We allow nodes that are exactly equal, but disallow equal references. + foreach($this->_nodes as $key => $node) { + /* + ZE1 equality operators choke on the recursive cycle introduced by the _graph field in the Node object. + So, we'll check references the hard way (change $this->_nodes[$key] and check if the change reflects in + $node) + */ + $savedData = $this->_nodes[$key]; + $referenceIsEqualFlag = false; + $this->_nodes[$key] = true; + if ($node === true) { + $this->_nodes[$key] = false; + if ($node === false) $referenceIsEqualFlag = true; + } + $this->_nodes[$key] = $savedData; + if ($referenceIsEqualFlag) return Pear::raiseError('Structures_Graph::addNode received an object that is a duplicate for this dataset', STRUCTURES_GRAPH_ERROR_GENERIC); + } + $this->_nodes[] =& $newNode; + $newNode->setGraph($this); + } + /* }}} */ + + /* removeNode (unimplemented) {{{ */ + /** + * + * Remove a Node from the Graph + * + * @todo This is unimplemented + * @param Structures_Graph_Node The node to be removed from the graph + * @access public + */ + function removeNode(&$node) { + } + /* }}} */ + + /* getNodes {{{ */ + /** + * + * Return the node set, in no particular order. For ordered node sets, use a Graph Manipulator insted. + * + * @access public + * @see Structures_Graph_Manipulator_TopologicalSorter + * @return array The set of nodes in this graph + */ + function &getNodes() { + return $this->_nodes; + } + /* }}} */ +} +?> +Structures_Graph-1.0.2/tests/testCase/BasicGraph.php100644 1750 1750 22050 10560475634 15722 | +// +-----------------------------------------------------------------------------+ +// + +require_once 'Structures/Graph.php'; +require_once 'PHPUnit.php'; + +/** + * @access private + */ +class BasicGraph extends PHPUnit_TestCase +{ + var $_graph = null; + + // constructor of the test suite + function StringTest($name) { + $this->PHPUnit_TestCase($name); + } + + function setUp() { + } + + function tearDown() { + } + + function test_create_graph() { + $this->_graph = new Structures_Graph(); + $this->assertTrue(is_a($this->_graph, 'Structures_Graph')); + } + + function test_add_node() { + $this->_graph = new Structures_Graph(); + $data = 1; + $node = new Structures_Graph_Node($data); + $this->_graph->addNode($node); + $node = new Structures_Graph_Node($data); + $this->_graph->addNode($node); + $node = new Structures_Graph_Node($data); + $this->_graph->addNode($node); + } + + function test_connect_node() { + $this->_graph = new Structures_Graph(); + $data = 1; + $node1 = new Structures_Graph_Node($data); + $node2 = new Structures_Graph_Node($data); + $this->_graph->addNode($node1); + $this->_graph->addNode($node2); + $node1->connectTo($node2); + + $node =& $this->_graph->getNodes(); + $node =& $node[0]; + $node = $node->getNeighbours(); + $node =& $node[0]; + /* + ZE1 == and === operators fail on $node,$node2 because of the recursion introduced + by the _graph field in the Node object. So, we'll use the stupid method for reference + testing + */ + $node = true; + $this->assertTrue($node2); + $node = false; + $this->assertFalse($node2); + } + + function test_data_references() { + $this->_graph = new Structures_Graph(); + $data = 1; + $node = new Structures_Graph_Node(); + $node->setData(&$data); + $this->_graph->addNode($node); + $data = 2; + $dataInNode =& $this->_graph->getNodes(); + $dataInNode =& $dataInNode[0]; + $dataInNode =& $dataInNode->getData(); + $this->assertTrue($data === $dataInNode); + } + + function test_metadata_references() { + $this->_graph = new Structures_Graph(); + $data = 1; + $node = new Structures_Graph_Node(); + $node->setMetadata('5', &$data); + $data = 2; + $dataInNode =& $node->getMetadata('5'); + $this->assertTrue($data === $dataInNode); + } + + function test_metadata_key_exists() { + $this->_graph = new Structures_Graph(); + $data = 1; + $node = new Structures_Graph_Node(); + $node->setMetadata('5', $data); + $this->assertTrue($node->metadataKeyExists('5')); + $this->assertFalse($node->metadataKeyExists('1')); + } + + function test_directed_degree() { + $this->_graph = new Structures_Graph(true); + $node = array(); + $node[] = new Structures_Graph_Node(); + $node[] = new Structures_Graph_Node(); + $node[] = new Structures_Graph_Node(); + $this->_graph->addNode($node[0]); + $this->_graph->addNode($node[1]); + $this->_graph->addNode($node[2]); + $this->assertEquals(0, $node[0]->inDegree(), 'inDegree test failed for node 0 with 0 arcs'); + $this->assertEquals(0, $node[1]->inDegree(), 'inDegree test failed for node 1 with 0 arcs'); + $this->assertEquals(0, $node[2]->inDegree(), 'inDegree test failed for node 2 with 0 arcs'); + $this->assertEquals(0, $node[0]->outDegree(), 'outDegree test failed for node 0 with 0 arcs'); + $this->assertEquals(0, $node[1]->outDegree(), 'outDegree test failed for node 1 with 0 arcs'); + $this->assertEquals(0, $node[2]->outDegree(), 'outDegree test failed for node 2 with 0 arcs'); + $node[0]->connectTo($node[1]); + $this->assertEquals(0, $node[0]->inDegree(), 'inDegree test failed for node 0 with 1 arc'); + $this->assertEquals(1, $node[1]->inDegree(), 'inDegree test failed for node 1 with 1 arc'); + $this->assertEquals(0, $node[2]->inDegree(), 'inDegree test failed for node 2 with 1 arc'); + $this->assertEquals(1, $node[0]->outDegree(), 'outDegree test failed for node 0 with 1 arc'); + $this->assertEquals(0, $node[1]->outDegree(), 'outDegree test failed for node 1 with 1 arc'); + $this->assertEquals(0, $node[2]->outDegree(), 'outDegree test failed for node 2 with 1 arc'); + $node[0]->connectTo($node[2]); + $this->assertEquals(0, $node[0]->inDegree(), 'inDegree test failed for node 0 with 2 arcs'); + $this->assertEquals(1, $node[1]->inDegree(), 'inDegree test failed for node 1 with 2 arcs'); + $this->assertEquals(1, $node[2]->inDegree(), 'inDegree test failed for node 2 with 2 arcs'); + $this->assertEquals(2, $node[0]->outDegree(), 'outDegree test failed for node 0 with 2 arcs'); + $this->assertEquals(0, $node[1]->outDegree(), 'outDegree test failed for node 1 with 2 arcs'); + $this->assertEquals(0, $node[2]->outDegree(), 'outDegree test failed for node 2 with 2 arcs'); + } + + function test_undirected_degree() { + $this->_graph = new Structures_Graph(false); + $node = array(); + $node[] = new Structures_Graph_Node(); + $node[] = new Structures_Graph_Node(); + $node[] = new Structures_Graph_Node(); + $this->_graph->addNode($node[0]); + $this->_graph->addNode($node[1]); + $this->_graph->addNode($node[2]); + $this->assertEquals(0, $node[0]->inDegree(), 'inDegree test failed for node 0 with 0 arcs'); + $this->assertEquals(0, $node[1]->inDegree(), 'inDegree test failed for node 1 with 0 arcs'); + $this->assertEquals(0, $node[2]->inDegree(), 'inDegree test failed for node 2 with 0 arcs'); + $this->assertEquals(0, $node[0]->outDegree(), 'outDegree test failed for node 0 with 0 arcs'); + $this->assertEquals(0, $node[1]->outDegree(), 'outDegree test failed for node 1 with 0 arcs'); + $this->assertEquals(0, $node[2]->outDegree(), 'outDegree test failed for node 2 with 0 arcs'); + $node[0]->connectTo($node[1]); + $this->assertEquals(1, $node[0]->inDegree(), 'inDegree test failed for node 0 with 1 arc'); + $this->assertEquals(1, $node[1]->inDegree(), 'inDegree test failed for node 1 with 1 arc'); + $this->assertEquals(0, $node[2]->inDegree(), 'inDegree test failed for node 2 with 1 arc'); + $this->assertEquals(1, $node[0]->outDegree(), 'outDegree test failed for node 0 with 1 arc'); + $this->assertEquals(1, $node[1]->outDegree(), 'outDegree test failed for node 1 with 1 arc'); + $this->assertEquals(0, $node[2]->outDegree(), 'outDegree test failed for node 2 with 1 arc'); + $node[0]->connectTo($node[2]); + $this->assertEquals(2, $node[0]->inDegree(), 'inDegree test failed for node 0 with 2 arcs'); + $this->assertEquals(1, $node[1]->inDegree(), 'inDegree test failed for node 1 with 2 arcs'); + $this->assertEquals(1, $node[2]->inDegree(), 'inDegree test failed for node 2 with 2 arcs'); + $this->assertEquals(2, $node[0]->outDegree(), 'outDegree test failed for node 0 with 2 arcs'); + $this->assertEquals(1, $node[1]->outDegree(), 'outDegree test failed for node 1 with 2 arcs'); + $this->assertEquals(1, $node[2]->outDegree(), 'outDegree test failed for node 2 with 2 arcs'); + } +} +?> +Structures_Graph-1.0.2/tests/all-tests.php100644 1750 1750 4373 10560475634 14044 #!/usr/bin/php + | +// +-----------------------------------------------------------------------------+ +// + +// Place development Structures_Graph ahead in the include_path +ini_set('include_path', realpath(dirname(__FILE__) . "/..") . ":.:" . ini_get('include_path')); + +require_once 'testCase/BasicGraph.php'; +require_once 'PHPUnit.php'; + +$suite = new PHPUnit_TestSuite(); +$suite->addTest(new PHPUnit_TestSuite('BasicGraph')); +$result = PHPUnit::run($suite); + +echo $result->toString(); +?> +Structures_Graph-1.0.2/tests/README100644 1750 1750 0 10560475634 12202 Structures_Graph-1.0.2/genpackage.xml.pl100644 1750 1750 642 10560475634 13455 #!/usr/bin/perl +while (<>) { + if (!/FILESGOHERE/) { + print $_; + } else { + open FILELIST,'find Structures -type f | grep -v .arch-ids |'; + while () { + $md5sum = `md5sum $_`; + chomp($md5sum); + $md5sum = substr $md5sum, 0, 32; +# $_ =~ s/\//\\\//g; + chomp($_); + print " \n"; + } + } +} +Structures_Graph-1.0.2/LICENSE100644 1750 1750 63476 10560475634 11317 GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + +Structures_Graph-1.0.2/package.sh100644 1750 1750 3027 10560475634 12203 #!/bin/bash +VERSION=`tla tree-version 2>&1 | sed "s/^.*\([0-9][0-9]*\.[0-9][0-9]*\)$/\1/g"` +TARGET_DIR=BUILD/ +TARGET_DIRS=`find Structures -type d | grep -v .arch-ids` +mkdir -p $TARGET_DIR +./genpackage.xml.pl > BUILD/package.xml << EOF + + + Structures_Graph + Graph datastructure manipulation library + LGPL + + Structures_Graph is a package for creating and manipulating graph datastructures. It allows building of directed + and undirected graphs, with data and metadata stored in nodes. The library provides functions for graph traversing + as well as for characteristic extraction from the graph topology. + + + + sergiosgc + Sérgio Carvalho + sergio.carvalho@portugalmail.com + lead + + + + + 1.0.3 + 2007-01-30 + stable + + Version 1.0.3 is functionally equivalent to 1.0.2, but released with a v1.0 package.xml to deal with bug #9965:installation problem + + +FILESGOHERE + + + + PEAR + + +EOF +for dir in $TARGET_DIRS +do + mkdir -p $TARGET_DIR/$dir + cp `find $dir -maxdepth 1 -type f | grep -v .arch-ids` $TARGET_DIR/$dir +done +cp LICENSE BUILD +(cd BUILD; pear package) +rm -Rf BUILD/package.xml BUILD/LICENSE BUILD/Structures + + +Structures_Graph-1.0.2/publish.sh100644 1750 1750 411 10560475634 12230 #!/bin/sh +./package.sh +scp BUILD/*.tgz root@sergiocarvalho.com:/home/httpd/vhosts/com/sergiocarvalho/pear-base/pear +(cd docs; ./generate.sh) +scp -r docs/html/* root@iluvatar.portugalmail.pt:/home/httpd/vhosts/com/sergiocarvalho/pear-base/pear/docs/Structures_Graph +package.xml100644 1750 1750 7334 10560475634 6436 + + + Structures_Graph + Graph datastructure manipulation library + Structures_Graph is a package for creating and manipulating graph datastructures. It allows building of directed +and undirected graphs, with data and metadata stored in nodes. The library provides functions for graph traversing +as well as for characteristic extraction from the graph topology. + + + + sergiosgc + Sérgio Carvalho + sergio.carvalho@portugalmail.com + lead + + + + 1.0.2 + 2007-02-01 + LGPL + stable + - Bug #9682 only variables can be returned by reference +- fix Bug #9661 notice in Structures_Graph_Manipulator_Topological::sort() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +package.xml100644 1750 1750 36035 11117075467 6455 + + XML_Util + pear.php.net + XML utility class + Selection of methods that are often needed when working with XML documents. Functionality includes creating of attribute lists from arrays, creation of tags, validation of XML names and more. + + Chuck Burgess + ashnazg + ashnazg@php.net + yes + + + Stephan Schmidt + schst + schst@php-tools.net + no + + + Davey Shafik + davey + davey@php.net + no + + 2008-12-07 + + + 1.2.1 + 1.2.0 + + + stable + stable + + BSD License + Fixed Bug #14760: Bug in getDocTypeDeclaration() [ashnazg|fpospisil] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.3.0 + + + 1.4.3 + + + pcre + + + + + + + + 1.2.1 + 1.2.0 + + + stable + stable + + 2008-12-07 + BSD License + Fixed Bug #14760: Bug in getDocTypeDeclaration() [ashnazg|fpospisil] + + + + 1.2.0 + 1.2.0 + + + stable + stable + + 2008-07-26 + BSD License + Changed license to New BSD License (Req #13826 [ashnazg]) +Added a test suite against all API methods [ashnazg] +Switch to package.xml v2 [ashnazg] +Added Req #13839: Missing XHTML empty tags to collapse [ashnazg|drry] +Fixed Bug #5392: encoding of ISO-8859-1 is the only supported encoding [ashnazg] +Fixed Bug #4950: Incorrect CDATA serializing [ashnazg|drry] +-- (this fix differs from the one in v1.2.0a1) + + + + 1.2.0RC1 + 1.2.0 + + + beta + beta + + 2008-07-12 + BSD License + Changed license to New BSD License (Req #13826 [ashnazg]) +Added a test suite against all API methods [ashnazg] +Switch to package.xml v2 [ashnazg] +Added Req #13839: Missing XHTML empty tags to collapse [ashnazg|drry] +Fixed Bug #5392: encoding of ISO-8859-1 is the only supported encoding [ashnazg] +Fixed Bug #4950: Incorrect CDATA serializing [ashnazg|drry] +-- (this fix differs from the one in v1.2.0a1) + + + + 1.2.0a2 + 1.2.0 + + + alpha + alpha + + 2008-05-22 + BSD License + Changed license to New BSD License (Req #13826 [ashnazg]) +Added a test suite against all API methods [ashnazg] +Switch to package.xml v2 [ashnazg] +Added Req #13839: Missing XHTML empty tags to collapse [ashnazg|drry] +Fixed Bug #5392: encoding of ISO-8859-1 is the only supported encoding [ashnazg] +Fixed Bug #4950: Incorrect CDATA serializing [ashnazg|drry] +-- (this fix differs from the one in v1.2.0a1) + + + + 1.2.0a1 + 1.2.0 + + + alpha + alpha + + 2008-05-04 + BSD License + Changed license to New BSD License (Req #13826 [ashnazg]) +Added a test suite against all API methods [ashnazg] +Switch to package.xml v2 [ashnazg] +Fixed Bug #4950: Incorrect CDATA serializing [ashnazg|ja.doma] + + + + 1.1.4 + 1.1.4 + + + stable + stable + + 2006-12-16 + PHP License + - Fixed bug #9561: Not allowing underscores in middle of tags + + + + 1.1.2 + 1.1.2 + + + stable + stable + + 2006-12-01 + PHP License + - fixed bug #5419: isValidName() now checks for character classes +- implemented request #8196: added optional parameter to influence array sorting to createTag() createTagFromArray() and createStartElement() + + + + 1.1.1 + 1.1.1 + + + stable + stable + + 2004-12-23 + PHP License + - fixed bug in replaceEntities() and reverseEntities() in conjunction with XML_UTIL_ENTITIES_HTML +- createTag() and createTagFromArray() now accept XML_UTIL_ENTITIES_XML, XML_UTIL_ENTITIES_XML_REQUIRED, XML_UTIL_ENTITIES_HTML, XML_UTIL_ENTITIES_NONE and XML_UTIL_CDATA_SECTION as $replaceEntities parameter + + + + 1.1.0 + 1.1.0 + + + stable + stable + + 2004-11-19 + PHP License + - Added collapseEmptyTags (patch by Sebastian Bergmann and Thomas Duffey) + + + + 1.0.0 + 1.0.0 + + + stable + stable + + 2004-10-28 + PHP License + - Added reverseEntities() (request #2639) + + + + 0.6.1 + 0.6.1 + + + stable + stable + + 2004-10-28 + PHP License + - Added check for tag name (either as local part or qualified name) in createTagFromArray() (bug #1083) + + + + 0.6.0 + 0.6.0 + + + stable + stable + + 2004-06-07 + PHP License + - Fixed bug 1438 (namespaces not accepted for isValidName()) (thanks to davey) +- added optional parameter to replaceEntities() to define the set of entities to replace +- added optional parameter to attributesToString() to define, whether entities should be replaced (requested by Sebastian Bergmann) +- allowed second parameter to XML_Util::attributesToString() to be an array containing options (easier to use, if you only need to set the last parameter) +- introduced XML_Util::raiseError() to avoid the necessity of including PEAR.php, will only be included on error + + + + 0.6.0beta1 + 0.6.0beta1 + + + beta + beta + + 2004-05-24 + PHP License + - Fixed bug 1438 (namespaces not accepted for isValidName()) (thanks to davey) +- added optional parameter to replaceEntities() to define the set of entities to replace +- added optional parameter to attributesToString() to define, whether entities should be replaced (requested by Sebastian Bergmann) +- allowed second parameter to XML_Util::attributesToString() to be an array containing options (easier to use, if you only need to set the last parameter) +- introduced XML_Util::raiseError() to avoid the necessity of including PEAR.php, will only be included on error + + + + 0.5.2 + 0.5.2 + + + stable + stable + + 2003-11-22 + PHP License + now creates XHTML compliant empty tags (Davey), +minor whitespace fixes (Davey) + + + + 0.5.1 + 0.5.1 + + + stable + stable + + 2003-09-26 + PHP License + added default namespace parameter (optional) in splitQualifiedName() (requested by Sebastian Bergmann) + + + + 0.5 + 0.5 + + + stable + stable + + 2003-09-23 + PHP License + added support for multiline attributes in attributesToString(), createTag*() and createStartElement (requested by Yavor Shahpasov for XML_Serializer), +added createComment + + + + 0.4 + 0.4 + + + stable + stable + + 2003-09-21 + PHP License + added createCDataSection(), +added support for CData sections in createTag* methods, +fixed bug #23, +fixed bug in splitQualifiedName() + + + + 0.3 + 0.3 + + + stable + stable + + 2003-09-12 + PHP License + added createStartElement() and createEndElement() + + + + 0.2.1 + 0.2.1 + + + stable + stable + + 2003-09-05 + PHP License + fixed bug with zero as tag content in createTagFromArray and createTag + + + + 0.2 + 0.2 + + + stable + stable + + 2003-08-12 + PHP License + added XML_Util::getDocTypeDeclaration() + + + + 0.1.1 + 0.1.1 + + + stable + stable + + 2003-08-02 + PHP License + bugfix: removed bug in createTagFromArray + + + + 0.1 + 0.1 + + + stable + stable + + 2003-08-01 + PHP License + inital release + + + +XML_Util-1.2.1/examples/example.php100644 1750 1750 21775 11117075466 12420 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Util + * @subpackage Examples + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: example.php,v 1.17 2008/05/05 19:05:28 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Util + */ + + /** + * set error level + */ + error_reporting(E_ALL); + + require_once 'XML/Util.php'; + + /** + * replacing XML entities + */ + print 'replace XML entities:
    '; + print XML_Util::replaceEntities('This string contains < & >.'); + print "\n

    \n"; + + /** + * reversing XML entities + */ + print 'replace XML entities:
    '; + print XML_Util::reverseEntities('This string contains < & >.'); + print "\n

    \n"; + + /** + * building XML declaration + */ + print 'building XML declaration:
    '; + print htmlspecialchars(XML_Util::getXMLDeclaration()); + print "\n

    \n"; + + print 'building XML declaration with additional attributes:
    '; + print htmlspecialchars(XML_Util::getXMLDeclaration('1.0', 'UTF-8', true)); + print "\n

    \n"; + + /** + * building document type declaration + */ + print 'building DocType declaration:
    '; + print htmlspecialchars(XML_Util::getDocTypeDeclaration('package', + 'http://pear.php.net/dtd/package-1.0')); + print "\n

    \n"; + + print 'building DocType declaration with public ID (does not exist):
    '; + print htmlspecialchars(XML_Util::getDocTypeDeclaration('package', + array('uri' => 'http://pear.php.net/dtd/package-1.0', + 'id' => '-//PHP//PEAR/DTD PACKAGE 0.1'))); + print "\n

    \n"; + + print 'building DocType declaration with internal DTD:
    '; + print '
    ';
    +    print htmlspecialchars(XML_Util::getDocTypeDeclaration('package', 
    +        'http://pear.php.net/dtd/package-1.0', 
    +        ''));
    +    print '
    '; + print "\n

    \n"; + + /** + * creating an attribute string + */ + $att = array( + 'foo' => 'bar', + 'argh' => 'tomato' + ); + + print 'converting array to string:
    '; + print XML_Util::attributesToString($att); + print "\n

    \n"; + + + /** + * creating an attribute string with linebreaks + */ + $att = array( + 'foo' => 'bar', + 'argh' => 'tomato' + ); + + print 'converting array to string (including line breaks):
    '; + print '
    ';
    +    print XML_Util::attributesToString($att, true, true);
    +    print '
    '; + print "\n

    \n"; + + + /** + * splitting a qualified tag name + */ + print 'splitting qualified tag name:
    '; + print '
    ';
    +    print_r(XML_Util::splitQualifiedName('xslt:stylesheet'));
    +    print '
    '; + print "\n
    \n"; + + + /** + * splitting a qualified tag name (no namespace) + */ + print 'splitting qualified tag name (no namespace):
    '; + print '
    ';
    +    print_r(XML_Util::splitQualifiedName('foo'));
    +    print '
    '; + print "\n
    \n"; + + /** + * splitting a qualified tag name (no namespace, but default namespace specified) + */ + print 'splitting qualified tag name ' + . '(no namespace, but default namespace specified):
    '; + print '
    ';
    +    print_r(XML_Util::splitQualifiedName('foo', 'bar'));
    +    print '
    '; + print "\n
    \n"; + + /** + * verifying XML names + */ + print 'verifying \'My private tag\':
    '; + print '
    ';
    +    print_r(XML_Util::isValidname('My Private Tag'));
    +    print '
    '; + print "\n

    \n"; + + print 'verifying \'-MyTag\':
    '; + print '
    ';
    +    print_r(XML_Util::isValidname('-MyTag'));
    +    print '
    '; + print "\n

    \n"; + + /** + * creating an XML tag + */ + $tag = array( + 'namespace' => 'foo', + 'localPart' => 'bar', + 'attributes' => array('key' => 'value', 'argh' => 'fruit&vegetable'), + 'content' => 'I\'m inside the tag' + ); + + print 'creating a tag with namespace and local part:
    '; + print htmlentities(XML_Util::createTagFromArray($tag)); + print "\n

    \n"; + + /** + * creating an XML tag + */ + $tag = array( + 'qname' => 'foo:bar', + 'namespaceUri' => 'http://foo.com', + 'attributes' => array('key' => 'value', 'argh' => 'fruit&vegetable'), + 'content' => 'I\'m inside the tag' + ); + + print 'creating a tag with qualified name and namespaceUri:
    '; + print htmlentities(XML_Util::createTagFromArray($tag)); + print "\n

    \n"; + + /** + * creating an XML tag + */ + $tag = array( + 'qname' => 'bar', + 'namespaceUri' => 'http://foo.com', + 'attributes' => array('key' => 'value', 'argh' => 'fruit&vegetable') + ); + + print 'creating an empty tag without namespace but namespace Uri:
    '; + print htmlentities(XML_Util::createTagFromArray($tag)); + print "\n

    \n"; + + /** + * creating an XML tag with more namespaces + */ + $tag = array( + 'namespace' => 'foo', + 'localPart' => 'bar', + 'attributes' => array('key' => 'value', 'argh' => 'fruit&vegetable'), + 'content' => 'I\'m inside the tag', + 'namespaces' => array( + 'bar' => 'http://bar.com', + 'pear' => 'http://pear.php.net', + ) + ); + + print 'creating an XML tag with more namespaces:
    '; + print htmlentities(XML_Util::createTagFromArray($tag)); + print "\n

    \n"; + + /** + * creating an XML tag with a CData Section + */ + $tag = array( + 'qname' => 'foo', + 'attributes' => array('key' => 'value', 'argh' => 'fruit&vegetable'), + 'content' => 'I\'m inside the tag' + ); + + print 'creating a tag with CData section:
    '; + print htmlentities(XML_Util::createTagFromArray($tag, XML_UTIL_CDATA_SECTION)); + print "\n

    \n"; + + /** + * creating an XML tag with a CData Section + */ + $tag = array( + 'qname' => 'foo', + 'attributes' => array('key' => 'value', 'argh' => 'tütü'), + 'content' => + 'Also XHTML-tags can be created ' + . 'and HTML entities can be replaced Ä ä Ü ö <>.' + ); + + print 'creating a tag with HTML entities:
    '; + print htmlentities(XML_Util::createTagFromArray($tag, XML_UTIL_ENTITIES_HTML)); + print "\n

    \n"; + + /** + * creating an XML tag with createTag + */ + print 'creating a tag with createTag:
    '; + print htmlentities(XML_Util::createTag('myNs:myTag', + array('foo' => 'bar'), + 'This is inside the tag', + 'http://www.w3c.org/myNs#')); + print "\n

    \n"; + + + /** + * trying to create an XML tag with an array as content + */ + $tag = array( + 'qname' => 'bar', + 'content' => array('foo' => 'bar') + ); + print 'trying to create an XML tag with an array as content:
    '; + print '
    ';
    +    print_r(XML_Util::createTagFromArray($tag));
    +    print '
    '; + print "\n

    \n"; + + /** + * trying to create an XML tag without a name + */ + $tag = array( + 'attributes' => array('foo' => 'bar'), + ); + print 'trying to create an XML tag without a name:
    '; + print '
    ';
    +    print_r(XML_Util::createTagFromArray($tag));
    +    print '
    '; + print "\n

    \n"; +?> +XML_Util-1.2.1/examples/example2.php100644 1750 1750 11441 11117075466 12467 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Util + * @subpackage Examples + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: example2.php,v 1.11 2008/05/05 19:03:13 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Util + */ + + /** + * set error level + */ + error_reporting(E_ALL); + + require_once 'XML/Util.php'; + + /** + * creating a start element + */ + print 'creating a start element:
    '; + print htmlentities(XML_Util::createStartElement('myNs:myTag', + array('foo' => 'bar'), 'http://www.w3c.org/myNs#')); + print "\n

    \n"; + + + /** + * creating a start element + */ + print 'creating a start element:
    '; + print htmlentities(XML_Util::createStartElement('myTag', + array(), 'http://www.w3c.org/myNs#')); + print "\n

    \n"; + + /** + * creating a start element + */ + print 'creating a start element:
    '; + print '
    ';
    +    print htmlentities(XML_Util::createStartElement('myTag', 
    +        array('foo' => 'bar', 'argh' => 'tomato'), 
    +        'http://www.w3c.org/myNs#', true));
    +    print '
    '; + print "\n

    \n"; + + + /** + * creating an end element + */ + print 'creating an end element:
    '; + print htmlentities(XML_Util::createEndElement('myNs:myTag')); + print "\n

    \n"; + + /** + * creating a CData section + */ + print 'creating a CData section:
    '; + print htmlentities(XML_Util::createCDataSection('I am content.')); + print "\n

    \n"; + + /** + * creating a comment + */ + print 'creating a comment:
    '; + print htmlentities(XML_Util::createComment('I am a comment.')); + print "\n

    \n"; + + /** + * creating an XML tag with multiline mode + */ + $tag = array( + 'qname' => 'foo:bar', + 'namespaceUri' => 'http://foo.com', + 'attributes' => array('key' => 'value', 'argh' => 'fruit&vegetable'), + 'content' => 'I\'m inside the tag & contain dangerous chars' + ); + + print 'creating a tag with qualified name and namespaceUri:
    '; + print '
    ';
    +    print htmlentities(XML_Util::createTagFromArray($tag, 
    +        XML_UTIL_REPLACE_ENTITIES, true));
    +    print '
    '; + print "\n

    \n"; + + /** + * create an attribute string without replacing the entities + */ + $atts = array('series' => 'Starsky & Hutch', 'channel' => 'ABC'); + print 'creating a attribute string, ' + . 'entities in values already had been replaced:
    '; + print htmlentities(XML_Util::attributesToString($atts, + true, false, false, false, XML_UTIL_ENTITIES_NONE)); + print "\n

    \n"; + + /** + * using the array-syntax for attributesToString() + */ + $atts = array('series' => 'Starsky & Hutch', 'channel' => 'ABC'); + print 'using the array-syntax for attributesToString()
    '; + print htmlentities(XML_Util::attributesToString($atts, + array('entities' => XML_UTIL_ENTITIES_NONE))); + print "\n

    \n"; + + +?> +XML_Util-1.2.1/tests/AllTests.php100644 1750 1750 7060 11117075466 12013 + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: AllTests.php,v 1.5 2008/05/30 11:53:09 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Util + * @since 1.2.0a1 + */ + + +/** + * Check PHP version... PhpUnit v3+ requires at least PHP v5.1.4 + */ +if (version_compare(PHP_VERSION, "5.1.4") < 0) { + // Cannnot run test suites + echo 'Cannot run test suite via PhpUnit... requires at least PHP v5.1.4.' . PHP_EOL; + echo 'Use "pear run-tests -p xml_util" to run the PHPT tests directly.' . PHP_EOL +; + exit(1); +} + + +/** + * Derive the "main" method name + * @internal PhpUnit would have to rename PHPUnit_MAIN_METHOD to PHPUNIT_MAIN_METHOD + * to make this usage meet the PEAR CS... we cannot rename it here. + */ +if (!defined('PHPUnit_MAIN_METHOD')) { + define('PHPUnit_MAIN_METHOD', 'XML_Util_AllTests::main'); +} + + +/* + * Files needed by PhpUnit + */ +require_once 'PHPUnit/Framework.php'; +require_once 'PHPUnit/TextUI/TestRunner.php'; +require_once 'PHPUnit/Extensions/PhptTestSuite.php'; + +/* + * You must add each additional class-level test suite file here + */ +// there are no PhpUnit test files... only PHPTs.. so nothing is listed here + +/** + * directory where PHPT tests are located + */ +define('XML_UTIL_DIR_PHPT', dirname(__FILE__)); + +/** + * Master Unit Test Suite class for XML_Util + * + * This top-level test suite class organizes + * all class test suite files, + * so that the full suite can be run + * by PhpUnit or via "pear run-tests -up xml_util". + * + * @category XML + * @package XML_Util + * @subpackage UnitTesting + * @author Chuck Burgess + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_Util + * @since 1.2.0a1 + */ +class XML_Util_AllTests +{ + + /** + * Launches the TextUI test runner + * + * @return void + * @uses PHPUnit_TextUI_TestRunner + */ + public static function main() + { + PHPUnit_TextUI_TestRunner::run(self::suite()); + } + + + /** + * Adds all class test suites into the master suite + * + * @return PHPUnit_Framework_TestSuite a master test suite + * containing all class test suites + * @uses PHPUnit_Framework_TestSuite + */ + public static function suite() + { + $suite = new PHPUnit_Framework_TestSuite( + 'XML_Util Full Suite of Unit Tests'); + + /* + * You must add each additional class-level test suite name here + */ + // there are no PhpUnit test files... only PHPTs.. so nothing is listed here + + /* + * add PHPT tests + */ + $phpt = new PHPUnit_Extensions_PhptTestSuite(XML_UTIL_DIR_PHPT); + $suite->addTestSuite($phpt); + + return $suite; + } +} + +/** + * Call the main method if this file is executed directly + * @internal PhpUnit would have to rename PHPUnit_MAIN_METHOD to PHPUNIT_MAIN_METHOD + * to make this usage meet the PEAR CS... we cannot rename it here. + */ +if (PHPUnit_MAIN_METHOD == 'XML_Util_AllTests::main') { + XML_Util_AllTests::main(); +} + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ +?> +XML_Util-1.2.1/tests/testBasic_apiVersion.phpt100644 1750 1750 701 11117075466 14537 --TEST-- +XML_Util::apiVersion() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- + +--EXPECT-- +=====XML_Util::apiVersion() basic tests===== + +TEST: basic apiVersion() call +1.1 +XML_Util-1.2.1/tests/testBasic_attributesToString.phpt100644 1750 1750 7057 11117075466 16333 --TEST-- +XML_Util::attributesToString() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- + "bar", "boo" => "baz"); +$sort1 = array( + 'multiline' => true, + 'indent' => '----', + 'linebreak' => "^", + 'entities' => XML_UTIL_ENTITIES_XML, + 'sort' => true +); +$sort2 = array( + 'multiline' => true, + 'indent' => '----', + 'linebreak' => "^", + 'entities' => XML_UTIL_ENTITIES_XML, +); + +echo "TEST: basic usage" . PHP_EOL; +echo XML_Util::attributesToString($att) . PHP_EOL . PHP_EOL; + +echo "TEST: explicit \$sort = true" . PHP_EOL; +echo XML_Util::attributesToString($att, true) . PHP_EOL . PHP_EOL; + +echo "TEST: explicit \$sort = false" . PHP_EOL; +echo XML_Util::attributesToString($att, false) . PHP_EOL . PHP_EOL; + +echo "TEST: explicit \$multiline = false" . PHP_EOL; +echo XML_Util::attributesToString($att, true, false) . PHP_EOL . PHP_EOL; + +echo "TEST: explicit \$multiline = true" . PHP_EOL; +echo XML_Util::attributesToString($att, true, true) . PHP_EOL . PHP_EOL; + +echo "TEST: explicit \$indent = ' ' (8 spaces)" . PHP_EOL; +echo XML_Util::attributesToString($att, true, true, ' ') . PHP_EOL . PHP_EOL; + +echo "TEST: explicit \$linebreak = '^' (some dummy char)" . PHP_EOL; +echo XML_Util::attributesToString($att, true, true, '^') . PHP_EOL . PHP_EOL; + +echo "TEST: passing \$sort array of options that includes 'sort'" . PHP_EOL; +echo XML_Util::attributesToString($att, $sort1) . PHP_EOL . PHP_EOL; + +echo "TEST: passing \$sort array of options that doesn't include 'sort'" . PHP_EOL; +echo XML_Util::attributesToString($att, $sort2) . PHP_EOL . PHP_EOL; + +echo "TEST: do not replace entities" . PHP_EOL; +$arr = array("foo" => "b@&r", "boo" => "b> "b@&r", "boo" => "b> "b@&r", "boo" => "b> "b@&r", "boo" => "b> +--EXPECT-- +=====XML_Util::attributesToString() basic tests===== + +TEST: basic usage + boo="baz" foo="bar" + +TEST: explicit $sort = true + boo="baz" foo="bar" + +TEST: explicit $sort = false + foo="bar" boo="baz" + +TEST: explicit $multiline = false + boo="baz" foo="bar" + +TEST: explicit $multiline = true + boo="baz" + foo="bar" + +TEST: explicit $indent = ' ' (8 spaces) + boo="baz" + foo="bar" + +TEST: explicit $linebreak = '^' (some dummy char) + boo="baz" +^foo="bar" + +TEST: passing $sort array of options that includes 'sort' + boo="baz" +----foo="bar" + +TEST: passing $sort array of options that doesn't include 'sort' + boo="baz" +----foo="bar" + +TEST: do not replace entities + boo="b> +# created for v1.2.0a1 2008-05-04 +--FILE-- +"; +$otherTag = "baz"; +$xhtmlTag = ""; + +echo "TEST: basic usage" . PHP_EOL; +echo XML_Util::collapseEmptyTags($emptyTag) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage alongside non-empty tag" . PHP_EOL; +echo XML_Util::collapseEmptyTags($emptyTag . $otherTag) . PHP_EOL . PHP_EOL; + +echo "TEST: one empty tag, with COLLAPSE_ALL set" . PHP_EOL; +echo XML_Util::collapseEmptyTags($emptyTag, XML_UTIL_COLLAPSE_ALL) . PHP_EOL . PHP_EOL; + +echo "TEST: one empty tag alongside non-empty tag, with COLLAPSE_ALL set" . PHP_EOL; +echo XML_Util::collapseEmptyTags($emptyTag . $otherTag, XML_UTIL_COLLAPSE_ALL) . PHP_EOL . PHP_EOL; + +echo "TEST: one empty tag, with COLLAPSE_XHTML_ONLY set" . PHP_EOL; +echo XML_Util::collapseEmptyTags($emptyTag, XML_UTIL_COLLAPSE_XHTML_ONLY) . PHP_EOL . PHP_EOL; + +echo "TEST: one empty tag alongside non-empty tag, with COLLAPSE_XHTML_ONLY set" . PHP_EOL; +echo XML_Util::collapseEmptyTags($emptyTag . $xhtmlTag . $otherTag, XML_UTIL_COLLAPSE_XHTML_ONLY) . PHP_EOL . PHP_EOL; +?> +--EXPECT-- +=====XML_Util::collapseEmptyTags() basic tests===== + +TEST: basic usage + + +TEST: basic usage alongside non-empty tag +baz + +TEST: one empty tag, with COLLAPSE_ALL set + + +TEST: one empty tag alongside non-empty tag, with COLLAPSE_ALL set +baz + +TEST: one empty tag, with COLLAPSE_XHTML_ONLY set + + +TEST: one empty tag alongside non-empty tag, with COLLAPSE_XHTML_ONLY set +baz +XML_Util-1.2.1/tests/testBasic_createCDataSection.phpt100644 1750 1750 756 11117075466 16117 --TEST-- +XML_Util::createCDataSection() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- + +--EXPECT-- +=====XML_Util::createCDataSection() basic tests===== + +TEST: basic usage + +XML_Util-1.2.1/tests/testBasic_createComment.phpt100644 1750 1750 727 11117075466 15216 --TEST-- +XML_Util::createComment() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- + +--EXPECT-- +=====XML_Util::createComment() basic tests===== + +TEST: basic usage + +XML_Util-1.2.1/tests/testBasic_createEndElement.phpt100644 1750 1750 1270 11117075466 15646 --TEST-- +XML_Util::createEndElement() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- + +--EXPECT-- +=====XML_Util::createEndElement() basic tests===== + +TEST: basic usage (myTag) + + +TEST: basic usage with a namespaced tag (myNs:myTag) + +XML_Util-1.2.1/tests/testBasic_createStartElement.phpt100644 1750 1750 7275 11117075466 16250 --TEST-- +XML_Util::createStartElement() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- + "bar") +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag only, passing '' as attribute arg" . PHP_EOL; +echo XML_Util::createStartElement( + 'myNs:myTag', + '' +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attributes and namespace" . PHP_EOL; +echo XML_Util::createStartElement( + "myNs:myTag", + array("foo" => "bar"), + "http://www.w3c.org/myNs#" +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with empty attributes, whose namespaceUri is not a full namespace" . PHP_EOL; +echo XML_Util::createStartElement( + 'myTag', + '', + 'foo' +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attributes, namespace, and multiline = true" . PHP_EOL; +echo XML_Util::createStartElement( + "myNs:myTag", + array("foo" => "bar"), + "http://www.w3c.org/myNs#", + true +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attributes, namespace, multiline = true, and indent = (2 spaces only)" . PHP_EOL; +echo XML_Util::createStartElement( + "myNs:myTag", + array("foo" => "bar"), + "http://www.w3c.org/myNs#", + true, + ' ' +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attributes, namespace, multiline = true, indent = (2 spaces only), and linebreak = '^'" . PHP_EOL; +echo XML_Util::createStartElement( + "myNs:myTag", + array("foo" => "bar"), + "http://www.w3c.org/myNs#", + true, + ' ', + '^' +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attributes, namespace, multiline = true, indent = (2 spaces only), linebreak = '^', and sortAttributes = true" . PHP_EOL; +echo XML_Util::createStartElement( + "myNs:myTag", + array("foo" => "bar", "boo" => "baz"), + "http://www.w3c.org/myNs#", + true, + ' ', + '^', + true +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attributes, namespace, multiline = true, indent = (2 spaces only), linebreak = '^', and sortAttributes = false" . PHP_EOL; +echo XML_Util::createStartElement( + "myNs:myTag", + array("foo" => "bar", "boo" => "baz"), + "http://www.w3c.org/myNs#", + true, + ' ', + '^', + false +) . PHP_EOL . PHP_EOL; +?> +--EXPECT-- +=====XML_Util::createStartElement() basic tests===== + +TEST: tag only + + +TEST: tag with attributes + + +TEST: tag only, passing '' as attribute arg + + +TEST: tag with attributes and namespace + + +TEST: tag with empty attributes, whose namespaceUri is not a full namespace + + +TEST: tag with attributes, namespace, and multiline = true + + +TEST: tag with attributes, namespace, multiline = true, and indent = (2 spaces only) + + +TEST: tag with attributes, namespace, multiline = true, indent = (2 spaces only), and linebreak = '^' + + +TEST: tag with attributes, namespace, multiline = true, indent = (2 spaces only), linebreak = '^', and sortAttributes = true + + +TEST: tag with attributes, namespace, multiline = true, indent = (2 spaces only), linebreak = '^', and sortAttributes = false + +XML_Util-1.2.1/tests/testBasic_createTag.phpt100644 1750 1750 13434 11117075466 14366 --TEST-- +XML_Util::createTag() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- + "bar") +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attribute and content" . PHP_EOL; +echo XML_Util::createTag( + "myNs:myTag", + array("foo" => "bar"), + "This is inside the tag" +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attribute, content, and namespace" . PHP_EOL; +echo XML_Util::createTag( + "myNs:myTag", + array("foo" => "bar"), + "This is inside the tag", + "http://www.w3c.org/myNs#" +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attribute, content, namespace, and REPLACE_ENTITIES" . PHP_EOL; +echo XML_Util::createTag( + "myNs:myTag", + array("foo" => "bar"), + "This is inside the tag and has < & @ > in it", + "http://www.w3c.org/myNs#", + XML_UTIL_REPLACE_ENTITIES +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attribute, content, namespace, and CDATA_SECTION" . PHP_EOL; +echo XML_Util::createTag( + "myNs:myTag", + array("foo" => "bar"), + "This is inside the tag and has < & @ > in it", + "http://www.w3c.org/myNs#", + XML_UTIL_CDATA_SECTION +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attribute, content, namespace, REPLACE_ENTITIES, and multiline = false" . PHP_EOL; +echo XML_Util::createTag( + "myNs:myTag", + array("foo" => "bar"), + "This is inside the tag and has < & @ > in it", + "http://www.w3c.org/myNs#", + XML_UTIL_REPLACE_ENTITIES, + false +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attribute, content, namespace, REPLACE_ENTITIES, and multiline = true" . PHP_EOL; +echo XML_Util::createTag( + "myNs:myTag", + array("foo" => "bar"), + "This is inside the tag and has < & @ > in it", + "http://www.w3c.org/myNs#", + XML_UTIL_REPLACE_ENTITIES, + true +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attribute, content, namespace, REPLACE_ENTITIES, multiline = true, and indent = (2 spaces)" . PHP_EOL; +echo XML_Util::createTag( + "myNs:myTag", + array("foo" => "bar"), + "This is inside the tag and has < & @ > in it", + "http://www.w3c.org/myNs#", + XML_UTIL_REPLACE_ENTITIES, + true, + ' ' +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attribute, content, namespace, REPLACE_ENTITIES, multiline = true, indent = (2 spaces), and linebreak = '^'" . PHP_EOL; +echo XML_Util::createTag( + "myNs:myTag", + array("foo" => "bar"), + "This is inside the tag and has < & @ > in it", + "http://www.w3c.org/myNs#", + XML_UTIL_REPLACE_ENTITIES, + true, + ' ', + '^' +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attribute, content, namespace, REPLACE_ENTITIES, multiline = true, indent = (2 spaces), linebreak = '^', and sortAttributes = true" . PHP_EOL; +echo XML_Util::createTag( + "myNs:myTag", + array("foo" => "bar", "boo" => "baz"), + "This is inside the tag and has < & @ > in it", + "http://www.w3c.org/myNs#", + XML_UTIL_REPLACE_ENTITIES, + true, + ' ', + '^', + true +) . PHP_EOL . PHP_EOL; + +echo "TEST: tag with attribute, content, namespace, REPLACE_ENTITIES, multiline = true, indent = (2 spaces), linebreak = '^', and sortAttributes = false" . PHP_EOL; +echo XML_Util::createTag( + "myNs:myTag", + array("foo" => "bar", "boo" => "baz"), + "This is inside the tag and has < & @ > in it", + "http://www.w3c.org/myNs#", + XML_UTIL_REPLACE_ENTITIES, + true, + ' ', + '^', + false +) . PHP_EOL . PHP_EOL; +?> +--EXPECT-- +=====XML_Util::createTag() basic tests===== + +TEST: tag with attribute + + +TEST: tag with attribute and content +This is inside the tag + +TEST: tag with attribute, content, and namespace +This is inside the tag + +TEST: tag with attribute, content, namespace, and REPLACE_ENTITIES +This is inside the tag and has < & @ > in it + +TEST: tag with attribute, content, namespace, and CDATA_SECTION + in it]]> + +TEST: tag with attribute, content, namespace, REPLACE_ENTITIES, and multiline = false +This is inside the tag and has < & @ > in it + +TEST: tag with attribute, content, namespace, REPLACE_ENTITIES, and multiline = true +This is inside the tag and has < & @ > in it + +TEST: tag with attribute, content, namespace, REPLACE_ENTITIES, multiline = true, and indent = (2 spaces) +This is inside the tag and has < & @ > in it + +TEST: tag with attribute, content, namespace, REPLACE_ENTITIES, multiline = true, indent = (2 spaces), and linebreak = '^' +This is inside the tag and has < & @ > in it + +TEST: tag with attribute, content, namespace, REPLACE_ENTITIES, multiline = true, indent = (2 spaces), linebreak = '^', and sortAttributes = true +This is inside the tag and has < & @ > in it + +TEST: tag with attribute, content, namespace, REPLACE_ENTITIES, multiline = true, indent = (2 spaces), linebreak = '^', and sortAttributes = false +This is inside the tag and has < & @ > in it +XML_Util-1.2.1/tests/testBasic_createTagFromArray.phpt100644 1750 1750 22131 11117075466 16203 --TEST-- +XML_Util::createTagFromArray() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- + "bar", +); +$tag1 = array( + "qname" => "foo:bar", +); +$tag2 = array( + "qname" => "foo:bar", + "namespaceUri" => "http://foo.com", +); +$tag3 = array( + "qname" => "foo:bar", + "namespaceUri" => "http://foo.com", + "attributes" => array( "key" => "value", "argh" => "fruit&vegetable" ), +); +$tag4 = array( + "qname" => "foo:bar", + "namespaceUri" => "http://foo.com", + "attributes" => array( "key" => "value", "argh" => "fruit&vegetable" ), + "content" => "I'm inside the tag", +); +$tag5 = array( + "qname" => "foo:bar", + "attributes" => array( "key" => "value", "argh" => "fruit&vegetable" ), + "content" => "I'm inside the tag", +); +$tag6 = array( + "qname" => "foo:bar", + "namespaceUri" => "http://foo.com", + "content" => "I'm inside the tag", +); +$tag7 = array( + "namespaceUri" => "http://foo.com", + "attributes" => array( "key" => "value", "argh" => "fruit&vegetable" ), + "content" => "I'm inside the tag", +); + +$tag8 = array( + 'content' => array('foo', 'bar') +); + +$tag9 = array( + 'qname' => 'foo:bar', + 'namespaces' => array('ns1' => 'uri1', 'ns2' => 'uri2') +); + +$tag10 = array( + 'namespace' => 'http://foo.org', + 'localPart' => 'bar' +); + +$tag11 = array( + 'namespace' => '', + 'localPart' => 'bar' +); + +$tag12 = array( + 'localPart' => 'foo', + 'namespaceUri' => 'http://bar.org' +); + +echo "TEST: basic usage with an invalid array" . PHP_EOL; +echo XML_Util::createTagFromArray($bad) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname only)" . PHP_EOL; +echo XML_Util::createTagFromArray($tag1) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname and namespaceUri)" . PHP_EOL; +echo XML_Util::createTagFromArray($tag2) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname, namespaceUri, and attributes)" . PHP_EOL; +echo XML_Util::createTagFromArray($tag3) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content)" . PHP_EOL; +echo XML_Util::createTagFromArray($tag4) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname, attributes, and content)" . PHP_EOL; +echo XML_Util::createTagFromArray($tag5) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname, namespaceUri, and content)" . PHP_EOL; +echo XML_Util::createTagFromArray($tag6) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (namespaceUri, attributes, and content)" . PHP_EOL; +echo XML_Util::createTagFromArray($tag7) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), plus REPLACE_ENTITIES" . PHP_EOL; +echo XML_Util::createTagFromArray($tag4, XML_UTIL_REPLACE_ENTITIES) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), plus ENTITIES_NONE" . PHP_EOL; +echo XML_Util::createTagFromArray($tag4, XML_UTIL_ENTITIES_NONE) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), REPLACE_ENTITIES, and multiline = false" . PHP_EOL; +echo XML_Util::createTagFromArray($tag4, XML_UTIL_REPLACE_ENTITIES, false) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), REPLACE_ENTITIES, and multiline = true" . PHP_EOL; +echo XML_Util::createTagFromArray($tag4, XML_UTIL_REPLACE_ENTITIES, true) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), REPLACE_ENTITIES, multiline = true, and indent = (2 spaces)" . PHP_EOL; +echo XML_Util::createTagFromArray($tag4, XML_UTIL_REPLACE_ENTITIES, true, ' ') . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), REPLACE_ENTITIES, multiline = true, indent = (2 spaces), and linebreak = '^'" . PHP_EOL; +echo XML_Util::createTagFromArray($tag4, XML_UTIL_REPLACE_ENTITIES, true, ' ', '^') . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), REPLACE_ENTITIES, multiline = true, indent = (2 spaces), linebreak = '^', and sortAttributes = true" . PHP_EOL; +echo XML_Util::createTagFromArray($tag4, XML_UTIL_REPLACE_ENTITIES, true, ' ', '^', true) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), REPLACE_ENTITIES, multiline = true, indent = (2 spaces), linebreak = '^', and sortAttributes = false" . PHP_EOL; +echo XML_Util::createTagFromArray($tag4, XML_UTIL_REPLACE_ENTITIES, true, ' ', '^', false) . PHP_EOL . PHP_EOL; + +echo 'TEST: cause a non-scalar error on the content tag' . PHP_EOL; +echo XML_Util::createTagFromArray($tag8) . PHP_EOL . PHP_EOL; + +echo 'TEST: handle an array of namespaces being passed' . PHP_EOL; +echo XML_Util::createTagFromArray($tag9) . PHP_EOL . PHP_EOL; + +echo 'TEST: qname is derived from namespace + localPart' . PHP_EOL; +echo XML_Util::createTagFromArray($tag10) . PHP_EOL . PHP_EOL; + +echo 'TEST: qname is derived from localPart only' . PHP_EOL; +echo XML_Util::createTagFromArray($tag11) . PHP_EOL . PHP_EOL; + +echo 'TEST: namespaceUri is given, but namespace is not' . PHP_EOL; +echo XML_Util::createTagFromArray($tag12) . PHP_EOL . PHP_EOL; +?> +--EXPECT-- +=====XML_Util::createTagFromArray() basic tests===== + +TEST: basic usage with an invalid array +You must either supply a qualified name (qname) or local tag name (localPart). + +TEST: basic usage with a valid array (qname only) + + +TEST: basic usage with a valid array (qname and namespaceUri) + + +TEST: basic usage with a valid array (qname, namespaceUri, and attributes) + + +TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content) +I'm inside the tag + +TEST: basic usage with a valid array (qname, attributes, and content) +I'm inside the tag + +TEST: basic usage with a valid array (qname, namespaceUri, and content) +I'm inside the tag + +TEST: basic usage with a valid array (namespaceUri, attributes, and content) +You must either supply a qualified name (qname) or local tag name (localPart). + +TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), plus REPLACE_ENTITIES +I'm inside the tag + +TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), plus ENTITIES_NONE +I'm inside the tag + +TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), REPLACE_ENTITIES, and multiline = false +I'm inside the tag + +TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), REPLACE_ENTITIES, and multiline = true +I'm inside the tag + +TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), REPLACE_ENTITIES, multiline = true, and indent = (2 spaces) +I'm inside the tag + +TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), REPLACE_ENTITIES, multiline = true, indent = (2 spaces), and linebreak = '^' +I'm inside the tag + +TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), REPLACE_ENTITIES, multiline = true, indent = (2 spaces), linebreak = '^', and sortAttributes = true +I'm inside the tag + +TEST: basic usage with a valid array (qname, namespaceUri, attributes, and content), REPLACE_ENTITIES, multiline = true, indent = (2 spaces), linebreak = '^', and sortAttributes = false +I'm inside the tag + +TEST: cause a non-scalar error on the content tag +Supplied non-scalar value as tag content + +TEST: handle an array of namespaces being passed + + +TEST: qname is derived from namespace + localPart + + +TEST: qname is derived from localPart only + + +TEST: namespaceUri is given, but namespace is not + +XML_Util-1.2.1/tests/testBasic_getDocTypeDeclaration.phpt100644 1750 1750 2754 11117075466 16667 --TEST-- +XML_Util::getDocTypeDeclaration() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- + 'http://pear.php.net/dtd/package-1.0', + 'id' => '-//PHP//PEAR/DTD PACKAGE 0.1' +); +$dtdEntry = ''; + +echo "TEST: using root and an array URI" . PHP_EOL; +echo XML_Util::getDocTypeDeclaration("rootTag", $uri) . PHP_EOL . PHP_EOL; + +echo "TEST: using root and an array URI and an internal DTD entry" . PHP_EOL; +echo XML_Util::getDocTypeDeclaration("rootTag", $uri, $dtdEntry) . PHP_EOL . PHP_EOL; +?> +--EXPECT-- +=====XML_Util::getDocTypeDeclaration() basic tests===== + +TEST: using root only + + +TEST: using root and a string URI + + +TEST: using root and an array URI + + +TEST: using root and an array URI and an internal DTD entry + +]> +XML_Util-1.2.1/tests/testBasic_getXmlDeclaration.phpt100644 1750 1750 2217 11117075466 16052 --TEST-- +XML_Util::getXmlDeclaration() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- + +--EXPECT-- +=====XML_Util::getXmlDeclaration() basic tests===== + +TEST: using version only + + +TEST: using version and encoding + + +TEST: using version, encoding, and standalone flag + + +TEST: using version and standalone flag + +XML_Util-1.2.1/tests/testBasic_isValidName.phpt100644 1750 1750 2602 11117075466 14636 --TEST-- +XML_Util::isValidName() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- +getMessage() . PHP_EOL . PHP_EOL; +} else { + print "Valid XML name." . PHP_EOL . PHP_EOL; +} + +echo "TEST: invalid tag" . PHP_EOL; +$result = XML_Util::isValidName("invalidTag?"); +if (is_a($result, 'PEAR_Error')) { + print "Invalid XML name: " . $result->getMessage() . PHP_EOL . PHP_EOL; +} else { + print "Valid XML name." . PHP_EOL . PHP_EOL; +} + +echo "TEST: invalid tag that doesn't start with a letter" . PHP_EOL; +$result = XML_Util::isValidName("1234five"); +if (is_a($result, 'PEAR_Error')) { + print "Invalid XML name: " . $result->getMessage() . PHP_EOL . PHP_EOL; +} else { + print "Valid XML name." . PHP_EOL . PHP_EOL; +} + +?> +--EXPECT-- +=====XML_Util::isValidName() basic tests===== + +TEST: valid tag +Valid XML name. + +TEST: invalid tag +Invalid XML name: XML names may only contain alphanumeric chars, period, hyphen, colon and underscores + +TEST: invalid tag that doesn't start with a letter +Invalid XML name: XML names may only start with letter or underscore +XML_Util-1.2.1/tests/testBasic_raiseError.phpt100644 1750 1750 766 11117075466 14550 --TEST-- +XML_Util::raiseError() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- +getMessage() . PHP_EOL; +} +?> +--EXPECT-- +=====XML_Util::raiseError() basic tests===== + +PEAR Error: I am an error +XML_Util-1.2.1/tests/testBasic_replaceEntities.phpt100644 1750 1750 6260 11117075466 15566 --TEST-- +XML_Util::replaceEntities() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- +.'; +$utf8 = 'This data contains special chars like <, >, & and " as well as ä, ö, ß, à and ê'; + +echo "TEST: basic usage" . PHP_EOL; +echo XML_Util::replaceEntities($data) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage but with bogus \$replaceEntities arg" . PHP_EOL; +echo XML_Util::replaceEntities($data, 'I_AM_BOGUS') . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with ENTITIES_XML" . PHP_EOL; +echo XML_Util::replaceEntities($data, XML_UTIL_ENTITIES_XML) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with ENTITIES_XML and UTF-8" . PHP_EOL; +echo XML_Util::replaceEntities($data, XML_UTIL_ENTITIES_XML, 'UTF-8') . PHP_EOL . PHP_EOL; + +echo "TEST: utf8 usage with ENTITIES_XML and UTF-8" . PHP_EOL; +echo XML_Util::replaceEntities($utf8, XML_UTIL_ENTITIES_XML, 'UTF-8') . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with ENTITIES_XML_REQUIRED" . PHP_EOL; +echo XML_Util::replaceEntities($data, XML_UTIL_ENTITIES_XML_REQUIRED) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with ENTITIES_XML_REQUIRED and UTF-8" . PHP_EOL; +echo XML_Util::replaceEntities($data, XML_UTIL_ENTITIES_XML_REQUIRED, 'UTF-8') . PHP_EOL . PHP_EOL; + +echo "TEST: utf8 usage with ENTITIES_XML_REQUIRED and UTF-8" . PHP_EOL; +echo XML_Util::replaceEntities($utf8, XML_UTIL_ENTITIES_XML_REQUIRED, 'UTF-8') . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with ENTITIES_HTML" . PHP_EOL; +echo XML_Util::replaceEntities($data, XML_UTIL_ENTITIES_HTML) . PHP_EOL . PHP_EOL; + +echo "TEST: basic usage with ENTITIES_HTML and UTF-8" . PHP_EOL; +echo XML_Util::replaceEntities($data, XML_UTIL_ENTITIES_HTML, 'UTF-8') . PHP_EOL . PHP_EOL; + +echo "TEST: utf8 usage with ENTITIES_HTML and UTF-8" . PHP_EOL; +echo XML_Util::replaceEntities($utf8, XML_UTIL_ENTITIES_HTML, 'UTF-8') . PHP_EOL . PHP_EOL; +?> +--EXPECT-- +=====XML_Util::replaceEntities() basic tests===== + +TEST: basic usage +This string contains < & >. + +TEST: basic usage but with bogus $replaceEntities arg +This string contains < & >. + +TEST: basic usage with ENTITIES_XML +This string contains < & >. + +TEST: basic usage with ENTITIES_XML and UTF-8 +This string contains < & >. + +TEST: utf8 usage with ENTITIES_XML and UTF-8 +This data contains special chars like <, >, & and " as well as ä, ö, ß, à and ê + +TEST: basic usage with ENTITIES_XML_REQUIRED +This string contains < & >. + +TEST: basic usage with ENTITIES_XML_REQUIRED and UTF-8 +This string contains < & >. + +TEST: utf8 usage with ENTITIES_XML_REQUIRED and UTF-8 +This data contains special chars like <, >, & and " as well as ä, ö, ß, à and ê + +TEST: basic usage with ENTITIES_HTML +This string contains < & >. + +TEST: basic usage with ENTITIES_HTML and UTF-8 +This string contains < & >. + +TEST: utf8 usage with ENTITIES_HTML and UTF-8 +This data contains special chars like <, >, & and " as well as ä, ö, ß, à and ê + +XML_Util-1.2.1/tests/testBasic_reverseEntities.phpt100644 1750 1750 6241 11117075466 15625 --TEST-- +XML_Util::reverseEntities() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- + +--EXPECT-- +=====XML_Util::reverseEntities() basic tests===== + +TEST: basic usage +This string contains < & >. + +TEST: basic usage but with bogus $replaceEntities arg +This string contains < & >. + +TEST: basic usage with ENTITIES_XML +This string contains < & >. + +TEST: basic usage with ENTITIES_XML and UTF-8 +This string contains < & >. + +TEST: utf8 usage with ENTITIES_XML and UTF-8 +This data contains special chars like <, >, & and " as well as ä, ö, ß, à and ê + +TEST: basic usage with ENTITIES_XML_REQUIRED +This string contains < & >. + +TEST: basic usage with ENTITIES_XML_REQUIRED and UTF-8 +This string contains < & >. + +TEST: utf8 usage with ENTITIES_XML_REQUIRED and UTF-8 +This data contains special chars like <, >, & and " as well as ä, ö, ß, à and ê + +TEST: basic usage with ENTITIES_HTML +This string contains < & >. + +TEST: basic usage with ENTITIES_HTML and UTF-8 +This string contains < & >. + +TEST: utf8 usage with ENTITIES_HTML and UTF-8 +This data contains special chars like <, >, & and " as well as ä, ö, ß, à and ê +XML_Util-1.2.1/tests/testBasic_splitQualifiedName.phpt100644 1750 1750 1727 11117075466 16231 --TEST-- +XML_Util::splitQualifiedName() basic tests +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- + " . $return['namespace'] . PHP_EOL; +echo "localPart => " . $return['localPart'] . PHP_EOL; +echo PHP_EOL; + +echo "TEST: basic usage with namespace" . PHP_EOL; +$return = XML_Util::splitQualifiedName("stylesheet", "myNs"); +echo "namespace => " . $return['namespace'] . PHP_EOL; +echo "localPart => " . $return['localPart'] . PHP_EOL; +echo PHP_EOL; +?> +--EXPECT-- +=====XML_Util::splitQualifiedName() basic tests===== + +TEST: basic usage without namespace +namespace => xslt +localPart => stylesheet + +TEST: basic usage with namespace +namespace => myNs +localPart => stylesheet +XML_Util-1.2.1/tests/testBug_4950.phpt100644 1750 1750 1243 11117075466 12537 --TEST-- +XML_Util tests for Bug #4950 "Incorrect CDATA serializing" +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- + here!", + null, XML_UTIL_CDATA_SECTION) . PHP_EOL; + +?> +--EXPECT-- +=====XML_Util tests for Bug #4950 "Incorrect CDATA serializing"===== + +TEST: test case provided in bug report + here!]]> + +XML_Util-1.2.1/tests/testBug_5392.phpt100644 1750 1750 2111 11117075466 12533 --TEST-- +XML_Util tests for Bug #5392 "encoding of ISO-8859-1 is the only supported encoding" +--CREDITS-- +Chuck Burgess +# created for v1.2.0a1 2008-05-04 +--FILE-- +, & and " as well as ä, ö, ß, à and ê'; + +$replaced = XML_Util::replaceEntities($data, XML_UTIL_ENTITIES_HTML, 'UTF-8'); + +$reversed = XML_Util::reverseEntities($replaced, XML_UTIL_ENTITIES_HTML, 'UTF-8'); + +echo $replaced . PHP_EOL; +echo $reversed . PHP_EOL; + +?> +--EXPECT-- +=====XML_Util tests for Bug #5392 "encoding of ISO-8859-1 is the only supported encoding"===== + +TEST: test case provided in bug report +This data contains special chars like <, >, & and " as well as ä, ö, ß, à and ê +This data contains special chars like <, >, & and " as well as ä, ö, ß, à and ê +XML_Util-1.2.1/Util.php100644 1750 1750 73350 11117075466 10060 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Util + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Util.php,v 1.38 2008/11/13 00:03:38 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Util + */ + +/** + * error code for invalid chars in XML name + */ +define('XML_UTIL_ERROR_INVALID_CHARS', 51); + +/** + * error code for invalid chars in XML name + */ +define('XML_UTIL_ERROR_INVALID_START', 52); + +/** + * error code for non-scalar tag content + */ +define('XML_UTIL_ERROR_NON_SCALAR_CONTENT', 60); + +/** + * error code for missing tag name + */ +define('XML_UTIL_ERROR_NO_TAG_NAME', 61); + +/** + * replace XML entities + */ +define('XML_UTIL_REPLACE_ENTITIES', 1); + +/** + * embedd content in a CData Section + */ +define('XML_UTIL_CDATA_SECTION', 5); + +/** + * do not replace entitites + */ +define('XML_UTIL_ENTITIES_NONE', 0); + +/** + * replace all XML entitites + * This setting will replace <, >, ", ' and & + */ +define('XML_UTIL_ENTITIES_XML', 1); + +/** + * replace only required XML entitites + * This setting will replace <, " and & + */ +define('XML_UTIL_ENTITIES_XML_REQUIRED', 2); + +/** + * replace HTML entitites + * @link http://www.php.net/htmlentities + */ +define('XML_UTIL_ENTITIES_HTML', 3); + +/** + * Collapse all empty tags. + */ +define('XML_UTIL_COLLAPSE_ALL', 1); + +/** + * Collapse only empty XHTML tags that have no end tag. + */ +define('XML_UTIL_COLLAPSE_XHTML_ONLY', 2); + +/** + * utility class for working with XML documents + * + + * @category XML + * @package XML_Util + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 1.2.1 + * @link http://pear.php.net/package/XML_Util + */ +class XML_Util +{ + /** + * return API version + * + * @return string $version API version + * @access public + * @static + */ + function apiVersion() + { + return '1.1'; + } + + /** + * replace XML entities + * + * With the optional second parameter, you may select, which + * entities should be replaced. + * + * + * require_once 'XML/Util.php'; + * + * // replace XML entites: + * $string = XML_Util::replaceEntities('This string contains < & >.'); + * + * + * With the optional third parameter, you may pass the character encoding + * + * require_once 'XML/Util.php'; + * + * // replace XML entites in UTF-8: + * $string = XML_Util::replaceEntities( + * 'This string contains < & > as well as ä, ö, ß, à and ê', + * XML_UTIL_ENTITIES_HTML, + * 'UTF-8' + * ); + * + * + * @param string $string string where XML special chars + * should be replaced + * @param int $replaceEntities setting for entities in attribute values + * (one of XML_UTIL_ENTITIES_XML, + * XML_UTIL_ENTITIES_XML_REQUIRED, + * XML_UTIL_ENTITIES_HTML) + * @param string $encoding encoding value (if any)... + * must be a valid encoding as determined + * by the htmlentities() function + * + * @return string string with replaced chars + * @access public + * @static + * @see reverseEntities() + */ + function replaceEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML, + $encoding = 'ISO-8859-1') + { + switch ($replaceEntities) { + case XML_UTIL_ENTITIES_XML: + return strtr($string, array( + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + '\'' => ''' )); + break; + case XML_UTIL_ENTITIES_XML_REQUIRED: + return strtr($string, array( + '&' => '&', + '<' => '<', + '"' => '"' )); + break; + case XML_UTIL_ENTITIES_HTML: + return htmlentities($string, ENT_COMPAT, $encoding); + break; + } + return $string; + } + + /** + * reverse XML entities + * + * With the optional second parameter, you may select, which + * entities should be reversed. + * + * + * require_once 'XML/Util.php'; + * + * // reverse XML entites: + * $string = XML_Util::reverseEntities('This string contains < & >.'); + * + * + * With the optional third parameter, you may pass the character encoding + * + * require_once 'XML/Util.php'; + * + * // reverse XML entites in UTF-8: + * $string = XML_Util::reverseEntities( + * 'This string contains < & > as well as' + * . ' ä, ö, ß, à and ê', + * XML_UTIL_ENTITIES_HTML, + * 'UTF-8' + * ); + * + * + * @param string $string string where XML special chars + * should be replaced + * @param int $replaceEntities setting for entities in attribute values + * (one of XML_UTIL_ENTITIES_XML, + * XML_UTIL_ENTITIES_XML_REQUIRED, + * XML_UTIL_ENTITIES_HTML) + * @param string $encoding encoding value (if any)... + * must be a valid encoding as determined + * by the html_entity_decode() function + * + * @return string string with replaced chars + * @access public + * @static + * @see replaceEntities() + */ + function reverseEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML, + $encoding = 'ISO-8859-1') + { + switch ($replaceEntities) { + case XML_UTIL_ENTITIES_XML: + return strtr($string, array( + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + ''' => '\'' )); + break; + case XML_UTIL_ENTITIES_XML_REQUIRED: + return strtr($string, array( + '&' => '&', + '<' => '<', + '"' => '"' )); + break; + case XML_UTIL_ENTITIES_HTML: + return html_entity_decode($string, ENT_COMPAT, $encoding); + break; + } + return $string; + } + + /** + * build an xml declaration + * + * + * require_once 'XML/Util.php'; + * + * // get an XML declaration: + * $xmlDecl = XML_Util::getXMLDeclaration('1.0', 'UTF-8', true); + * + * + * @param string $version xml version + * @param string $encoding character encoding + * @param bool $standalone document is standalone (or not) + * + * @return string xml declaration + * @access public + * @static + * @uses attributesToString() to serialize the attributes of the XML declaration + */ + function getXMLDeclaration($version = '1.0', $encoding = null, + $standalone = null) + { + $attributes = array( + 'version' => $version, + ); + // add encoding + if ($encoding !== null) { + $attributes['encoding'] = $encoding; + } + // add standalone, if specified + if ($standalone !== null) { + $attributes['standalone'] = $standalone ? 'yes' : 'no'; + } + + return sprintf('', + XML_Util::attributesToString($attributes, false)); + } + + /** + * build a document type declaration + * + * + * require_once 'XML/Util.php'; + * + * // get a doctype declaration: + * $xmlDecl = XML_Util::getDocTypeDeclaration('rootTag','myDocType.dtd'); + * + * + * @param string $root name of the root tag + * @param string $uri uri of the doctype definition + * (or array with uri and public id) + * @param string $internalDtd internal dtd entries + * + * @return string doctype declaration + * @access public + * @static + * @since 0.2 + */ + function getDocTypeDeclaration($root, $uri = null, $internalDtd = null) + { + if (is_array($uri)) { + $ref = sprintf(' PUBLIC "%s" "%s"', $uri['id'], $uri['uri']); + } elseif (!empty($uri)) { + $ref = sprintf(' SYSTEM "%s"', $uri); + } else { + $ref = ''; + } + + if (empty($internalDtd)) { + return sprintf('', $root, $ref); + } else { + return sprintf("", $root, $ref, $internalDtd); + } + } + + /** + * create string representation of an attribute list + * + * + * require_once 'XML/Util.php'; + * + * // build an attribute string + * $att = array( + * 'foo' => 'bar', + * 'argh' => 'tomato' + * ); + * + * $attList = XML_Util::attributesToString($att); + * + * + * @param array $attributes attribute array + * @param bool|array $sort sort attribute list alphabetically, + * may also be an assoc array containing + * the keys 'sort', 'multiline', 'indent', + * 'linebreak' and 'entities' + * @param bool $multiline use linebreaks, if more than + * one attribute is given + * @param string $indent string used for indentation of + * multiline attributes + * @param string $linebreak string used for linebreaks of + * multiline attributes + * @param int $entities setting for entities in attribute values + * (one of XML_UTIL_ENTITIES_NONE, + * XML_UTIL_ENTITIES_XML, + * XML_UTIL_ENTITIES_XML_REQUIRED, + * XML_UTIL_ENTITIES_HTML) + * + * @return string string representation of the attributes + * @access public + * @static + * @uses replaceEntities() to replace XML entities in attribute values + * @todo allow sort also to be an options array + */ + function attributesToString($attributes, $sort = true, $multiline = false, + $indent = ' ', $linebreak = "\n", $entities = XML_UTIL_ENTITIES_XML) + { + /* + * second parameter may be an array + */ + if (is_array($sort)) { + if (isset($sort['multiline'])) { + $multiline = $sort['multiline']; + } + if (isset($sort['indent'])) { + $indent = $sort['indent']; + } + if (isset($sort['linebreak'])) { + $multiline = $sort['linebreak']; + } + if (isset($sort['entities'])) { + $entities = $sort['entities']; + } + if (isset($sort['sort'])) { + $sort = $sort['sort']; + } else { + $sort = true; + } + } + $string = ''; + if (is_array($attributes) && !empty($attributes)) { + if ($sort) { + ksort($attributes); + } + if ( !$multiline || count($attributes) == 1) { + foreach ($attributes as $key => $value) { + if ($entities != XML_UTIL_ENTITIES_NONE) { + if ($entities === XML_UTIL_CDATA_SECTION) { + $entities = XML_UTIL_ENTITIES_XML; + } + $value = XML_Util::replaceEntities($value, $entities); + } + $string .= ' ' . $key . '="' . $value . '"'; + } + } else { + $first = true; + foreach ($attributes as $key => $value) { + if ($entities != XML_UTIL_ENTITIES_NONE) { + $value = XML_Util::replaceEntities($value, $entities); + } + if ($first) { + $string .= ' ' . $key . '="' . $value . '"'; + $first = false; + } else { + $string .= $linebreak . $indent . $key . '="' . $value . '"'; + } + } + } + } + return $string; + } + + /** + * Collapses empty tags. + * + * @param string $xml XML + * @param int $mode Whether to collapse all empty tags (XML_UTIL_COLLAPSE_ALL) + * or only XHTML (XML_UTIL_COLLAPSE_XHTML_ONLY) ones. + * + * @return string XML + * @access public + * @static + * @todo PEAR CS - unable to avoid "space after open parens" error + * in the IF branch + */ + function collapseEmptyTags($xml, $mode = XML_UTIL_COLLAPSE_ALL) + { + if ($mode == XML_UTIL_COLLAPSE_XHTML_ONLY) { + return preg_replace( + '/<(area|base(?:font)?|br|col|frame|hr|img|input|isindex|link|meta|' + . 'param)([^>]*)><\/\\1>/s', + '<\\1\\2 />', + $xml); + } else { + return preg_replace('/<(\w+)([^>]*)><\/\\1>/s', '<\\1\\2 />', $xml); + } + } + + /** + * create a tag + * + * This method will call XML_Util::createTagFromArray(), which + * is more flexible. + * + * + * require_once 'XML/Util.php'; + * + * // create an XML tag: + * $tag = XML_Util::createTag('myNs:myTag', + * array('foo' => 'bar'), + * 'This is inside the tag', + * 'http://www.w3c.org/myNs#'); + * + * + * @param string $qname qualified tagname (including namespace) + * @param array $attributes array containg attributes + * @param mixed $content the content + * @param string $namespaceUri URI of the namespace + * @param int $replaceEntities whether to replace XML special chars in + * content, embedd it in a CData section + * or none of both + * @param bool $multiline whether to create a multiline tag where + * each attribute gets written to a single line + * @param string $indent string used to indent attributes + * (_auto indents attributes so they start + * at the same column) + * @param string $linebreak string used for linebreaks + * @param bool $sortAttributes Whether to sort the attributes or not + * + * @return string XML tag + * @access public + * @static + * @see createTagFromArray() + * @uses createTagFromArray() to create the tag + */ + function createTag($qname, $attributes = array(), $content = null, + $namespaceUri = null, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, + $multiline = false, $indent = '_auto', $linebreak = "\n", + $sortAttributes = true) + { + $tag = array( + 'qname' => $qname, + 'attributes' => $attributes + ); + + // add tag content + if ($content !== null) { + $tag['content'] = $content; + } + + // add namespace Uri + if ($namespaceUri !== null) { + $tag['namespaceUri'] = $namespaceUri; + } + + return XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, + $indent, $linebreak, $sortAttributes); + } + + /** + * create a tag from an array + * this method awaits an array in the following format + *
    +     * array(
    +     *     // qualified name of the tag
    +     *     'qname' => $qname        
    +     *
    +     *     // namespace prefix (optional, if qname is specified or no namespace)
    +     *     'namespace' => $namespace    
    +     *
    +     *     // local part of the tagname (optional, if qname is specified)
    +     *     'localpart' => $localpart,   
    +     *
    +     *     // array containing all attributes (optional)
    +     *     'attributes' => array(),      
    +     *
    +     *     // tag content (optional)
    +     *     'content' => $content,     
    +     *
    +     *     // namespaceUri for the given namespace (optional)
    +     *     'namespaceUri' => $namespaceUri 
    +     * )
    +     * 
    + * + * + * require_once 'XML/Util.php'; + * + * $tag = array( + * 'qname' => 'foo:bar', + * 'namespaceUri' => 'http://foo.com', + * 'attributes' => array('key' => 'value', 'argh' => 'fruit&vegetable'), + * 'content' => 'I\'m inside the tag', + * ); + * // creating a tag with qualified name and namespaceUri + * $string = XML_Util::createTagFromArray($tag); + * + * + * @param array $tag tag definition + * @param int $replaceEntities whether to replace XML special chars in + * content, embedd it in a CData section + * or none of both + * @param bool $multiline whether to create a multiline tag where each + * attribute gets written to a single line + * @param string $indent string used to indent attributes + * (_auto indents attributes so they start + * at the same column) + * @param string $linebreak string used for linebreaks + * @param bool $sortAttributes Whether to sort the attributes or not + * + * @return string XML tag + * @access public + * @static + * @see createTag() + * @uses attributesToString() to serialize the attributes of the tag + * @uses splitQualifiedName() to get local part and namespace of a qualified name + * @uses createCDataSection() + * @uses raiseError() + */ + function createTagFromArray($tag, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, + $multiline = false, $indent = '_auto', $linebreak = "\n", + $sortAttributes = true) + { + if (isset($tag['content']) && !is_scalar($tag['content'])) { + return XML_Util::raiseError('Supplied non-scalar value as tag content', + XML_UTIL_ERROR_NON_SCALAR_CONTENT); + } + + if (!isset($tag['qname']) && !isset($tag['localPart'])) { + return XML_Util::raiseError('You must either supply a qualified name ' + . '(qname) or local tag name (localPart).', + XML_UTIL_ERROR_NO_TAG_NAME); + } + + // if no attributes hav been set, use empty attributes + if (!isset($tag['attributes']) || !is_array($tag['attributes'])) { + $tag['attributes'] = array(); + } + + if (isset($tag['namespaces'])) { + foreach ($tag['namespaces'] as $ns => $uri) { + $tag['attributes']['xmlns:' . $ns] = $uri; + } + } + + if (!isset($tag['qname'])) { + // qualified name is not given + + // check for namespace + if (isset($tag['namespace']) && !empty($tag['namespace'])) { + $tag['qname'] = $tag['namespace'] . ':' . $tag['localPart']; + } else { + $tag['qname'] = $tag['localPart']; + } + } elseif (isset($tag['namespaceUri']) && !isset($tag['namespace'])) { + // namespace URI is set, but no namespace + + $parts = XML_Util::splitQualifiedName($tag['qname']); + + $tag['localPart'] = $parts['localPart']; + if (isset($parts['namespace'])) { + $tag['namespace'] = $parts['namespace']; + } + } + + if (isset($tag['namespaceUri']) && !empty($tag['namespaceUri'])) { + // is a namespace given + if (isset($tag['namespace']) && !empty($tag['namespace'])) { + $tag['attributes']['xmlns:' . $tag['namespace']] = + $tag['namespaceUri']; + } else { + // define this Uri as the default namespace + $tag['attributes']['xmlns'] = $tag['namespaceUri']; + } + } + + // check for multiline attributes + if ($multiline === true) { + if ($indent === '_auto') { + $indent = str_repeat(' ', (strlen($tag['qname'])+2)); + } + } + + // create attribute list + $attList = XML_Util::attributesToString($tag['attributes'], + $sortAttributes, $multiline, $indent, $linebreak, $replaceEntities); + if (!isset($tag['content']) || (string)$tag['content'] == '') { + $tag = sprintf('<%s%s />', $tag['qname'], $attList); + } else { + switch ($replaceEntities) { + case XML_UTIL_ENTITIES_NONE: + break; + case XML_UTIL_CDATA_SECTION: + $tag['content'] = XML_Util::createCDataSection($tag['content']); + break; + default: + $tag['content'] = XML_Util::replaceEntities($tag['content'], + $replaceEntities); + break; + } + $tag = sprintf('<%s%s>%s', $tag['qname'], $attList, $tag['content'], + $tag['qname']); + } + return $tag; + } + + /** + * create a start element + * + * + * require_once 'XML/Util.php'; + * + * // create an XML start element: + * $tag = XML_Util::createStartElement('myNs:myTag', + * array('foo' => 'bar') ,'http://www.w3c.org/myNs#'); + * + * + * @param string $qname qualified tagname (including namespace) + * @param array $attributes array containg attributes + * @param string $namespaceUri URI of the namespace + * @param bool $multiline whether to create a multiline tag where each + * attribute gets written to a single line + * @param string $indent string used to indent attributes (_auto indents + * attributes so they start at the same column) + * @param string $linebreak string used for linebreaks + * @param bool $sortAttributes Whether to sort the attributes or not + * + * @return string XML start element + * @access public + * @static + * @see createEndElement(), createTag() + */ + function createStartElement($qname, $attributes = array(), $namespaceUri = null, + $multiline = false, $indent = '_auto', $linebreak = "\n", + $sortAttributes = true) + { + // if no attributes hav been set, use empty attributes + if (!isset($attributes) || !is_array($attributes)) { + $attributes = array(); + } + + if ($namespaceUri != null) { + $parts = XML_Util::splitQualifiedName($qname); + } + + // check for multiline attributes + if ($multiline === true) { + if ($indent === '_auto') { + $indent = str_repeat(' ', (strlen($qname)+2)); + } + } + + if ($namespaceUri != null) { + // is a namespace given + if (isset($parts['namespace']) && !empty($parts['namespace'])) { + $attributes['xmlns:' . $parts['namespace']] = $namespaceUri; + } else { + // define this Uri as the default namespace + $attributes['xmlns'] = $namespaceUri; + } + } + + // create attribute list + $attList = XML_Util::attributesToString($attributes, $sortAttributes, + $multiline, $indent, $linebreak); + $element = sprintf('<%s%s>', $qname, $attList); + return $element; + } + + /** + * create an end element + * + * + * require_once 'XML/Util.php'; + * + * // create an XML start element: + * $tag = XML_Util::createEndElement('myNs:myTag'); + * + * + * @param string $qname qualified tagname (including namespace) + * + * @return string XML end element + * @access public + * @static + * @see createStartElement(), createTag() + */ + function createEndElement($qname) + { + $element = sprintf('', $qname); + return $element; + } + + /** + * create an XML comment + * + * + * require_once 'XML/Util.php'; + * + * // create an XML start element: + * $tag = XML_Util::createComment('I am a comment'); + * + * + * @param string $content content of the comment + * + * @return string XML comment + * @access public + * @static + */ + function createComment($content) + { + $comment = sprintf('', $content); + return $comment; + } + + /** + * create a CData section + * + * + * require_once 'XML/Util.php'; + * + * // create a CData section + * $tag = XML_Util::createCDataSection('I am content.'); + * + * + * @param string $data data of the CData section + * + * @return string CData section with content + * @access public + * @static + */ + function createCDataSection($data) + { + return sprintf('', + preg_replace('/\]\]>/', ']]]]>', strval($data))); + + } + + /** + * split qualified name and return namespace and local part + * + * + * require_once 'XML/Util.php'; + * + * // split qualified tag + * $parts = XML_Util::splitQualifiedName('xslt:stylesheet'); + * + * the returned array will contain two elements: + *
    +     * array(
    +     *     'namespace' => 'xslt',
    +     *     'localPart' => 'stylesheet'
    +     * );
    +     * 
    + * + * @param string $qname qualified tag name + * @param string $defaultNs default namespace (optional) + * + * @return array array containing namespace and local part + * @access public + * @static + */ + function splitQualifiedName($qname, $defaultNs = null) + { + if (strstr($qname, ':')) { + $tmp = explode(':', $qname); + return array( + 'namespace' => $tmp[0], + 'localPart' => $tmp[1] + ); + } + return array( + 'namespace' => $defaultNs, + 'localPart' => $qname + ); + } + + /** + * check, whether string is valid XML name + * + *

    XML names are used for tagname, attribute names and various + * other, lesser known entities.

    + *

    An XML name may only consist of alphanumeric characters, + * dashes, undescores and periods, and has to start with a letter + * or an underscore.

    + * + * + * require_once 'XML/Util.php'; + * + * // verify tag name + * $result = XML_Util::isValidName('invalidTag?'); + * if (is_a($result, 'PEAR_Error')) { + * print 'Invalid XML name: ' . $result->getMessage(); + * } + * + * + * @param string $string string that should be checked + * + * @return mixed true, if string is a valid XML name, PEAR error otherwise + * @access public + * @static + * @todo support for other charsets + * @todo PEAR CS - unable to avoid 85-char limit on second preg_match + */ + function isValidName($string) + { + // check for invalid chars + if (!preg_match('/^[[:alpha:]_]$/', $string{0})) { + return XML_Util::raiseError('XML names may only start with letter ' + . 'or underscore', XML_UTIL_ERROR_INVALID_START); + } + + // check for invalid chars + if (!preg_match('/^([[:alpha:]_]([[:alnum:]\-\.]*)?:)?[[:alpha:]_]([[:alnum:]\_\-\.]+)?$/', + $string) + ) { + return XML_Util::raiseError('XML names may only contain alphanumeric ' + . 'chars, period, hyphen, colon and underscores', + XML_UTIL_ERROR_INVALID_CHARS); + } + // XML name is valid + return true; + } + + /** + * replacement for XML_Util::raiseError + * + * Avoids the necessity to always require + * PEAR.php + * + * @param string $msg error message + * @param int $code error code + * + * @return PEAR_Error + * @access public + * @static + * @todo PEAR CS - should this use include_once instead? + */ + function raiseError($msg, $code) + { + require_once 'PEAR.php'; + return PEAR::raiseError($msg, $code); + } +} +?> +package.sig100644 1750 1750 305 11117075544 6362 -----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.9 (GNU/Linux) + +iEYEABECAAYFAkk8e2QACgkQcqMhusJF8XULZgCg0iwVLWueraJzK5s1AesDkVzv +GucAn22sSv3QiTSUWG9BmakiW9hFsb4V +=g2mr +-----END PGP SIGNATURE----- + + * @author Tomas V.V. Cox + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Installer.php 287446 2009-08-18 11:45:05Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * Used for installation groups in package.xml 2.0 and platform exceptions + */ +require_once 'phar://go-pear.phar/' . 'OS/Guess.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/Downloader.php'; + +define('PEAR_INSTALLER_NOBINARY', -240); +/** + * Administration class used to install PEAR packages and maintain the + * installed package database. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Installer extends PEAR_Downloader +{ + // {{{ properties + + /** name of the package directory, for example Foo-1.0 + * @var string + */ + var $pkgdir; + + /** directory where PHP code files go + * @var string + */ + var $phpdir; + + /** directory where PHP extension files go + * @var string + */ + var $extdir; + + /** directory where documentation goes + * @var string + */ + var $docdir; + + /** installation root directory (ala PHP's INSTALL_ROOT or + * automake's DESTDIR + * @var string + */ + var $installroot = ''; + + /** debug level + * @var int + */ + var $debug = 1; + + /** temporary directory + * @var string + */ + var $tmpdir; + + /** + * PEAR_Registry object used by the installer + * @var PEAR_Registry + */ + var $registry; + + /** + * array of PEAR_Downloader_Packages + * @var array + */ + var $_downloadedPackages; + + /** List of file transactions queued for an install/upgrade/uninstall. + * + * Format: + * array( + * 0 => array("rename => array("from-file", "to-file")), + * 1 => array("delete" => array("file-to-delete")), + * ... + * ) + * + * @var array + */ + var $file_operations = array(); + + // }}} + + // {{{ constructor + + /** + * PEAR_Installer constructor. + * + * @param object $ui user interface object (instance of PEAR_Frontend_*) + * + * @access public + */ + function PEAR_Installer(&$ui) + { + parent::PEAR_Common(); + $this->setFrontendObject($ui); + $this->debug = $this->config->get('verbose'); + } + + function setOptions($options) + { + $this->_options = $options; + } + + function setConfig(&$config) + { + $this->config = &$config; + $this->_registry = &$config->getRegistry(); + } + + // }}} + + function _removeBackups($files) + { + foreach ($files as $path) { + $this->addFileOperation('removebackup', array($path)); + } + } + + // {{{ _deletePackageFiles() + + /** + * Delete a package's installed files, does not remove empty directories. + * + * @param string package name + * @param string channel name + * @param bool if true, then files are backed up first + * @return bool TRUE on success, or a PEAR error on failure + * @access protected + */ + function _deletePackageFiles($package, $channel = false, $backup = false) + { + if (!$channel) { + $channel = 'pear.php.net'; + } + + if (!strlen($package)) { + return $this->raiseError("No package to uninstall given"); + } + + if (strtolower($package) == 'pear' && $channel == 'pear.php.net') { + // to avoid race conditions, include all possible needed files + require_once 'phar://go-pear.phar/' . 'PEAR/Task/Common.php'; + require_once 'phar://go-pear.phar/' . 'PEAR/Task/Replace.php'; + require_once 'phar://go-pear.phar/' . 'PEAR/Task/Unixeol.php'; + require_once 'phar://go-pear.phar/' . 'PEAR/Task/Windowseol.php'; + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v1.php'; + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v2.php'; + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/Generator/v1.php'; + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/Generator/v2.php'; + } + + $filelist = $this->_registry->packageInfo($package, 'filelist', $channel); + if ($filelist == null) { + return $this->raiseError("$channel/$package not installed"); + } + + $ret = array(); + foreach ($filelist as $file => $props) { + if (empty($props['installed_as'])) { + continue; + } + + $path = $props['installed_as']; + if ($backup) { + $this->addFileOperation('backup', array($path)); + $ret[] = $path; + } + + $this->addFileOperation('delete', array($path)); + } + + if ($backup) { + return $ret; + } + + return true; + } + + // }}} + // {{{ _installFile() + + /** + * @param string filename + * @param array attributes from tag in package.xml + * @param string path to install the file in + * @param array options from command-line + * @access private + */ + function _installFile($file, $atts, $tmp_path, $options) + { + // {{{ return if this file is meant for another platform + static $os; + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + if (isset($atts['platform'])) { + if (empty($os)) { + $os = new OS_Guess(); + } + + if (strlen($atts['platform']) && $atts['platform']{0} == '!') { + $negate = true; + $platform = substr($atts['platform'], 1); + } else { + $negate = false; + $platform = $atts['platform']; + } + + if ((bool) $os->matchSignature($platform) === $negate) { + $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")"); + return PEAR_INSTALLER_SKIPPED; + } + } + // }}} + + $channel = $this->pkginfo->getChannel(); + // {{{ assemble the destination paths + switch ($atts['role']) { + case 'src': + case 'extsrc': + $this->source_files++; + return; + case 'doc': + case 'data': + case 'test': + $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) . + DIRECTORY_SEPARATOR . $this->pkginfo->getPackage(); + unset($atts['baseinstalldir']); + break; + case 'ext': + case 'php': + $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel); + break; + case 'script': + $dest_dir = $this->config->get('bin_dir', null, $channel); + break; + default: + return $this->raiseError("Invalid role `$atts[role]' for file $file"); + } + + $save_destdir = $dest_dir; + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + + if (dirname($file) != '.' && empty($atts['install-as'])) { + $dest_dir .= DIRECTORY_SEPARATOR . dirname($file); + } + + if (empty($atts['install-as'])) { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file); + } else { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as']; + } + $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file; + + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + array(DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), + array($dest_file, $orig_file)); + $final_dest_file = $installed_as = $dest_file; + if (isset($this->_options['packagingroot'])) { + $installedas_dest_dir = dirname($final_dest_file); + $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']); + } else { + $installedas_dest_dir = dirname($final_dest_file); + $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + } + + $dest_dir = dirname($final_dest_file); + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) { + return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED); + } + // }}} + + if (empty($this->_options['register-only']) && + (!file_exists($dest_dir) || !is_dir($dest_dir))) { + if (!$this->mkDirHier($dest_dir)) { + return $this->raiseError("failed to mkdir $dest_dir", + PEAR_INSTALLER_FAILED); + } + $this->log(3, "+ mkdir $dest_dir"); + } + + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (empty($atts['replacements'])) { + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + if (!@copy($orig_file, $dest_file)) { + return $this->raiseError("failed to write $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $orig_file $dest_file"); + if (isset($atts['md5sum'])) { + $md5sum = md5_file($dest_file); + } + } else { + // {{{ file with replacements + if (!file_exists($orig_file)) { + return $this->raiseError("file does not exist", + PEAR_INSTALLER_FAILED); + } + + $contents = file_get_contents($orig_file); + if ($contents === false) { + $contents = ''; + } + + if (isset($atts['md5sum'])) { + $md5sum = md5($contents); + } + + $subst_from = $subst_to = array(); + foreach ($atts['replacements'] as $a) { + $to = ''; + if ($a['type'] == 'php-const') { + if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) { + eval("\$to = $a[to];"); + } else { + if (!isset($options['soft'])) { + $this->log(0, "invalid php-const replacement: $a[to]"); + } + continue; + } + } elseif ($a['type'] == 'pear-config') { + if ($a['to'] == 'master_server') { + $chan = $this->_registry->getChannel($channel); + if (!PEAR::isError($chan)) { + $to = $chan->getServer(); + } else { + $to = $this->config->get($a['to'], null, $channel); + } + } else { + $to = $this->config->get($a['to'], null, $channel); + } + if (is_null($to)) { + if (!isset($options['soft'])) { + $this->log(0, "invalid pear-config replacement: $a[to]"); + } + continue; + } + } elseif ($a['type'] == 'package-info') { + if ($t = $this->pkginfo->packageInfo($a['to'])) { + $to = $t; + } else { + if (!isset($options['soft'])) { + $this->log(0, "invalid package-info replacement: $a[to]"); + } + continue; + } + } + if (!is_null($to)) { + $subst_from[] = $a['from']; + $subst_to[] = $to; + } + } + + $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file"); + if (sizeof($subst_from)) { + $contents = str_replace($subst_from, $subst_to, $contents); + } + + $wp = @fopen($dest_file, "wb"); + if (!is_resource($wp)) { + return $this->raiseError("failed to create $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + if (@fwrite($wp, $contents) === false) { + return $this->raiseError("failed writing to $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + fclose($wp); + // }}} + } + + // {{{ check the md5 + if (isset($md5sum)) { + if (strtolower($md5sum) === strtolower($atts['md5sum'])) { + $this->log(2, "md5sum ok: $final_dest_file"); + } else { + if (empty($options['force'])) { + // delete the file + if (file_exists($dest_file)) { + unlink($dest_file); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError("bad md5sum for file $final_dest_file", + PEAR_INSTALLER_FAILED); + } + + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } else { + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } + } + } + // }}} + // {{{ set file permissions + if (!OS_WINDOWS) { + if ($atts['role'] == 'script') { + $mode = 0777 & ~(int)octdec($this->config->get('umask')); + $this->log(3, "+ chmod +x $dest_file"); + } else { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + } + + if ($atts['role'] != 'src') { + $this->addFileOperation("chmod", array($mode, $dest_file)); + if (!@chmod($dest_file, $mode)) { + if (!isset($options['soft'])) { + $this->log(0, "failed to change mode of $dest_file: $php_errormsg"); + } + } + } + } + // }}} + + if ($atts['role'] == 'src') { + rename($dest_file, $final_dest_file); + $this->log(2, "renamed source file $dest_file to $final_dest_file"); + } else { + $this->addFileOperation("rename", array($dest_file, $final_dest_file, + $atts['role'] == 'ext')); + } + } + + // Store the full path where the file was installed for easy unistall + if ($atts['role'] != 'script') { + $loc = $this->config->get($atts['role'] . '_dir'); + } else { + $loc = $this->config->get('bin_dir'); + } + + if ($atts['role'] != 'src') { + $this->addFileOperation("installed_as", array($file, $installed_as, + $loc, + dirname(substr($installedas_dest_file, strlen($loc))))); + } + + //$this->log(2, "installed: $dest_file"); + return PEAR_INSTALLER_OK; + } + + // }}} + // {{{ _installFile2() + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string filename + * @param array attributes from tag in package.xml + * @param string path to install the file in + * @param array options from command-line + * @access private + */ + function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options) + { + $atts = $real_atts; + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + $channel = $pkg->getChannel(); + // {{{ assemble the destination paths + if (!in_array($atts['attribs']['role'], + PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) { + return $this->raiseError('Invalid role `' . $atts['attribs']['role'] . + "' for file $file"); + } + + $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config); + $err = $role->setup($this, $pkg, $atts['attribs'], $file); + if (PEAR::isError($err)) { + return $err; + } + + if (!$role->isInstallable()) { + return; + } + + $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path); + if (PEAR::isError($info)) { + return $info; + } + + list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info; + if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) { + return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED); + } + + $final_dest_file = $installed_as = $dest_file; + if (isset($this->_options['packagingroot'])) { + $final_dest_file = $this->_prependPath($final_dest_file, + $this->_options['packagingroot']); + } + + $dest_dir = dirname($final_dest_file); + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + // }}} + + if (empty($this->_options['register-only'])) { + if (!file_exists($dest_dir) || !is_dir($dest_dir)) { + if (!$this->mkDirHier($dest_dir)) { + return $this->raiseError("failed to mkdir $dest_dir", + PEAR_INSTALLER_FAILED); + } + $this->log(3, "+ mkdir $dest_dir"); + } + } + + $attribs = $atts['attribs']; + unset($atts['attribs']); + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (!count($atts)) { // no tasks + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + if (!@copy($orig_file, $dest_file)) { + return $this->raiseError("failed to write $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $orig_file $dest_file"); + if (isset($attribs['md5sum'])) { + $md5sum = md5_file($dest_file); + } + } else { // file with tasks + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + $contents = file_get_contents($orig_file); + if ($contents === false) { + $contents = ''; + } + + if (isset($attribs['md5sum'])) { + $md5sum = md5($contents); + } + + foreach ($atts as $tag => $raw) { + $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag); + $task = "PEAR_Task_$tag"; + $task = &new $task($this->config, $this, PEAR_TASK_INSTALL); + if (!$task->isScript()) { // scripts are only handled after installation + $task->init($raw, $attribs, $pkg->getLastInstalledVersion()); + $res = $task->startSession($pkg, $contents, $final_dest_file); + if ($res === false) { + continue; // skip this file + } + + if (PEAR::isError($res)) { + return $res; + } + + $contents = $res; // save changes + } + + $wp = @fopen($dest_file, "wb"); + if (!is_resource($wp)) { + return $this->raiseError("failed to create $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + if (fwrite($wp, $contents) === false) { + return $this->raiseError("failed writing to $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + fclose($wp); + } + } + + // {{{ check the md5 + if (isset($md5sum)) { + // Make sure the original md5 sum matches with expected + if (strtolower($md5sum) === strtolower($attribs['md5sum'])) { + $this->log(2, "md5sum ok: $final_dest_file"); + + if (isset($contents)) { + // set md5 sum based on $content in case any tasks were run. + $real_atts['attribs']['md5sum'] = md5($contents); + } + } else { + if (empty($options['force'])) { + // delete the file + if (file_exists($dest_file)) { + unlink($dest_file); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError("bad md5sum for file $final_dest_file", + PEAR_INSTALLER_FAILED); + } + + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } else { + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } + } + } else { + $real_atts['attribs']['md5sum'] = md5_file($dest_file); + } + + // }}} + // {{{ set file permissions + if (!OS_WINDOWS) { + if ($role->isExecutable()) { + $mode = 0777 & ~(int)octdec($this->config->get('umask')); + $this->log(3, "+ chmod +x $dest_file"); + } else { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + } + + if ($attribs['role'] != 'src') { + $this->addFileOperation("chmod", array($mode, $dest_file)); + if (!@chmod($dest_file, $mode)) { + if (!isset($options['soft'])) { + $this->log(0, "failed to change mode of $dest_file: $php_errormsg"); + } + } + } + } + // }}} + + if ($attribs['role'] == 'src') { + rename($dest_file, $final_dest_file); + $this->log(2, "renamed source file $dest_file to $final_dest_file"); + } else { + $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension())); + } + } + + // Store the full path where the file was installed for easy uninstall + if ($attribs['role'] != 'src') { + $loc = $this->config->get($role->getLocationConfig(), null, $channel); + $this->addFileOperation('installed_as', array($file, $installed_as, + $loc, + dirname(substr($installed_as, strlen($loc))))); + } + + //$this->log(2, "installed: $dest_file"); + return PEAR_INSTALLER_OK; + } + + // }}} + // {{{ addFileOperation() + + /** + * Add a file operation to the current file transaction. + * + * @see startFileTransaction() + * @param string $type This can be one of: + * - rename: rename a file ($data has 3 values) + * - backup: backup an existing file ($data has 1 value) + * - removebackup: clean up backups created during install ($data has 1 value) + * - chmod: change permissions on a file ($data has 2 values) + * - delete: delete a file ($data has 1 value) + * - rmdir: delete a directory if empty ($data has 1 value) + * - installed_as: mark a file as installed ($data has 4 values). + * @param array $data For all file operations, this array must contain the + * full path to the file or directory that is being operated on. For + * the rename command, the first parameter must be the file to rename, + * the second its new name, the third whether this is a PHP extension. + * + * The installed_as operation contains 4 elements in this order: + * 1. Filename as listed in the filelist element from package.xml + * 2. Full path to the installed file + * 3. Full path from the php_dir configuration variable used in this + * installation + * 4. Relative path from the php_dir that this file is installed in + */ + function addFileOperation($type, $data) + { + if (!is_array($data)) { + return $this->raiseError('Internal Error: $data in addFileOperation' + . ' must be an array, was ' . gettype($data)); + } + + if ($type == 'chmod') { + $octmode = decoct($data[0]); + $this->log(3, "adding to transaction: $type $octmode $data[1]"); + } else { + $this->log(3, "adding to transaction: $type " . implode(" ", $data)); + } + $this->file_operations[] = array($type, $data); + } + + // }}} + // {{{ startFileTransaction() + + function startFileTransaction($rollback_in_case = false) + { + if (count($this->file_operations) && $rollback_in_case) { + $this->rollbackFileTransaction(); + } + $this->file_operations = array(); + } + + // }}} + // {{{ commitFileTransaction() + + function commitFileTransaction() + { + $n = count($this->file_operations); + $this->log(2, "about to commit $n file operations"); + // {{{ first, check permissions and such manually + $errors = array(); + foreach ($this->file_operations as $tr) { + list($type, $data) = $tr; + switch ($type) { + case 'rename': + if (!file_exists($data[0])) { + $errors[] = "cannot rename file $data[0], doesn't exist"; + } + + // check that dest dir. is writable + if (!is_writable(dirname($data[1]))) { + $errors[] = "permission denied ($type): $data[1]"; + } + break; + case 'chmod': + // check that file is writable + if (!is_writable($data[1])) { + $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]); + } + break; + case 'delete': + if (!file_exists($data[0])) { + $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted"); + } + // check that directory is writable + if (file_exists($data[0])) { + if (!is_writable(dirname($data[0]))) { + $errors[] = "permission denied ($type): $data[0]"; + } else { + // make sure the file to be deleted can be opened for writing + $fp = false; + if (!is_dir($data[0]) && + (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) { + $errors[] = "permission denied ($type): $data[0]"; + } elseif ($fp) { + fclose($fp); + } + } + } + break; + } + + } + // }}} + $m = count($errors); + if ($m > 0) { + foreach ($errors as $error) { + if (!isset($this->_options['soft'])) { + $this->log(1, $error); + } + } + + if (!isset($this->_options['ignore-errors'])) { + return false; + } + } + + $this->_dirtree = array(); + // {{{ really commit the transaction + foreach ($this->file_operations as $i => $tr) { + if (!$tr) { + // support removal of non-existing backups + continue; + } + + list($type, $data) = $tr; + switch ($type) { + case 'backup': + if (!file_exists($data[0])) { + $this->file_operations[$i] = false; + break; + } + + if (!@copy($data[0], $data[0] . '.bak')) { + $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] . + '.bak ' . $php_errormsg); + return false; + } + $this->log(3, "+ backup $data[0] to $data[0].bak"); + break; + case 'removebackup': + if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) { + unlink($data[0] . '.bak'); + $this->log(3, "+ rm backup of $data[0] ($data[0].bak)"); + } + break; + case 'rename': + $test = file_exists($data[1]) ? @unlink($data[1]) : null; + if (!$test && file_exists($data[1])) { + if ($data[2]) { + $extra = ', this extension must be installed manually. Rename to "' . + basename($data[1]) . '"'; + } else { + $extra = ''; + } + + if (!isset($this->_options['soft'])) { + $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' . + $data[0] . $extra); + } + + if (!isset($this->_options['ignore-errors'])) { + return false; + } + } + + // permissions issues with rename - copy() is far superior + $perms = @fileperms($data[0]); + if (!@copy($data[0], $data[1])) { + $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] . + ' ' . $php_errormsg); + return false; + } + + // copy over permissions, otherwise they are lost + @chmod($data[1], $perms); + @unlink($data[0]); + $this->log(3, "+ mv $data[0] $data[1]"); + break; + case 'chmod': + if (!@chmod($data[1], $data[0])) { + $this->log(1, 'Could not chmod ' . $data[1] . ' to ' . + decoct($data[0]) . ' ' . $php_errormsg); + return false; + } + + $octmode = decoct($data[0]); + $this->log(3, "+ chmod $octmode $data[1]"); + break; + case 'delete': + if (file_exists($data[0])) { + if (!@unlink($data[0])) { + $this->log(1, 'Could not delete ' . $data[0] . ' ' . + $php_errormsg); + return false; + } + $this->log(3, "+ rm $data[0]"); + } + break; + case 'rmdir': + if (file_exists($data[0])) { + do { + $testme = opendir($data[0]); + while (false !== ($entry = readdir($testme))) { + if ($entry == '.' || $entry == '..') { + continue; + } + closedir($testme); + break 2; // this directory is not empty and can't be + // deleted + } + + closedir($testme); + if (!@rmdir($data[0])) { + $this->log(1, 'Could not rmdir ' . $data[0] . ' ' . + $php_errormsg); + return false; + } + $this->log(3, "+ rmdir $data[0]"); + } while (false); + } + break; + case 'installed_as': + $this->pkginfo->setInstalledAs($data[0], $data[1]); + if (!isset($this->_dirtree[dirname($data[1])])) { + $this->_dirtree[dirname($data[1])] = true; + $this->pkginfo->setDirtree(dirname($data[1])); + + while(!empty($data[3]) && dirname($data[3]) != $data[3] && + $data[3] != '/' && $data[3] != '\\') { + $this->pkginfo->setDirtree($pp = + $this->_prependPath($data[3], $data[2])); + $this->_dirtree[$pp] = true; + $data[3] = dirname($data[3]); + } + } + break; + } + } + // }}} + $this->log(2, "successfully committed $n file operations"); + $this->file_operations = array(); + return true; + } + + // }}} + // {{{ rollbackFileTransaction() + + function rollbackFileTransaction() + { + $n = count($this->file_operations); + $this->log(2, "rolling back $n file operations"); + foreach ($this->file_operations as $tr) { + list($type, $data) = $tr; + switch ($type) { + case 'backup': + if (file_exists($data[0] . '.bak')) { + if (file_exists($data[0] && is_writable($data[0]))) { + unlink($data[0]); + } + @copy($data[0] . '.bak', $data[0]); + $this->log(3, "+ restore $data[0] from $data[0].bak"); + } + break; + case 'removebackup': + if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) { + unlink($data[0] . '.bak'); + $this->log(3, "+ rm backup of $data[0] ($data[0].bak)"); + } + break; + case 'rename': + @unlink($data[0]); + $this->log(3, "+ rm $data[0]"); + break; + case 'mkdir': + @rmdir($data[0]); + $this->log(3, "+ rmdir $data[0]"); + break; + case 'chmod': + break; + case 'delete': + break; + case 'installed_as': + $this->pkginfo->setInstalledAs($data[0], false); + break; + } + } + $this->pkginfo->resetDirtree(); + $this->file_operations = array(); + } + + // }}} + // {{{ mkDirHier($dir) + + function mkDirHier($dir) + { + $this->addFileOperation('mkdir', array($dir)); + return parent::mkDirHier($dir); + } + + // }}} + // {{{ download() + + /** + * Download any files and their dependencies, if necessary + * + * @param array a mixed list of package names, local files, or package.xml + * @param PEAR_Config + * @param array options from the command line + * @param array this is the array that will be populated with packages to + * install. Format of each entry: + * + * + * array('pkg' => 'package_name', 'file' => '/path/to/local/file', + * 'info' => array() // parsed package.xml + * ); + * + * @param array this will be populated with any error messages + * @param false private recursion variable + * @param false private recursion variable + * @param false private recursion variable + * @deprecated in favor of PEAR_Downloader + */ + function download($packages, $options, &$config, &$installpackages, + &$errors, $installed = false, $willinstall = false, $state = false) + { + // trickiness: initialize here + parent::PEAR_Downloader($this->ui, $options, $config); + $ret = parent::download($packages); + $errors = $this->getErrorMsgs(); + $installpackages = $this->getDownloadedPackages(); + trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " . + "in favor of PEAR_Downloader class", E_USER_WARNING); + return $ret; + } + + // }}} + // {{{ _parsePackageXml() + + function _parsePackageXml(&$descfile, &$tmpdir) + { + if (substr($descfile, -4) == '.xml') { + $tmpdir = false; + } else { + // {{{ Decompress pack in tmp dir ------------------------------------- + + // To allow relative package file names + $descfile = realpath($descfile); + + if (PEAR::isError($tmpdir = System::mktemp('-d'))) { + return $tmpdir; + } + $this->log(3, '+ tmp dir created at ' . $tmpdir); + // }}} + } + + // Parse xml file ----------------------------------------------- + $pkg = new PEAR_PackageFile($this->config, $this->debug, $tmpdir); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($p)) { + if (is_array($p->getUserInfo())) { + foreach ($p->getUserInfo() as $err) { + $loglevel = $err['level'] == 'error' ? 0 : 1; + if (!isset($this->_options['soft'])) { + $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']); + } + } + } + return $this->raiseError('Installation failed: invalid package file'); + } + + $descfile = $p->getPackageFile(); + return $p; + } + + // }}} + /** + * Set the list of PEAR_Downloader_Package objects to allow more sane + * dependency validation + * @param array + */ + function setDownloadedPackages(&$pkgs) + { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($pkgs); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + return $err; + } + $this->_downloadedPackages = &$pkgs; + } + + /** + * Set the list of PEAR_Downloader_Package objects to allow more sane + * dependency validation + * @param array + */ + function setUninstallPackages(&$pkgs) + { + $this->_downloadedPackages = &$pkgs; + } + + function getInstallPackages() + { + return $this->_downloadedPackages; + } + + // {{{ install() + + /** + * Installs the files within the package file specified. + * + * @param string|PEAR_Downloader_Package $pkgfile path to the package file, + * or a pre-initialized packagefile object + * @param array $options + * recognized options: + * - installroot : optional prefix directory for installation + * - force : force installation + * - register-only : update registry but don't install files + * - upgrade : upgrade existing install + * - soft : fail silently + * - nodeps : ignore dependency conflicts/missing dependencies + * - alldeps : install all dependencies + * - onlyreqdeps : install only required dependencies + * + * @return array|PEAR_Error package info if successful + */ + function install($pkgfile, $options = array()) + { + $this->_options = $options; + $this->_registry = &$this->config->getRegistry(); + if (is_object($pkgfile)) { + $dlpkg = &$pkgfile; + $pkg = $pkgfile->getPackageFile(); + $pkgfile = $pkg->getArchiveFile(); + $descfile = $pkg->getPackageFile(); + $tmpdir = dirname($descfile); + } else { + $descfile = $pkgfile; + $tmpdir = ''; + $pkg = $this->_parsePackageXml($descfile, $tmpdir); + if (PEAR::isError($pkg)) { + return $pkg; + } + } + + if (realpath($descfile) != realpath($pkgfile)) { + $tar = new Archive_Tar($pkgfile); + if (!$tar->extract($tmpdir)) { + return $this->raiseError("unable to unpack $pkgfile"); + } + } + + $pkgname = $pkg->getName(); + $channel = $pkg->getChannel(); + if (isset($this->_options['packagingroot'])) { + $regdir = $this->_prependPath( + $this->config->get('php_dir', null, 'pear.php.net'), + $this->_options['packagingroot']); + + $packrootphp_dir = $this->_prependPath( + $this->config->get('php_dir', null, $channel), + $this->_options['packagingroot']); + } + + if (isset($options['installroot'])) { + $this->config->setInstallRoot($options['installroot']); + $this->_registry = &$this->config->getRegistry(); + $installregistry = &$this->_registry; + $this->installroot = ''; // all done automagically now + $php_dir = $this->config->get('php_dir', null, $channel); + } else { + $this->config->setInstallRoot(false); + $this->_registry = &$this->config->getRegistry(); + if (isset($this->_options['packagingroot'])) { + $installregistry = &new PEAR_Registry($regdir); + if (!$installregistry->channelExists($channel, true)) { + // we need to fake a channel-discover of this channel + $chanobj = $this->_registry->getChannel($channel, true); + $installregistry->addChannel($chanobj); + } + $php_dir = $packrootphp_dir; + } else { + $installregistry = &$this->_registry; + $php_dir = $this->config->get('php_dir', null, $channel); + } + $this->installroot = ''; + } + + // {{{ checks to do when not in "force" mode + if (empty($options['force']) && + (file_exists($this->config->get('php_dir')) && + is_dir($this->config->get('php_dir')))) { + $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname); + $instfilelist = $pkg->getInstallationFileList(true); + if (PEAR::isError($instfilelist)) { + return $instfilelist; + } + + // ensure we have the most accurate registry + $installregistry->flushFileMap(); + $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1'); + if (PEAR::isError($test)) { + return $test; + } + + if (sizeof($test)) { + $pkgs = $this->getInstallPackages(); + $found = false; + foreach ($pkgs as $param) { + if ($pkg->isSubpackageOf($param)) { + $found = true; + break; + } + } + + if ($found) { + // subpackages can conflict with earlier versions of parent packages + $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel()); + $tmp = $test; + foreach ($tmp as $file => $info) { + if (is_array($info)) { + if (strtolower($info[1]) == strtolower($param->getPackage()) && + strtolower($info[0]) == strtolower($param->getChannel()) + ) { + if (isset($parentreg['filelist'][$file])) { + unset($parentreg['filelist'][$file]); + } else{ + $pos = strpos($file, '/'); + $basedir = substr($file, 0, $pos); + $file2 = substr($file, $pos + 1); + if (isset($parentreg['filelist'][$file2]['baseinstalldir']) + && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir + ) { + unset($parentreg['filelist'][$file2]); + } + } + + unset($test[$file]); + } + } else { + if (strtolower($param->getChannel()) != 'pear.php.net') { + continue; + } + + if (strtolower($info) == strtolower($param->getPackage())) { + if (isset($parentreg['filelist'][$file])) { + unset($parentreg['filelist'][$file]); + } else{ + $pos = strpos($file, '/'); + $basedir = substr($file, 0, $pos); + $file2 = substr($file, $pos + 1); + if (isset($parentreg['filelist'][$file2]['baseinstalldir']) + && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir + ) { + unset($parentreg['filelist'][$file2]); + } + } + + unset($test[$file]); + } + } + } + + $pfk = &new PEAR_PackageFile($this->config); + $parentpkg = &$pfk->fromArray($parentreg); + $installregistry->updatePackage2($parentpkg); + } + + if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) { + $tmp = $test; + foreach ($tmp as $file => $info) { + if (is_string($info)) { + // pear.php.net packages are always stored as strings + if (strtolower($info) == strtolower($param->getPackage())) { + // upgrading existing package + unset($test[$file]); + } + } + } + } + + if (count($test)) { + $msg = "$channel/$pkgname: conflicting files found:\n"; + $longest = max(array_map("strlen", array_keys($test))); + $fmt = "%${longest}s (%s)\n"; + foreach ($test as $file => $info) { + if (!is_array($info)) { + $info = array('pear.php.net', $info); + } + $info = $info[0] . '/' . $info[1]; + $msg .= sprintf($fmt, $file, $info); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError($msg); + } + + if (!isset($options['soft'])) { + $this->log(0, "WARNING: $msg"); + } + } + } + } + // }}} + + $this->startFileTransaction(); + + if (empty($options['upgrade']) && empty($options['soft'])) { + // checks to do only when installing new packages + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + } + } else { + $test = $installregistry->packageExists($pkgname, $channel); + } + + if (empty($options['force']) && $test) { + return $this->raiseError("$channel/$pkgname is already installed"); + } + } else { + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + $usechannel = 'pear.php.net'; + } + } else { + $test = $installregistry->packageExists($pkgname, $channel); + } + + if ($test) { + $v1 = $installregistry->packageInfo($pkgname, 'version', $usechannel); + $v2 = $pkg->getVersion(); + $cmp = version_compare("$v1", "$v2", 'gt'); + if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) { + return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)"); + } + + if (empty($options['register-only'])) { + // when upgrading, remove old release's files first: + if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel, + true))) { + if (!isset($options['ignore-errors'])) { + return $this->raiseError($err); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $err->getMessage()); + } + } else { + $backedup = $err; + } + } + } + } + + // {{{ Copy files to dest dir --------------------------------------- + + // info from the package it self we want to access from _installFile + $this->pkginfo = &$pkg; + // used to determine whether we should build any C code + $this->source_files = 0; + + $savechannel = $this->config->get('default_channel'); + if (empty($options['register-only']) && !is_dir($php_dir)) { + if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) { + return $this->raiseError("no installation destination directory '$php_dir'\n"); + } + } + + $tmp_path = dirname($descfile); + if (substr($pkgfile, -4) != '.xml') { + $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion(); + } + + $this->configSet('default_channel', $channel); + // {{{ install files + + $ver = $pkg->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + $filelist = $pkg->getInstallationFilelist(); + } else { + $filelist = $pkg->getFileList(); + } + + if (PEAR::isError($filelist)) { + return $filelist; + } + + $p = &$installregistry->getPackage($pkgname, $channel); + $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false; + + $pkg->resetFilelist(); + $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(), + 'version', $pkg->getChannel())); + foreach ($filelist as $file => $atts) { + $this->expectError(PEAR_INSTALLER_FAILED); + if ($pkg->getPackagexmlVersion() == '1.0') { + $res = $this->_installFile($file, $atts, $tmp_path, $options); + } else { + $res = $this->_installFile2($pkg, $file, $atts, $tmp_path, $options); + } + $this->popExpect(); + + if (PEAR::isError($res)) { + if (empty($options['ignore-errors'])) { + $this->rollbackFileTransaction(); + if ($res->getMessage() == "file does not exist") { + $this->raiseError("file $file in package.xml does not exist"); + } + + return $this->raiseError($res); + } + + if (!isset($options['soft'])) { + $this->log(0, "Warning: " . $res->getMessage()); + } + } + + $real = isset($atts['attribs']) ? $atts['attribs'] : $atts; + if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') { + // Register files that were installed + $pkg->installedFile($file, $atts); + } + } + // }}} + + // {{{ compile and install source files + if ($this->source_files > 0 && empty($options['nobuild'])) { + if (PEAR::isError($err = + $this->_compileSourceFiles($savechannel, $pkg))) { + return $err; + } + } + // }}} + + if (isset($backedup)) { + $this->_removeBackups($backedup); + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED); + } + // }}} + + $ret = false; + $installphase = 'install'; + $oldversion = false; + // {{{ Register that the package is installed ----------------------- + if (empty($options['upgrade'])) { + // if 'force' is used, replace the info in registry + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + $usechannel = 'pear.php.net'; + } + } else { + $test = $installregistry->packageExists($pkgname, $channel); + } + + if (!empty($options['force']) && $test) { + $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel); + $installregistry->deletePackage($pkgname, $usechannel); + } + $ret = $installregistry->addPackage2($pkg); + } else { + if ($dirtree) { + $this->startFileTransaction(); + // attempt to delete empty directories + uksort($dirtree, array($this, '_sortDirs')); + foreach($dirtree as $dir => $notused) { + $this->addFileOperation('rmdir', array($dir)); + } + $this->commitFileTransaction(); + } + + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + $usechannel = 'pear.php.net'; + } + } else { + $test = $installregistry->packageExists($pkgname, $channel); + } + + // new: upgrade installs a package if it isn't installed + if (!$test) { + $ret = $installregistry->addPackage2($pkg); + } else { + if ($usechannel != $channel) { + $installregistry->deletePackage($pkgname, $usechannel); + $ret = $installregistry->addPackage2($pkg); + } else { + $ret = $installregistry->updatePackage2($pkg); + } + $installphase = 'upgrade'; + } + } + + if (!$ret) { + $this->configSet('default_channel', $savechannel); + return $this->raiseError("Adding package $channel/$pkgname to registry failed"); + } + // }}} + + $this->configSet('default_channel', $savechannel); + if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist + if (PEAR_Task_Common::hasPostinstallTasks()) { + PEAR_Task_Common::runPostinstallTasks($installphase); + } + } + + return $pkg->toArray(true); + } + + // }}} + + // {{{ _compileSourceFiles() + /** + * @param string + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function _compileSourceFiles($savechannel, &$filelist) + { + require_once 'phar://go-pear.phar/' . 'PEAR/Builder.php'; + $this->log(1, "$this->source_files source files, building"); + $bob = &new PEAR_Builder($this->ui); + $bob->debug = $this->debug; + $built = $bob->build($filelist, array(&$this, '_buildCallback')); + if (PEAR::isError($built)) { + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + return $built; + } + + $this->log(1, "\nBuild process completed successfully"); + foreach ($built as $ext) { + $bn = basename($ext['file']); + list($_ext_name, $_ext_suff) = explode('.', $bn); + if ($_ext_suff == '.so' || $_ext_suff == '.dll') { + if (extension_loaded($_ext_name)) { + $this->raiseError("Extension '$_ext_name' already loaded. " . + 'Please unload it in your php.ini file ' . + 'prior to install or upgrade'); + } + $role = 'ext'; + } else { + $role = 'src'; + } + + $dest = $ext['dest']; + $packagingroot = ''; + if (isset($this->_options['packagingroot'])) { + $packagingroot = $this->_options['packagingroot']; + } + + $copyto = $this->_prependPath($dest, $packagingroot); + $extra = $copyto != $dest ? " as '$copyto'" : ''; + $this->log(1, "Installing '$dest'$extra"); + + $copydir = dirname($copyto); + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (!file_exists($copydir) || !is_dir($copydir)) { + if (!$this->mkDirHier($copydir)) { + return $this->raiseError("failed to mkdir $copydir", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ mkdir $copydir"); + } + + if (!@copy($ext['file'], $copyto)) { + return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $ext[file] $copyto"); + $this->addFileOperation('rename', array($ext['file'], $copyto)); + if (!OS_WINDOWS) { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + $this->addFileOperation('chmod', array($mode, $copyto)); + if (!@chmod($copyto, $mode)) { + $this->log(0, "failed to change mode of $copyto ($php_errormsg)"); + } + } + } + + + $data = array( + 'role' => $role, + 'name' => $bn, + 'installed_as' => $dest, + 'php_api' => $ext['php_api'], + 'zend_mod_api' => $ext['zend_mod_api'], + 'zend_ext_api' => $ext['zend_ext_api'], + ); + + if ($filelist->getPackageXmlVersion() == '1.0') { + $filelist->installedFile($bn, $data); + } else { + $filelist->installedFile($bn, array('attribs' => $data)); + } + } + } + + // }}} + function &getUninstallPackages() + { + return $this->_downloadedPackages; + } + // {{{ uninstall() + + /** + * Uninstall a package + * + * This method removes all files installed by the application, and then + * removes any empty directories. + * @param string package name + * @param array Command-line options. Possibilities include: + * + * - installroot: base installation dir, if not the default + * - register-only : update registry but don't remove files + * - nodeps: do not process dependencies of other packages to ensure + * uninstallation does not break things + */ + function uninstall($package, $options = array()) + { + $installRoot = isset($options['installroot']) ? $options['installroot'] : ''; + $this->config->setInstallRoot($installRoot); + + $this->installroot = ''; + $this->_registry = &$this->config->getRegistry(); + if (is_object($package)) { + $channel = $package->getChannel(); + $pkg = $package; + $package = $pkg->getPackage(); + } else { + $pkg = false; + $info = $this->_registry->parsePackageName($package, + $this->config->get('default_channel')); + $channel = $info['channel']; + $package = $info['package']; + } + + $savechannel = $this->config->get('default_channel'); + $this->configSet('default_channel', $channel); + if (!is_object($pkg)) { + $pkg = $this->_registry->getPackage($package, $channel); + } + + if (!$pkg) { + $this->configSet('default_channel', $savechannel); + return $this->raiseError($this->_registry->parsedPackageNameToString( + array( + 'channel' => $channel, + 'package' => $package + ), true) . ' not installed'); + } + + if ($pkg->getInstalledBinary()) { + // this is just an alias for a binary package + return $this->_registry->deletePackage($package, $channel); + } + + $filelist = $pkg->getFilelist(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!class_exists('PEAR_Dependency2')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Dependency2.php'; + } + + $depchecker = &new PEAR_Dependency2($this->config, $options, + array('channel' => $channel, 'package' => $package), + PEAR_VALIDATE_UNINSTALLING); + $e = $depchecker->validatePackageUninstall($this); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['ignore-errors'])) { + return $this->raiseError($e); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $e->getMessage()); + } + } elseif (is_array($e)) { + if (!isset($options['soft'])) { + $this->log(0, $e[0]); + } + } + + $this->pkginfo = &$pkg; + // pretty much nothing happens if we are only registering the uninstall + if (empty($options['register-only'])) { + // {{{ Delete the files + $this->startFileTransaction(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) { + PEAR::popErrorHandling(); + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + if (!isset($options['ignore-errors'])) { + return $this->raiseError($err); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $err->getMessage()); + } + } else { + PEAR::popErrorHandling(); + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + if (!isset($options['ignore-errors'])) { + return $this->raiseError("uninstall failed"); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: uninstall failed'); + } + } else { + $this->startFileTransaction(); + $dirtree = $pkg->getDirTree(); + if ($dirtree === false) { + $this->configSet('default_channel', $savechannel); + return $this->_registry->deletePackage($package, $channel); + } + + // attempt to delete empty directories + uksort($dirtree, array($this, '_sortDirs')); + foreach($dirtree as $dir => $notused) { + $this->addFileOperation('rmdir', array($dir)); + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + if (!isset($options['ignore-errors'])) { + return $this->raiseError("uninstall failed"); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: uninstall failed'); + } + } + } + // }}} + } + + $this->configSet('default_channel', $savechannel); + // Register that the package is no longer installed + return $this->_registry->deletePackage($package, $channel); + } + + /** + * Sort a list of arrays of array(downloaded packagefilename) by dependency. + * + * It also removes duplicate dependencies + * @param array an array of PEAR_PackageFile_v[1/2] objects + * @return array|PEAR_Error array of array(packagefilename, package.xml contents) + */ + function sortPackagesForUninstall(&$packages) + { + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config); + if (PEAR::isError($this->_dependencyDB)) { + return $this->_dependencyDB; + } + usort($packages, array(&$this, '_sortUninstall')); + } + + function _sortUninstall($a, $b) + { + if (!$a->getDeps() && !$b->getDeps()) { + return 0; // neither package has dependencies, order is insignificant + } + if ($a->getDeps() && !$b->getDeps()) { + return -1; // $a must be installed after $b because $a has dependencies + } + if (!$a->getDeps() && $b->getDeps()) { + return 1; // $b must be installed after $a because $b has dependencies + } + // both packages have dependencies + if ($this->_dependencyDB->dependsOn($a, $b)) { + return -1; + } + if ($this->_dependencyDB->dependsOn($b, $a)) { + return 1; + } + return 0; + } + + // }}} + // {{{ _sortDirs() + function _sortDirs($a, $b) + { + if (strnatcmp($a, $b) == -1) return 1; + if (strnatcmp($a, $b) == 1) return -1; + return 0; + } + + // }}} + + // {{{ _buildCallback() + + function _buildCallback($what, $data) + { + if (($what == 'cmdoutput' && $this->debug > 1) || + ($what == 'output' && $this->debug > 0)) { + $this->ui->outputData(rtrim($data), 'build'); + } + } + + // }}} +} + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Role.php 278552 2009-04-10 19:42:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base class for installer roles + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Installer/Role/Common.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/XMLParser.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role +{ + /** + * Set up any additional configuration variables that file roles require + * + * Never call this directly, it is called by the PEAR_Config constructor + * @param PEAR_Config + * @access private + * @static + */ + function initializeConfig(&$config) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $class => $info) { + if (!$info['config_vars']) { + continue; + } + + $config->_addConfigVars($class, $info['config_vars']); + } + } + + /** + * @param PEAR_PackageFile_v2 + * @param string role name + * @param PEAR_Config + * @return PEAR_Installer_Role_Common + * @static + */ + function &factory($pkg, $role, &$config) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + if (!in_array($role, PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) { + $a = false; + return $a; + } + + $a = 'PEAR_Installer_Role_' . ucfirst($role); + if (!class_exists($a)) { + require_once 'phar://go-pear.phar/' . str_replace('_', '/', $a) . '.php'; + } + + $b = new $a($config); + return $b; + } + + /** + * Get a list of file roles that are valid for the particular release type. + * + * For instance, src files serve no purpose in regular php releases. + * @param string + * @param bool clear cache + * @return array + * @static + */ + function getValidRoles($release, $clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret = array(); + if ($clear) { + $ret = array(); + } + + if (isset($ret[$release])) { + return $ret[$release]; + } + + $ret[$release] = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if (in_array($release, $okreleases['releasetypes'])) { + $ret[$release][] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret[$release]; + } + + /** + * Get a list of roles that require their files to be installed + * + * Most roles must be installed, but src and package roles, for instance + * are pseudo-roles. src files are compiled into a new extension. Package + * roles are actually fully bundled releases of a package + * @param bool clear cache + * @return array + * @static + */ + function getInstallableRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['installable']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Return an array of roles that are affected by the baseinstalldir attribute + * + * Most roles ignore this attribute, and instead install directly into: + * PackageName/filepath + * so a tests file tests/file.phpt is installed into PackageName/tests/filepath.php + * @param bool clear cache + * @return array + * @static + */ + function getBaseinstallRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['honorsbaseinstall']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Return an array of file roles that should be analyzed for PHP content at package time, + * like the "php" role. + * @param bool clear cache + * @return array + * @static + */ + function getPhpRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['phpfile']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Scan through the Command directory looking for classes + * and see what commands they implement. + * @param string which directory to look for classes, defaults to + * the Installer/Roles subdirectory of + * the directory from where this file (__FILE__) is + * included. + * + * @return bool TRUE on success, a PEAR error on failure + * @access public + * @static + */ + function registerRoles($dir = null) + { + $GLOBALS['_PEAR_INSTALLER_ROLES'] = array(); + $parser = new PEAR_XMLParser; + if ($dir === null) { + $dir = dirname(__FILE__) . '/Role'; + } + + if (!file_exists($dir) || !is_dir($dir)) { + return PEAR::raiseError("registerRoles: opendir($dir) failed: does not exist/is not directory"); + } + + $dp = @opendir($dir); + if (empty($dp)) { + return PEAR::raiseError("registerRoles: opendir($dir) failed: $php_errmsg"); + } + + while ($entry = readdir($dp)) { + if ($entry{0} == '.' || substr($entry, -4) != '.xml') { + continue; + } + + $class = "PEAR_Installer_Role_".substr($entry, 0, -4); + // List of roles + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'][$class])) { + $file = "$dir/$entry"; + $parser->parse(file_get_contents($file)); + $data = $parser->getData(); + if (!is_array($data['releasetypes'])) { + $data['releasetypes'] = array($data['releasetypes']); + } + + $GLOBALS['_PEAR_INSTALLER_ROLES'][$class] = $data; + } + } + + closedir($dp); + ksort($GLOBALS['_PEAR_INSTALLER_ROLES']); + PEAR_Installer_Role::getBaseinstallRoles(true); + PEAR_Installer_Role::getInstallableRoles(true); + PEAR_Installer_Role::getPhpRoles(true); + PEAR_Installer_Role::getValidRoles('****', true); + return true; + } +} + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class for all installation roles. + * + * This class allows extensibility of file roles. Packages with complex + * customization can now provide custom file roles along with the possibility of + * adding configuration values to match. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Common +{ + /** + * @var PEAR_Config + * @access protected + */ + var $config; + + /** + * @param PEAR_Config + */ + function PEAR_Installer_Role_Common(&$config) + { + $this->config = $config; + } + + /** + * Retrieve configuration information about a file role from its XML info + * + * @param string $role Role Classname, as in "PEAR_Installer_Role_Data" + * @return array + */ + function getInfo($role) + { + if (empty($GLOBALS['_PEAR_INSTALLER_ROLES'][$role])) { + return PEAR::raiseError('Unknown Role class: "' . $role . '"'); + } + return $GLOBALS['_PEAR_INSTALLER_ROLES'][$role]; + } + + /** + * This is called for each file to set up the directories and files + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param array attributes from the tag + * @param string file name + * @return array an array consisting of: + * + * 1 the original, pre-baseinstalldir installation directory + * 2 the final installation directory + * 3 the full path to the final location of the file + * 4 the location of the pre-installation file + */ + function processInstallation($pkg, $atts, $file, $tmp_path, $layer = null) + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + if (!$roleInfo['locationconfig']) { + return false; + } + if ($roleInfo['honorsbaseinstall']) { + $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'], $layer, + $pkg->getChannel()); + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + } elseif ($roleInfo['unusualbaseinstall']) { + $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'], + $layer, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage(); + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + } else { + $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'], + $layer, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage(); + } + if (dirname($file) != '.' && empty($atts['install-as'])) { + $dest_dir .= DIRECTORY_SEPARATOR . dirname($file); + } + if (empty($atts['install-as'])) { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file); + } else { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as']; + } + $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file; + + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + + list($dest_dir, $dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), + array($dest_dir, $dest_file, $orig_file)); + return array($save_destdir, $dest_dir, $dest_file, $orig_file); + } + + /** + * Get the name of the configuration variable that specifies the location of this file + * @return string|false + */ + function getLocationConfig() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['locationconfig']; + } + + /** + * Do any unusual setup here + * @param PEAR_Installer + * @param PEAR_PackageFile_v2 + * @param array file attributes + * @param string file name + */ + function setup(&$installer, $pkg, $atts, $file) + { + } + + function isExecutable() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['executable']; + } + + function isInstallable() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['installable']; + } + + function isExtension() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['phpextension']; + } +} +?> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Data.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Data extends PEAR_Installer_Role_Common {} +?> + php + extsrc + extbin + zendextsrc + zendextbin + 1 + data_dir + + + + + + + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Doc.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Doc extends PEAR_Installer_Role_Common {} +?> + php + extsrc + extbin + zendextsrc + zendextbin + 1 + doc_dir + + + + + + + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Php.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Php extends PEAR_Installer_Role_Common {} +?> + php + extsrc + extbin + zendextsrc + zendextbin + 1 + php_dir + 1 + + 1 + + + + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Script.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Script extends PEAR_Installer_Role_Common {} +?> + php + extsrc + extbin + zendextsrc + zendextbin + 1 + bin_dir + 1 + + + 1 + + + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Test.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Test extends PEAR_Installer_Role_Common {} +?> + php + extsrc + extbin + zendextsrc + zendextbin + 1 + test_dir + + + + + + + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: PackageFile.php 286670 2009-08-02 14:16:06Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * needed for PEAR_VALIDATE_* constants + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Validate.php'; +/** + * Error code if the package.xml tag does not contain a valid version + */ +define('PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION', 1); +/** + * Error code if the package.xml tag version is not supported (version 1.0 and 1.1 are the only supported versions, + * currently + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_PACKAGEVERSION', 2); +/** + * Abstraction for the package.xml package description file + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile +{ + /** + * @var PEAR_Config + */ + var $_config; + var $_debug; + /** + * Temp directory for uncompressing tgz files. + * @var string|false + */ + var $_tmpdir; + var $_logger = false; + /** + * @var boolean + */ + var $_rawReturn = false; + + /** + * + * @param PEAR_Config $config + * @param ? $debug + * @param string @tmpdir Optional temporary directory for uncompressing + * files + */ + function PEAR_PackageFile(&$config, $debug = false, $tmpdir = false) + { + $this->_config = $config; + $this->_debug = $debug; + $this->_tmpdir = $tmpdir; + } + + /** + * Turn off validation - return a parsed package.xml without checking it + * + * This is used by the package-validate command + */ + function rawReturn() + { + $this->_rawReturn = true; + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + /** + * Create a PEAR_PackageFile_Parser_v* of a given version. + * @param int $version + * @return PEAR_PackageFile_Parser_v1|PEAR_PackageFile_Parser_v1 + */ + function &parserFactory($version) + { + if (!in_array($version{0}, array('1', '2'))) { + $a = false; + return $a; + } + + include_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/Parser/v' . $version{0} . '.php'; + $version = $version{0}; + $class = "PEAR_PackageFile_Parser_v$version"; + $a = new $class; + return $a; + } + + /** + * For simpler unit-testing + * @return string + */ + function getClassPrefix() + { + return 'PEAR_PackageFile_v'; + } + + /** + * Create a PEAR_PackageFile_v* of a given version. + * @param int $version + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v1 + */ + function &factory($version) + { + if (!in_array($version{0}, array('1', '2'))) { + $a = false; + return $a; + } + + include_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v' . $version{0} . '.php'; + $version = $version{0}; + $class = $this->getClassPrefix() . $version; + $a = new $class; + return $a; + } + + /** + * Create a PEAR_PackageFile_v* from its toArray() method + * + * WARNING: no validation is performed, the array is assumed to be valid, + * always parse from xml if you want validation. + * @param array $arr + * @return PEAR_PackageFileManager_v1|PEAR_PackageFileManager_v2 + * @uses factory() to construct the returned object. + */ + function &fromArray($arr) + { + if (isset($arr['xsdversion'])) { + $obj = &$this->factory($arr['xsdversion']); + if ($this->_logger) { + $obj->setLogger($this->_logger); + } + + $obj->setConfig($this->_config); + $obj->fromArray($arr); + return $obj; + } + + if (isset($arr['package']['attribs']['version'])) { + $obj = &$this->factory($arr['package']['attribs']['version']); + } else { + $obj = &$this->factory('1.0'); + } + + if ($this->_logger) { + $obj->setLogger($this->_logger); + } + + $obj->setConfig($this->_config); + $obj->fromArray($arr); + return $obj; + } + + /** + * Create a PEAR_PackageFile_v* from an XML string. + * @access public + * @param string $data contents of package.xml file + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @param string $file full path to the package.xml file (and the files + * it references) + * @param string $archive optional name of the archive that the XML was + * extracted from, if any + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses parserFactory() to construct a parser to load the package. + */ + function &fromXmlString($data, $state, $file, $archive = false) + { + if (preg_match('/]+version="([0-9]+\.[0-9]+)"/', $data, $packageversion)) { + if (!in_array($packageversion[1], array('1.0', '2.0', '2.1'))) { + return PEAR::raiseError('package.xml version "' . $packageversion[1] . + '" is not supported, only 1.0, 2.0, and 2.1 are supported.'); + } + + $object = &$this->parserFactory($packageversion[1]); + if ($this->_logger) { + $object->setLogger($this->_logger); + } + + $object->setConfig($this->_config); + $pf = $object->parse($data, $file, $archive); + if (PEAR::isError($pf)) { + return $pf; + } + + if ($this->_rawReturn) { + return $pf; + } + + if (!$pf->validate($state)) {; + if ($this->_config->get('verbose') > 0 + && $this->_logger && $pf->getValidationWarnings(false) + ) { + foreach ($pf->getValidationWarnings(false) as $warning) { + $this->_logger->log(0, 'ERROR: ' . $warning['message']); + } + } + + $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', + 2, null, null, $pf->getValidationWarnings()); + return $a; + } + + if ($this->_logger && $pf->getValidationWarnings(false)) { + foreach ($pf->getValidationWarnings() as $warning) { + $this->_logger->log(0, 'WARNING: ' . $warning['message']); + } + } + + if (method_exists($pf, 'flattenFilelist')) { + $pf->flattenFilelist(); // for v2 + } + + return $pf; + } elseif (preg_match('/]+version="([^"]+)"/', $data, $packageversion)) { + $a = PEAR::raiseError('package.xml file "' . $file . + '" has unsupported package.xml version "' . $packageversion[1] . '"'); + return $a; + } else { + if (!class_exists('PEAR_ErrorStack')) { + require_once 'phar://go-pear.phar/' . 'PEAR/ErrorStack.php'; + } + + PEAR_ErrorStack::staticPush('PEAR_PackageFile', + PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION, + 'warning', array('xml' => $data), 'package.xml "' . $file . + '" has no package.xml version'); + $object = &$this->parserFactory('1.0'); + $object->setConfig($this->_config); + $pf = $object->parse($data, $file, $archive); + if (PEAR::isError($pf)) { + return $pf; + } + + if ($this->_rawReturn) { + return $pf; + } + + if (!$pf->validate($state)) { + $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', + 2, null, null, $pf->getValidationWarnings()); + return $a; + } + + if ($this->_logger && $pf->getValidationWarnings(false)) { + foreach ($pf->getValidationWarnings() as $warning) { + $this->_logger->log(0, 'WARNING: ' . $warning['message']); + } + } + + if (method_exists($pf, 'flattenFilelist')) { + $pf->flattenFilelist(); // for v2 + } + + return $pf; + } + } + + /** + * Register a temporary file or directory. When the destructor is + * executed, all registered temporary files and directories are + * removed. + * + * @param string $file name of file or directory + * @return void + */ + function addTempFile($file) + { + $GLOBALS['_PEAR_Common_tempfiles'][] = $file; + } + + /** + * Create a PEAR_PackageFile_v* from a compresed Tar or Tgz file. + * @access public + * @param string contents of package.xml file + * @param int package state (one of PEAR_VALIDATE_* constants) + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @using Archive_Tar to extract the files + * @using fromPackageFile() to load the package after the package.xml + * file is extracted. + */ + function &fromTgzFile($file, $state) + { + if (!class_exists('Archive_Tar')) { + require_once 'phar://go-pear.phar/' . 'Archive/Tar.php'; + } + + $tar = new Archive_Tar($file); + if ($this->_debug <= 1) { + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + } + + $content = $tar->listContent(); + if ($this->_debug <= 1) { + $tar->popErrorHandling(); + } + + if (!is_array($content)) { + if (is_string($file) && strlen($file < 255) && + (!file_exists($file) || !@is_file($file))) { + $ret = PEAR::raiseError("could not open file \"$file\""); + return $ret; + } + + $file = realpath($file); + $ret = PEAR::raiseError("Could not get contents of package \"$file\"". + '. Invalid tgz file.'); + return $ret; + } + + if (!count($content) && !@is_file($file)) { + $ret = PEAR::raiseError("could not open file \"$file\""); + return $ret; + } + + $xml = null; + $origfile = $file; + foreach ($content as $file) { + $name = $file['filename']; + if ($name == 'package2.xml') { // allow a .tgz to distribute both versions + $xml = $name; + break; + } + + if ($name == 'package.xml') { + $xml = $name; + break; + } elseif (preg_match('/package.xml$/', $name, $match)) { + $xml = $name; + break; + } + } + + if ($this->_tmpdir) { + $tmpdir = $this->_tmpdir; + } else { + $tmpdir = System::mkTemp(array('-t', $this->_config->get('temp_dir'), '-d', 'pear')); + if ($tmpdir === false) { + $ret = PEAR::raiseError("there was a problem with getting the configured temp directory"); + return $ret; + } + + PEAR_PackageFile::addTempFile($tmpdir); + } + + $this->_extractErrors(); + PEAR::staticPushErrorHandling(PEAR_ERROR_CALLBACK, array($this, '_extractErrors')); + if (!$xml || !$tar->extractList(array($xml), $tmpdir)) { + $extra = implode("\n", $this->_extractErrors()); + if ($extra) { + $extra = ' ' . $extra; + } + + PEAR::staticPopErrorHandling(); + $ret = PEAR::raiseError('could not extract the package.xml file from "' . + $origfile . '"' . $extra); + return $ret; + } + + PEAR::staticPopErrorHandling(); + $ret = &PEAR_PackageFile::fromPackageFile("$tmpdir/$xml", $state, $origfile); + return $ret; + } + + /** + * helper for extracting Archive_Tar errors + * @var array + * @access private + */ + var $_extractErrors = array(); + + /** + * helper callback for extracting Archive_Tar errors + * + * @param PEAR_Error|null $err + * @return array + * @access private + */ + function _extractErrors($err = null) + { + static $errors = array(); + if ($err === null) { + $e = $errors; + $errors = array(); + return $e; + } + $errors[] = $err->getMessage(); + } + + /** + * Create a PEAR_PackageFile_v* from a package.xml file. + * + * @access public + * @param string $descfile name of package xml file + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @param string|false $archive name of the archive this package.xml came + * from, if any + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses PEAR_PackageFile::fromXmlString to create the oject after the + * XML is loaded from the package.xml file. + */ + function &fromPackageFile($descfile, $state, $archive = false) + { + $fp = false; + if (is_string($descfile) && strlen($descfile) < 255 && + ( + !file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) + || (!$fp = @fopen($descfile, 'r')) + ) + ) { + $a = PEAR::raiseError("Unable to open $descfile"); + return $a; + } + + // read the whole thing so we only get one cdata callback + // for each block of cdata + fclose($fp); + $data = file_get_contents($descfile); + $ret = &PEAR_PackageFile::fromXmlString($data, $state, $descfile, $archive); + return $ret; + } + + + /** + * Create a PEAR_PackageFile_v* from a .tgz archive or package.xml file. + * + * This method is able to extract information about a package from a .tgz + * archive or from a XML package definition file. + * + * @access public + * @param string $info file name + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses fromPackageFile() if the file appears to be XML + * @uses fromTgzFile() to load all non-XML files + */ + function &fromAnyFile($info, $state) + { + if (is_dir($info)) { + $dir_name = realpath($info); + if (file_exists($dir_name . '/package.xml')) { + $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package.xml', $state); + } elseif (file_exists($dir_name . '/package2.xml')) { + $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package2.xml', $state); + } else { + $info = PEAR::raiseError("No package definition found in '$info' directory"); + } + + return $info; + } + + $fp = false; + if (is_string($info) && strlen($info) < 255 && + (file_exists($info) || ($fp = @fopen($info, 'r'))) + ) { + + if ($fp) { + fclose($fp); + } + + $tmp = substr($info, -4); + if ($tmp == '.xml') { + $info = &PEAR_PackageFile::fromPackageFile($info, $state); + } elseif ($tmp == '.tar' || $tmp == '.tgz') { + $info = &PEAR_PackageFile::fromTgzFile($info, $state); + } else { + $fp = fopen($info, 'r'); + $test = fread($fp, 5); + fclose($fp); + if ($test == ' + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v1.php 286494 2009-07-29 06:57:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * needed for PEAR_VALIDATE_* constants + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Validate.php'; +require_once 'phar://go-pear.phar/' . 'System.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v2.php'; +/** + * This class converts a PEAR_PackageFile_v1 object into any output format. + * + * Supported output formats include array, XML string, and a PEAR_PackageFile_v2 + * object, for converting package.xml 1.0 into package.xml 2.0 with no sweat. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Generator_v1 +{ + /** + * @var PEAR_PackageFile_v1 + */ + var $_packagefile; + function PEAR_PackageFile_Generator_v1(&$packagefile) + { + $this->_packagefile = &$packagefile; + } + + function getPackagerVersion() + { + return '1.9.0'; + } + + /** + * @param PEAR_Packager + * @param bool if true, a .tgz is written, otherwise a .tar is written + * @param string|null directory in which to save the .tgz + * @return string|PEAR_Error location of package or error object + */ + function toTgz(&$packager, $compress = true, $where = null) + { + require_once 'phar://go-pear.phar/' . 'Archive/Tar.php'; + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: "' . $where . '" could' . + ' not be created'); + } + if (file_exists($where . DIRECTORY_SEPARATOR . 'package.xml') && + !is_file($where . DIRECTORY_SEPARATOR . 'package.xml')) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: unable to save package.xml as' . + ' "' . $where . DIRECTORY_SEPARATOR . 'package.xml"'); + } + if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: invalid package file'); + } + $pkginfo = $this->_packagefile->getArray(); + $ext = $compress ? '.tgz' : '.tar'; + $pkgver = $pkginfo['package'] . '-' . $pkginfo['version']; + $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext; + if (file_exists(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext) && + !is_file(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: cannot create tgz file "' . + getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext . '"'); + } + if ($pkgfile = $this->_packagefile->getPackageFile()) { + $pkgdir = dirname(realpath($pkgfile)); + $pkgfile = basename($pkgfile); + } else { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: package file object must ' . + 'be created from a real file'); + } + // {{{ Create the package file list + $filelist = array(); + $i = 0; + + foreach ($this->_packagefile->getFilelist() as $fname => $atts) { + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return PEAR::raiseError("File does not exist: $fname"); + } else { + $filelist[$i++] = $file; + if (!isset($atts['md5sum'])) { + $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($file)); + } + $packager->log(2, "Adding file $fname"); + } + } + // }}} + $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true); + if ($packagexml) { + $tar =& new Archive_Tar($dest_package, $compress); + $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors + // ----- Creates with the package.xml file + $ok = $tar->createModify(array($packagexml), '', $where); + if (PEAR::isError($ok)) { + return $ok; + } elseif (!$ok) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed'); + } + // ----- Add the content of the package + if (!$tar->addModify($filelist, $pkgver, $pkgdir)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed'); + } + return $dest_package; + } + } + + /** + * @param string|null directory to place the package.xml in, or null for a temporary dir + * @param int one of the PEAR_VALIDATE_* constants + * @param string name of the generated file + * @param bool if true, then no analysis will be performed on role="php" files + * @return string|PEAR_Error path to the created file on success + */ + function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml', + $nofilechecking = false) + { + if (!$this->_packagefile->validate($state, $nofilechecking)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: invalid package.xml', + null, null, null, $this->_packagefile->getValidationWarnings()); + } + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: "' . $where . '" could' . + ' not be created'); + } + $newpkgfile = $where . DIRECTORY_SEPARATOR . $name; + $np = @fopen($newpkgfile, 'wb'); + if (!$np) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: unable to save ' . + "$name as $newpkgfile"); + } + fwrite($np, $this->toXml($state, true)); + fclose($np); + return $newpkgfile; + } + + /** + * fix both XML encoding to be UTF8, and replace standard XML entities < > " & ' + * + * @param string $string + * @return string + * @access private + */ + function _fixXmlEncoding($string) + { + if (version_compare(phpversion(), '5.0.0', 'lt')) { + $string = utf8_encode($string); + } + return strtr($string, array( + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + '\'' => ''' )); + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @return string XML data + */ + function toXml($state = PEAR_VALIDATE_NORMAL, $nofilevalidation = false) + { + $this->_packagefile->setDate(date('Y-m-d')); + if (!$this->_packagefile->validate($state, $nofilevalidation)) { + return false; + } + $pkginfo = $this->_packagefile->getArray(); + static $maint_map = array( + "handle" => "user", + "name" => "name", + "email" => "email", + "role" => "role", + ); + $ret = "\n"; + $ret .= "\n"; + $ret .= "\n" . +" $pkginfo[package]"; + if (isset($pkginfo['extends'])) { + $ret .= "\n$pkginfo[extends]"; + } + $ret .= + "\n ".$this->_fixXmlEncoding($pkginfo['summary'])."\n" . +" ".trim($this->_fixXmlEncoding($pkginfo['description']))."\n \n" . +" \n"; + foreach ($pkginfo['maintainers'] as $maint) { + $ret .= " \n"; + foreach ($maint_map as $idx => $elm) { + $ret .= " <$elm>"; + $ret .= $this->_fixXmlEncoding($maint[$idx]); + $ret .= "\n"; + } + $ret .= " \n"; + } + $ret .= " \n"; + $ret .= $this->_makeReleaseXml($pkginfo, false, $state); + if (isset($pkginfo['changelog']) && count($pkginfo['changelog']) > 0) { + $ret .= " \n"; + foreach ($pkginfo['changelog'] as $oldrelease) { + $ret .= $this->_makeReleaseXml($oldrelease, true); + } + $ret .= " \n"; + } + $ret .= "\n"; + return $ret; + } + + // }}} + // {{{ _makeReleaseXml() + + /** + * Generate part of an XML description with release information. + * + * @param array $pkginfo array with release information + * @param bool $changelog whether the result will be in a changelog element + * + * @return string XML data + * + * @access private + */ + function _makeReleaseXml($pkginfo, $changelog = false, $state = PEAR_VALIDATE_NORMAL) + { + // XXX QUOTE ENTITIES IN PCDATA, OR EMBED IN CDATA BLOCKS!! + $indent = $changelog ? " " : ""; + $ret = "$indent \n"; + if (!empty($pkginfo['version'])) { + $ret .= "$indent $pkginfo[version]\n"; + } + if (!empty($pkginfo['release_date'])) { + $ret .= "$indent $pkginfo[release_date]\n"; + } + if (!empty($pkginfo['release_license'])) { + $ret .= "$indent $pkginfo[release_license]\n"; + } + if (!empty($pkginfo['release_state'])) { + $ret .= "$indent $pkginfo[release_state]\n"; + } + if (!empty($pkginfo['release_notes'])) { + $ret .= "$indent ".trim($this->_fixXmlEncoding($pkginfo['release_notes'])) + ."\n$indent \n"; + } + if (!empty($pkginfo['release_warnings'])) { + $ret .= "$indent ".$this->_fixXmlEncoding($pkginfo['release_warnings'])."\n"; + } + if (isset($pkginfo['release_deps']) && sizeof($pkginfo['release_deps']) > 0) { + $ret .= "$indent \n"; + foreach ($pkginfo['release_deps'] as $dep) { + $ret .= "$indent _fixXmlEncoding($c['name']) . "\""; + if (isset($c['default'])) { + $ret .= " default=\"" . $this->_fixXmlEncoding($c['default']) . "\""; + } + $ret .= " prompt=\"" . $this->_fixXmlEncoding($c['prompt']) . "\""; + $ret .= "/>\n"; + } + $ret .= "$indent \n"; + } + if (isset($pkginfo['provides'])) { + foreach ($pkginfo['provides'] as $key => $what) { + $ret .= "$indent recursiveXmlFilelist($pkginfo['filelist']); + } else { + foreach ($pkginfo['filelist'] as $file => $fa) { + if (!isset($fa['role'])) { + $fa['role'] = ''; + } + $ret .= "$indent _fixXmlEncoding($fa['baseinstalldir']) . '"'; + } + if (isset($fa['md5sum'])) { + $ret .= " md5sum=\"$fa[md5sum]\""; + } + if (isset($fa['platform'])) { + $ret .= " platform=\"$fa[platform]\""; + } + if (!empty($fa['install-as'])) { + $ret .= ' install-as="' . + $this->_fixXmlEncoding($fa['install-as']) . '"'; + } + $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"'; + if (empty($fa['replacements'])) { + $ret .= "/>\n"; + } else { + $ret .= ">\n"; + foreach ($fa['replacements'] as $r) { + $ret .= "$indent $v) { + $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"'; + } + $ret .= "/>\n"; + } + $ret .= "$indent \n"; + } + } + } + $ret .= "$indent
    \n"; + } + $ret .= "$indent \n"; + return $ret; + } + + /** + * @param array + * @access protected + */ + function recursiveXmlFilelist($list) + { + $this->_dirs = array(); + foreach ($list as $file => $attributes) { + $this->_addDir($this->_dirs, explode('/', dirname($file)), $file, $attributes); + } + return $this->_formatDir($this->_dirs); + } + + /** + * @param array + * @param array + * @param string|null + * @param array|null + * @access private + */ + function _addDir(&$dirs, $dir, $file = null, $attributes = null) + { + if ($dir == array() || $dir == array('.')) { + $dirs['files'][basename($file)] = $attributes; + return; + } + $curdir = array_shift($dir); + if (!isset($dirs['dirs'][$curdir])) { + $dirs['dirs'][$curdir] = array(); + } + $this->_addDir($dirs['dirs'][$curdir], $dir, $file, $attributes); + } + + /** + * @param array + * @param string + * @param string + * @access private + */ + function _formatDir($dirs, $indent = '', $curdir = '') + { + $ret = ''; + if (!count($dirs)) { + return ''; + } + if (isset($dirs['dirs'])) { + uksort($dirs['dirs'], 'strnatcasecmp'); + foreach ($dirs['dirs'] as $dir => $contents) { + $usedir = "$curdir/$dir"; + $ret .= "$indent \n"; + $ret .= $this->_formatDir($contents, "$indent ", $usedir); + $ret .= "$indent \n"; + } + } + if (isset($dirs['files'])) { + uksort($dirs['files'], 'strnatcasecmp'); + foreach ($dirs['files'] as $file => $attribs) { + $ret .= $this->_formatFile($file, $attribs, $indent); + } + } + return $ret; + } + + /** + * @param string + * @param array + * @param string + * @access private + */ + function _formatFile($file, $attributes, $indent) + { + $ret = "$indent _fixXmlEncoding($attributes['baseinstalldir']) . '"'; + } + if (isset($attributes['md5sum'])) { + $ret .= " md5sum=\"$attributes[md5sum]\""; + } + if (isset($attributes['platform'])) { + $ret .= " platform=\"$attributes[platform]\""; + } + if (!empty($attributes['install-as'])) { + $ret .= ' install-as="' . + $this->_fixXmlEncoding($attributes['install-as']) . '"'; + } + $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"'; + if (empty($attributes['replacements'])) { + $ret .= "/>\n"; + } else { + $ret .= ">\n"; + foreach ($attributes['replacements'] as $r) { + $ret .= "$indent $v) { + $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"'; + } + $ret .= "/>\n"; + } + $ret .= "$indent \n"; + } + return $ret; + } + + // {{{ _unIndent() + + /** + * Unindent given string (?) + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } + } + return $data; + } + + /** + * @return array + */ + function dependenciesToV2() + { + $arr = array(); + $this->_convertDependencies2_0($arr); + return $arr['dependencies']; + } + + /** + * Convert a package.xml version 1.0 into version 2.0 + * + * Note that this does a basic conversion, to allow more advanced + * features like bundles and multiple releases + * @param string the classname to instantiate and return. This must be + * PEAR_PackageFile_v2 or a descendant + * @param boolean if true, only valid, deterministic package.xml 1.0 as defined by the + * strictest parameters will be converted + * @return PEAR_PackageFile_v2|PEAR_Error + */ + function &toV2($class = 'PEAR_PackageFile_v2', $strict = false) + { + if ($strict) { + if (!$this->_packagefile->validate()) { + $a = PEAR::raiseError('invalid package.xml version 1.0 cannot be converted' . + ' to version 2.0', null, null, null, + $this->_packagefile->getValidationWarnings(true)); + return $a; + } + } + + $arr = array( + 'attribs' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => "http://pear.php.net/dtd/tasks-1.0\n" . +"http://pear.php.net/dtd/tasks-1.0.xsd\n" . +"http://pear.php.net/dtd/package-2.0\n" . +'http://pear.php.net/dtd/package-2.0.xsd', + ), + 'name' => $this->_packagefile->getPackage(), + 'channel' => 'pear.php.net', + ); + $arr['summary'] = $this->_packagefile->getSummary(); + $arr['description'] = $this->_packagefile->getDescription(); + $maintainers = $this->_packagefile->getMaintainers(); + foreach ($maintainers as $maintainer) { + if ($maintainer['role'] != 'lead') { + continue; + } + $new = array( + 'name' => $maintainer['name'], + 'user' => $maintainer['handle'], + 'email' => $maintainer['email'], + 'active' => 'yes', + ); + $arr['lead'][] = $new; + } + + if (!isset($arr['lead'])) { // some people... you know? + $arr['lead'] = array( + 'name' => 'unknown', + 'user' => 'unknown', + 'email' => 'noleadmaintainer@example.com', + 'active' => 'no', + ); + } + + if (count($arr['lead']) == 1) { + $arr['lead'] = $arr['lead'][0]; + } + + foreach ($maintainers as $maintainer) { + if ($maintainer['role'] == 'lead') { + continue; + } + $new = array( + 'name' => $maintainer['name'], + 'user' => $maintainer['handle'], + 'email' => $maintainer['email'], + 'active' => 'yes', + ); + $arr[$maintainer['role']][] = $new; + } + + if (isset($arr['developer']) && count($arr['developer']) == 1) { + $arr['developer'] = $arr['developer'][0]; + } + + if (isset($arr['contributor']) && count($arr['contributor']) == 1) { + $arr['contributor'] = $arr['contributor'][0]; + } + + if (isset($arr['helper']) && count($arr['helper']) == 1) { + $arr['helper'] = $arr['helper'][0]; + } + + $arr['date'] = $this->_packagefile->getDate(); + $arr['version'] = + array( + 'release' => $this->_packagefile->getVersion(), + 'api' => $this->_packagefile->getVersion(), + ); + $arr['stability'] = + array( + 'release' => $this->_packagefile->getState(), + 'api' => $this->_packagefile->getState(), + ); + $licensemap = + array( + 'php' => 'http://www.php.net/license', + 'php license' => 'http://www.php.net/license', + 'lgpl' => 'http://www.gnu.org/copyleft/lesser.html', + 'bsd' => 'http://www.opensource.org/licenses/bsd-license.php', + 'bsd style' => 'http://www.opensource.org/licenses/bsd-license.php', + 'bsd-style' => 'http://www.opensource.org/licenses/bsd-license.php', + 'mit' => 'http://www.opensource.org/licenses/mit-license.php', + 'gpl' => 'http://www.gnu.org/copyleft/gpl.html', + 'apache' => 'http://www.opensource.org/licenses/apache2.0.php' + ); + + if (isset($licensemap[strtolower($this->_packagefile->getLicense())])) { + $arr['license'] = array( + 'attribs' => array('uri' => + $licensemap[strtolower($this->_packagefile->getLicense())]), + '_content' => $this->_packagefile->getLicense() + ); + } else { + // don't use bogus uri + $arr['license'] = $this->_packagefile->getLicense(); + } + + $arr['notes'] = $this->_packagefile->getNotes(); + $temp = array(); + $arr['contents'] = $this->_convertFilelist2_0($temp); + $this->_convertDependencies2_0($arr); + $release = ($this->_packagefile->getConfigureOptions() || $this->_isExtension) ? + 'extsrcrelease' : 'phprelease'; + if ($release == 'extsrcrelease') { + $arr['channel'] = 'pecl.php.net'; + $arr['providesextension'] = $arr['name']; // assumption + } + + $arr[$release] = array(); + if ($this->_packagefile->getConfigureOptions()) { + $arr[$release]['configureoption'] = $this->_packagefile->getConfigureOptions(); + foreach ($arr[$release]['configureoption'] as $i => $opt) { + $arr[$release]['configureoption'][$i] = array('attribs' => $opt); + } + if (count($arr[$release]['configureoption']) == 1) { + $arr[$release]['configureoption'] = $arr[$release]['configureoption'][0]; + } + } + + $this->_convertRelease2_0($arr[$release], $temp); + if ($release == 'extsrcrelease' && count($arr[$release]) > 1) { + // multiple extsrcrelease tags added in PEAR 1.4.1 + $arr['dependencies']['required']['pearinstaller']['min'] = '1.4.1'; + } + + if ($cl = $this->_packagefile->getChangelog()) { + foreach ($cl as $release) { + $rel = array(); + $rel['version'] = + array( + 'release' => $release['version'], + 'api' => $release['version'], + ); + if (!isset($release['release_state'])) { + $release['release_state'] = 'stable'; + } + + $rel['stability'] = + array( + 'release' => $release['release_state'], + 'api' => $release['release_state'], + ); + if (isset($release['release_date'])) { + $rel['date'] = $release['release_date']; + } else { + $rel['date'] = date('Y-m-d'); + } + + if (isset($release['release_license'])) { + if (isset($licensemap[strtolower($release['release_license'])])) { + $uri = $licensemap[strtolower($release['release_license'])]; + } else { + $uri = 'http://www.example.com'; + } + $rel['license'] = array( + 'attribs' => array('uri' => $uri), + '_content' => $release['release_license'] + ); + } else { + $rel['license'] = $arr['license']; + } + + if (!isset($release['release_notes'])) { + $release['release_notes'] = 'no release notes'; + } + + $rel['notes'] = $release['release_notes']; + $arr['changelog']['release'][] = $rel; + } + } + + $ret = new $class; + $ret->setConfig($this->_packagefile->_config); + if (isset($this->_packagefile->_logger) && is_object($this->_packagefile->_logger)) { + $ret->setLogger($this->_packagefile->_logger); + } + + $ret->fromArray($arr); + return $ret; + } + + /** + * @param array + * @param bool + * @access private + */ + function _convertDependencies2_0(&$release, $internal = false) + { + $peardep = array('pearinstaller' => + array('min' => '1.4.0b1')); // this is a lot safer + $required = $optional = array(); + $release['dependencies'] = array('required' => array()); + if ($this->_packagefile->hasDeps()) { + foreach ($this->_packagefile->getDeps() as $dep) { + if (!isset($dep['optional']) || $dep['optional'] == 'no') { + $required[] = $dep; + } else { + $optional[] = $dep; + } + } + foreach (array('required', 'optional') as $arr) { + $deps = array(); + foreach ($$arr as $dep) { + // organize deps by dependency type and name + if (!isset($deps[$dep['type']])) { + $deps[$dep['type']] = array(); + } + if (isset($dep['name'])) { + $deps[$dep['type']][$dep['name']][] = $dep; + } else { + $deps[$dep['type']][] = $dep; + } + } + do { + if (isset($deps['php'])) { + $php = array(); + if (count($deps['php']) > 1) { + $php = $this->_processPhpDeps($deps['php']); + } else { + if (!isset($deps['php'][0])) { + list($key, $blah) = each ($deps['php']); // stupid buggy versions + $deps['php'] = array($blah[0]); + } + $php = $this->_processDep($deps['php'][0]); + if (!$php) { + break; // poor mans throw + } + } + $release['dependencies'][$arr]['php'] = $php; + } + } while (false); + do { + if (isset($deps['pkg'])) { + $pkg = array(); + $pkg = $this->_processMultipleDepsName($deps['pkg']); + if (!$pkg) { + break; // poor mans throw + } + $release['dependencies'][$arr]['package'] = $pkg; + } + } while (false); + do { + if (isset($deps['ext'])) { + $pkg = array(); + $pkg = $this->_processMultipleDepsName($deps['ext']); + $release['dependencies'][$arr]['extension'] = $pkg; + } + } while (false); + // skip sapi - it's not supported so nobody will have used it + // skip os - it's not supported in 1.0 + } + } + if (isset($release['dependencies']['required'])) { + $release['dependencies']['required'] = + array_merge($peardep, $release['dependencies']['required']); + } else { + $release['dependencies']['required'] = $peardep; + } + if (!isset($release['dependencies']['required']['php'])) { + $release['dependencies']['required']['php'] = + array('min' => '4.0.0'); + } + $order = array(); + $bewm = $release['dependencies']['required']; + $order['php'] = $bewm['php']; + $order['pearinstaller'] = $bewm['pearinstaller']; + isset($bewm['package']) ? $order['package'] = $bewm['package'] :0; + isset($bewm['extension']) ? $order['extension'] = $bewm['extension'] :0; + $release['dependencies']['required'] = $order; + } + + /** + * @param array + * @access private + */ + function _convertFilelist2_0(&$package) + { + $ret = array('dir' => + array( + 'attribs' => array('name' => '/'), + 'file' => array() + ) + ); + $package['platform'] = + $package['install-as'] = array(); + $this->_isExtension = false; + foreach ($this->_packagefile->getFilelist() as $name => $file) { + $file['name'] = $name; + if (isset($file['role']) && $file['role'] == 'src') { + $this->_isExtension = true; + } + if (isset($file['replacements'])) { + $repl = $file['replacements']; + unset($file['replacements']); + } else { + unset($repl); + } + if (isset($file['install-as'])) { + $package['install-as'][$name] = $file['install-as']; + unset($file['install-as']); + } + if (isset($file['platform'])) { + $package['platform'][$name] = $file['platform']; + unset($file['platform']); + } + $file = array('attribs' => $file); + if (isset($repl)) { + foreach ($repl as $replace ) { + $file['tasks:replace'][] = array('attribs' => $replace); + } + if (count($repl) == 1) { + $file['tasks:replace'] = $file['tasks:replace'][0]; + } + } + $ret['dir']['file'][] = $file; + } + return $ret; + } + + /** + * Post-process special files with install-as/platform attributes and + * make the release tag. + * + * This complex method follows this work-flow to create the release tags: + * + *
    +     * - if any install-as/platform exist, create a generic release and fill it with
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     * - create a release for each platform encountered and fill with
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     * 
    + * + * It does this by accessing the $package parameter, which contains an array with + * indices: + * + * - platform: mapping of file => OS the file should be installed on + * - install-as: mapping of file => installed name + * - osmap: mapping of OS => list of files that should be installed + * on that OS + * - notosmap: mapping of OS => list of files that should not be + * installed on that OS + * + * @param array + * @param array + * @access private + */ + function _convertRelease2_0(&$release, $package) + { + //- if any install-as/platform exist, create a generic release and fill it with + if (count($package['platform']) || count($package['install-as'])) { + $generic = array(); + $genericIgnore = array(); + foreach ($package['install-as'] as $file => $as) { + //o tags for + if (!isset($package['platform'][$file])) { + $generic[] = $file; + continue; + } + //o tags for + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} == '!') { + $generic[] = $file; + continue; + } + //o tags for + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} != '!') { + $genericIgnore[] = $file; + continue; + } + } + foreach ($package['platform'] as $file => $platform) { + if (isset($package['install-as'][$file])) { + continue; + } + if ($platform{0} != '!') { + //o tags for + $genericIgnore[] = $file; + } + } + if (count($package['platform'])) { + $oses = $notplatform = $platform = array(); + foreach ($package['platform'] as $file => $os) { + // get a list of oses + if ($os{0} == '!') { + if (isset($oses[substr($os, 1)])) { + continue; + } + $oses[substr($os, 1)] = count($oses); + } else { + if (isset($oses[$os])) { + continue; + } + $oses[$os] = count($oses); + } + } + //- create a release for each platform encountered and fill with + foreach ($oses as $os => $releaseNum) { + $release[$releaseNum]['installconditions']['os']['name'] = $os; + $release[$releaseNum]['filelist'] = array('install' => array(), + 'ignore' => array()); + foreach ($package['install-as'] as $file => $as) { + //o tags for + if (!isset($package['platform'][$file])) { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file] == $os) { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file] != "!$os" && + $package['platform'][$file]{0} == '!') { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file] == "!$os") { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} != '!' && + $package['platform'][$file] != $os) { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + } + foreach ($package['platform'] as $file => $platform) { + if (isset($package['install-as'][$file])) { + continue; + } + //o tags for + if ($platform == "!$os") { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + //o tags for + if ($platform{0} != '!' && $platform != $os) { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + } + } + if (!count($release[$releaseNum]['filelist']['install'])) { + unset($release[$releaseNum]['filelist']['install']); + } + if (!count($release[$releaseNum]['filelist']['ignore'])) { + unset($release[$releaseNum]['filelist']['ignore']); + } + } + if (count($generic) || count($genericIgnore)) { + $release[count($oses)] = array(); + if (count($generic)) { + foreach ($generic as $file) { + if (isset($package['install-as'][$file])) { + $installas = $package['install-as'][$file]; + } else { + $installas = $file; + } + $release[count($oses)]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $installas, + ) + ); + } + } + if (count($genericIgnore)) { + foreach ($genericIgnore as $file) { + $release[count($oses)]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ) + ); + } + } + } + // cleanup + foreach ($release as $i => $rel) { + if (isset($rel['filelist']['install']) && + count($rel['filelist']['install']) == 1) { + $release[$i]['filelist']['install'] = + $release[$i]['filelist']['install'][0]; + } + if (isset($rel['filelist']['ignore']) && + count($rel['filelist']['ignore']) == 1) { + $release[$i]['filelist']['ignore'] = + $release[$i]['filelist']['ignore'][0]; + } + } + if (count($release) == 1) { + $release = $release[0]; + } + } else { + // no platform atts, but some install-as atts + foreach ($package['install-as'] as $file => $value) { + $release['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $value + ) + ); + } + if (count($release['filelist']['install']) == 1) { + $release['filelist']['install'] = $release['filelist']['install'][0]; + } + } + } + } + + /** + * @param array + * @return array + * @access private + */ + function _processDep($dep) + { + if ($dep['type'] == 'php') { + if ($dep['rel'] == 'has') { + // come on - everyone has php! + return false; + } + } + $php = array(); + if ($dep['type'] != 'php') { + $php['name'] = $dep['name']; + if ($dep['type'] == 'pkg') { + $php['channel'] = 'pear.php.net'; + } + } + switch ($dep['rel']) { + case 'gt' : + $php['min'] = $dep['version']; + $php['exclude'] = $dep['version']; + break; + case 'ge' : + if (!isset($dep['version'])) { + if ($dep['type'] == 'php') { + if (isset($dep['name'])) { + $dep['version'] = $dep['name']; + } + } + } + $php['min'] = $dep['version']; + break; + case 'lt' : + $php['max'] = $dep['version']; + $php['exclude'] = $dep['version']; + break; + case 'le' : + $php['max'] = $dep['version']; + break; + case 'eq' : + $php['min'] = $dep['version']; + $php['max'] = $dep['version']; + break; + case 'ne' : + $php['exclude'] = $dep['version']; + break; + case 'not' : + $php['conflicts'] = 'yes'; + break; + } + return $php; + } + + /** + * @param array + * @return array + */ + function _processPhpDeps($deps) + { + $test = array(); + foreach ($deps as $dep) { + $test[] = $this->_processDep($dep); + } + $min = array(); + $max = array(); + foreach ($test as $dep) { + if (!$dep) { + continue; + } + if (isset($dep['min'])) { + $min[$dep['min']] = count($min); + } + if (isset($dep['max'])) { + $max[$dep['max']] = count($max); + } + } + if (count($min) > 0) { + uksort($min, 'version_compare'); + } + if (count($max) > 0) { + uksort($max, 'version_compare'); + } + if (count($min)) { + // get the highest minimum + $min = array_pop($a = array_flip($min)); + } else { + $min = false; + } + if (count($max)) { + // get the lowest maximum + $max = array_shift($a = array_flip($max)); + } else { + $max = false; + } + if ($min) { + $php['min'] = $min; + } + if ($max) { + $php['max'] = $max; + } + $exclude = array(); + foreach ($test as $dep) { + if (!isset($dep['exclude'])) { + continue; + } + $exclude[] = $dep['exclude']; + } + if (count($exclude)) { + $php['exclude'] = $exclude; + } + return $php; + } + + /** + * process multiple dependencies that have a name, like package deps + * @param array + * @return array + * @access private + */ + function _processMultipleDepsName($deps) + { + $ret = $tests = array(); + foreach ($deps as $name => $dep) { + foreach ($dep as $d) { + $tests[$name][] = $this->_processDep($d); + } + } + + foreach ($tests as $name => $test) { + $max = $min = $php = array(); + $php['name'] = $name; + foreach ($test as $dep) { + if (!$dep) { + continue; + } + if (isset($dep['channel'])) { + $php['channel'] = 'pear.php.net'; + } + if (isset($dep['conflicts']) && $dep['conflicts'] == 'yes') { + $php['conflicts'] = 'yes'; + } + if (isset($dep['min'])) { + $min[$dep['min']] = count($min); + } + if (isset($dep['max'])) { + $max[$dep['max']] = count($max); + } + } + if (count($min) > 0) { + uksort($min, 'version_compare'); + } + if (count($max) > 0) { + uksort($max, 'version_compare'); + } + if (count($min)) { + // get the highest minimum + $min = array_pop($a = array_flip($min)); + } else { + $min = false; + } + if (count($max)) { + // get the lowest maximum + $max = array_shift($a = array_flip($max)); + } else { + $max = false; + } + if ($min) { + $php['min'] = $min; + } + if ($max) { + $php['max'] = $max; + } + $exclude = array(); + foreach ($test as $dep) { + if (!isset($dep['exclude'])) { + continue; + } + $exclude[] = $dep['exclude']; + } + if (count($exclude)) { + $php['exclude'] = $exclude; + } + $ret[] = $php; + } + return $ret; + } +} +?> + * @author Stephan Schmidt (original XML_Serializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v2.php 278907 2009-04-17 21:10:04Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * file/dir manipulation routines + */ +require_once 'phar://go-pear.phar/' . 'System.php'; +require_once 'phar://go-pear.phar/' . 'XML/Util.php'; + +/** + * This class converts a PEAR_PackageFile_v2 object into any output format. + * + * Supported output formats include array, XML string (using S. Schmidt's + * XML_Serializer, slightly customized) + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Stephan Schmidt (original XML_Serializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Generator_v2 +{ + /** + * default options for the serialization + * @access private + * @var array $_defaultOptions + */ + var $_defaultOptions = array( + 'indent' => ' ', // string used for indentation + 'linebreak' => "\n", // string used for newlines + 'typeHints' => false, // automatically add type hin attributes + 'addDecl' => true, // add an XML declaration + 'defaultTagName' => 'XML_Serializer_Tag', // tag used for indexed arrays or invalid names + 'classAsTagName' => false, // use classname for objects in indexed arrays + 'keyAttribute' => '_originalKey', // attribute where original key is stored + 'typeAttribute' => '_type', // attribute for type (only if typeHints => true) + 'classAttribute' => '_class', // attribute for class of objects (only if typeHints => true) + 'scalarAsAttributes' => false, // scalar values (strings, ints,..) will be serialized as attribute + 'prependAttributes' => '', // prepend string for attributes + 'indentAttributes' => false, // indent the attributes, if set to '_auto', it will indent attributes so they all start at the same column + 'mode' => 'simplexml', // use 'simplexml' to use parent name as tagname if transforming an indexed array + 'addDoctype' => false, // add a doctype declaration + 'doctype' => null, // supply a string or an array with id and uri ({@see XML_Util::getDoctypeDeclaration()} + 'rootName' => 'package', // name of the root tag + 'rootAttributes' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0 +http://pear.php.net/dtd/tasks-1.0.xsd +http://pear.php.net/dtd/package-2.0 +http://pear.php.net/dtd/package-2.0.xsd', + ), // attributes of the root tag + 'attributesArray' => 'attribs', // all values in this key will be treated as attributes + 'contentName' => '_content', // this value will be used directly as content, instead of creating a new tag, may only be used in conjuction with attributesArray + 'beautifyFilelist' => false, + 'encoding' => 'UTF-8', + ); + + /** + * options for the serialization + * @access private + * @var array $options + */ + var $options = array(); + + /** + * current tag depth + * @var integer $_tagDepth + */ + var $_tagDepth = 0; + + /** + * serilialized representation of the data + * @var string $_serializedData + */ + var $_serializedData = null; + /** + * @var PEAR_PackageFile_v2 + */ + var $_packagefile; + /** + * @param PEAR_PackageFile_v2 + */ + function PEAR_PackageFile_Generator_v2(&$packagefile) + { + $this->_packagefile = &$packagefile; + if (isset($this->_packagefile->encoding)) { + $this->_defaultOptions['encoding'] = $this->_packagefile->encoding; + } + } + + /** + * @return string + */ + function getPackagerVersion() + { + return '1.9.0'; + } + + /** + * @param PEAR_Packager + * @param bool generate a .tgz or a .tar + * @param string|null temporary directory to package in + */ + function toTgz(&$packager, $compress = true, $where = null) + { + $a = null; + return $this->toTgz2($packager, $a, $compress, $where); + } + + /** + * Package up both a package.xml and package2.xml for the same release + * @param PEAR_Packager + * @param PEAR_PackageFile_v1 + * @param bool generate a .tgz or a .tar + * @param string|null temporary directory to package in + */ + function toTgz2(&$packager, &$pf1, $compress = true, $where = null) + { + require_once 'phar://go-pear.phar/' . 'Archive/Tar.php'; + if (!$this->_packagefile->isEquivalent($pf1)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' . + basename($pf1->getPackageFile()) . + '" is not equivalent to "' . basename($this->_packagefile->getPackageFile()) + . '"'); + } + + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' . $where . '" could' . + ' not be created'); + } + + $file = $where . DIRECTORY_SEPARATOR . 'package.xml'; + if (file_exists($file) && !is_file($file)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: unable to save package.xml as' . + ' "' . $file .'"'); + } + + if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: invalid package.xml'); + } + + $ext = $compress ? '.tgz' : '.tar'; + $pkgver = $this->_packagefile->getPackage() . '-' . $this->_packagefile->getVersion(); + $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext; + if (file_exists($dest_package) && !is_file($dest_package)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: cannot create tgz file "' . + $dest_package . '"'); + } + + $pkgfile = $this->_packagefile->getPackageFile(); + if (!$pkgfile) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: package file object must ' . + 'be created from a real file'); + } + + $pkgdir = dirname(realpath($pkgfile)); + $pkgfile = basename($pkgfile); + + // {{{ Create the package file list + $filelist = array(); + $i = 0; + $this->_packagefile->flattenFilelist(); + $contents = $this->_packagefile->getContents(); + if (isset($contents['bundledpackage'])) { // bundles of packages + $contents = $contents['bundledpackage']; + if (!isset($contents[0])) { + $contents = array($contents); + } + + $packageDir = $where; + foreach ($contents as $i => $package) { + $fname = $package; + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return $packager->raiseError("File does not exist: $fname"); + } + + $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname; + System::mkdir(array('-p', dirname($tfile))); + copy($file, $tfile); + $filelist[$i++] = $tfile; + $packager->log(2, "Adding package $fname"); + } + } else { // normal packages + $contents = $contents['dir']['file']; + if (!isset($contents[0])) { + $contents = array($contents); + } + + $packageDir = $where; + foreach ($contents as $i => $file) { + $fname = $file['attribs']['name']; + $atts = $file['attribs']; + $orig = $file; + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return $packager->raiseError("File does not exist: $fname"); + } + + $origperms = fileperms($file); + $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname; + unset($orig['attribs']); + if (count($orig)) { // file with tasks + // run any package-time tasks + $contents = file_get_contents($file); + foreach ($orig as $tag => $raw) { + $tag = str_replace( + array($this->_packagefile->getTasksNs() . ':', '-'), + array('', '_'), $tag); + $task = "PEAR_Task_$tag"; + $task = &new $task($this->_packagefile->_config, + $this->_packagefile->_logger, + PEAR_TASK_PACKAGE); + $task->init($raw, $atts, null); + $res = $task->startSession($this->_packagefile, $contents, $tfile); + if (!$res) { + continue; // skip this task + } + + if (PEAR::isError($res)) { + return $res; + } + + $contents = $res; // save changes + System::mkdir(array('-p', dirname($tfile))); + $wp = fopen($tfile, "wb"); + fwrite($wp, $contents); + fclose($wp); + } + } + + if (!file_exists($tfile)) { + System::mkdir(array('-p', dirname($tfile))); + copy($file, $tfile); + } + + chmod($tfile, $origperms); + $filelist[$i++] = $tfile; + $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($tfile), $i - 1); + $packager->log(2, "Adding file $fname"); + } + } + // }}} + + $name = $pf1 !== null ? 'package2.xml' : 'package.xml'; + $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, $name); + if ($packagexml) { + $tar =& new Archive_Tar($dest_package, $compress); + $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors + // ----- Creates with the package.xml file + $ok = $tar->createModify(array($packagexml), '', $where); + if (PEAR::isError($ok)) { + return $packager->raiseError($ok); + } elseif (!$ok) { + return $packager->raiseError('PEAR_Packagefile_v2::toTgz(): adding ' . $name . + ' failed'); + } + + // ----- Add the content of the package + if (!$tar->addModify($filelist, $pkgver, $where)) { + return $packager->raiseError( + 'PEAR_Packagefile_v2::toTgz(): tarball creation failed'); + } + + // add the package.xml version 1.0 + if ($pf1 !== null) { + $pfgen = &$pf1->getDefaultGenerator(); + $packagexml1 = $pfgen->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true); + if (!$tar->addModify(array($packagexml1), '', $where)) { + return $packager->raiseError( + 'PEAR_Packagefile_v2::toTgz(): adding package.xml failed'); + } + } + + return $dest_package; + } + } + + function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml') + { + if (!$this->_packagefile->validate($state)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: invalid package.xml', + null, null, null, $this->_packagefile->getValidationWarnings()); + } + + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: "' . $where . '" could' . + ' not be created'); + } + + $newpkgfile = $where . DIRECTORY_SEPARATOR . $name; + $np = @fopen($newpkgfile, 'wb'); + if (!$np) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: unable to save ' . + "$name as $newpkgfile"); + } + fwrite($np, $this->toXml($state)); + fclose($np); + return $newpkgfile; + } + + function &toV2() + { + return $this->_packagefile; + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @return string XML data + */ + function toXml($state = PEAR_VALIDATE_NORMAL, $options = array()) + { + $this->_packagefile->setDate(date('Y-m-d')); + $this->_packagefile->setTime(date('H:i:s')); + if (!$this->_packagefile->validate($state)) { + return false; + } + + if (is_array($options)) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = $this->_defaultOptions; + } + + $arr = $this->_packagefile->getArray(); + if (isset($arr['filelist'])) { + unset($arr['filelist']); + } + + if (isset($arr['_lastversion'])) { + unset($arr['_lastversion']); + } + + // Fix the notes a little bit + if (isset($arr['notes'])) { + // This trims out the indenting, needs fixing + $arr['notes'] = "\n" . trim($arr['notes']) . "\n"; + } + + if (isset($arr['changelog']) && !empty($arr['changelog'])) { + // Fix for inconsistency how the array is filled depending on the changelog release amount + if (!isset($arr['changelog']['release'][0])) { + $release = $arr['changelog']['release']; + unset($arr['changelog']['release']); + + $arr['changelog']['release'] = array(); + $arr['changelog']['release'][0] = $release; + } + + foreach (array_keys($arr['changelog']['release']) as $key) { + $c =& $arr['changelog']['release'][$key]; + if (isset($c['notes'])) { + // This trims out the indenting, needs fixing + $c['notes'] = "\n" . trim($c['notes']) . "\n"; + } + } + } + + if ($state ^ PEAR_VALIDATE_PACKAGING && !isset($arr['bundle'])) { + $use = $this->_recursiveXmlFilelist($arr['contents']['dir']['file']); + unset($arr['contents']['dir']['file']); + if (isset($use['dir'])) { + $arr['contents']['dir']['dir'] = $use['dir']; + } + if (isset($use['file'])) { + $arr['contents']['dir']['file'] = $use['file']; + } + $this->options['beautifyFilelist'] = true; + } + + $arr['attribs']['packagerversion'] = '1.9.0'; + if ($this->serialize($arr, $options)) { + return $this->_serializedData . "\n"; + } + + return false; + } + + + function _recursiveXmlFilelist($list) + { + $dirs = array(); + if (isset($list['attribs'])) { + $file = $list['attribs']['name']; + unset($list['attribs']['name']); + $attributes = $list['attribs']; + $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes); + } else { + foreach ($list as $a) { + $file = $a['attribs']['name']; + $attributes = $a['attribs']; + unset($a['attribs']); + $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes, $a); + } + } + $this->_formatDir($dirs); + $this->_deFormat($dirs); + return $dirs; + } + + function _addDir(&$dirs, $dir, $file = null, $attributes = null, $tasks = null) + { + if (!$tasks) { + $tasks = array(); + } + if ($dir == array() || $dir == array('.')) { + $dirs['file'][basename($file)] = $tasks; + $attributes['name'] = basename($file); + $dirs['file'][basename($file)]['attribs'] = $attributes; + return; + } + $curdir = array_shift($dir); + if (!isset($dirs['dir'][$curdir])) { + $dirs['dir'][$curdir] = array(); + } + $this->_addDir($dirs['dir'][$curdir], $dir, $file, $attributes, $tasks); + } + + function _formatDir(&$dirs) + { + if (!count($dirs)) { + return array(); + } + $newdirs = array(); + if (isset($dirs['dir'])) { + $newdirs['dir'] = $dirs['dir']; + } + if (isset($dirs['file'])) { + $newdirs['file'] = $dirs['file']; + } + $dirs = $newdirs; + if (isset($dirs['dir'])) { + uksort($dirs['dir'], 'strnatcasecmp'); + foreach ($dirs['dir'] as $dir => $contents) { + $this->_formatDir($dirs['dir'][$dir]); + } + } + if (isset($dirs['file'])) { + uksort($dirs['file'], 'strnatcasecmp'); + }; + } + + function _deFormat(&$dirs) + { + if (!count($dirs)) { + return array(); + } + $newdirs = array(); + if (isset($dirs['dir'])) { + foreach ($dirs['dir'] as $dir => $contents) { + $newdir = array(); + $newdir['attribs']['name'] = $dir; + $this->_deFormat($contents); + foreach ($contents as $tag => $val) { + $newdir[$tag] = $val; + } + $newdirs['dir'][] = $newdir; + } + if (count($newdirs['dir']) == 1) { + $newdirs['dir'] = $newdirs['dir'][0]; + } + } + if (isset($dirs['file'])) { + foreach ($dirs['file'] as $name => $file) { + $newdirs['file'][] = $file; + } + if (count($newdirs['file']) == 1) { + $newdirs['file'] = $newdirs['file'][0]; + } + } + $dirs = $newdirs; + } + + /** + * reset all options to default options + * + * @access public + * @see setOption(), XML_Unserializer() + */ + function resetOptions() + { + $this->options = $this->_defaultOptions; + } + + /** + * set an option + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Serializer() + */ + function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * sets several options at once + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Unserializer(), setOption() + */ + function setOptions($options) + { + $this->options = array_merge($this->options, $options); + } + + /** + * serialize data + * + * @access public + * @param mixed $data data to serialize + * @return boolean true on success, pear error on failure + */ + function serialize($data, $options = null) + { + // if options have been specified, use them instead + // of the previously defined ones + if (is_array($options)) { + $optionsBak = $this->options; + if (isset($options['overrideOptions']) && $options['overrideOptions'] == true) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = array_merge($this->options, $options); + } + } else { + $optionsBak = null; + } + + // start depth is zero + $this->_tagDepth = 0; + $this->_serializedData = ''; + // serialize an array + if (is_array($data)) { + $tagName = isset($this->options['rootName']) ? $this->options['rootName'] : 'array'; + $this->_serializedData .= $this->_serializeArray($data, $tagName, $this->options['rootAttributes']); + } + + // add doctype declaration + if ($this->options['addDoctype'] === true) { + $this->_serializedData = XML_Util::getDoctypeDeclaration($tagName, $this->options['doctype']) + . $this->options['linebreak'] + . $this->_serializedData; + } + + // build xml declaration + if ($this->options['addDecl']) { + $atts = array(); + $encoding = isset($this->options['encoding']) ? $this->options['encoding'] : null; + $this->_serializedData = XML_Util::getXMLDeclaration('1.0', $encoding) + . $this->options['linebreak'] + . $this->_serializedData; + } + + + if ($optionsBak !== null) { + $this->options = $optionsBak; + } + + return true; + } + + /** + * get the result of the serialization + * + * @access public + * @return string serialized XML + */ + function getSerializedData() + { + if ($this->_serializedData === null) { + return $this->raiseError('No serialized data available. Use XML_Serializer::serialize() first.', XML_SERIALIZER_ERROR_NO_SERIALIZATION); + } + return $this->_serializedData; + } + + /** + * serialize any value + * + * This method checks for the type of the value and calls the appropriate method + * + * @access private + * @param mixed $value + * @param string $tagName + * @param array $attributes + * @return string + */ + function _serializeValue($value, $tagName = null, $attributes = array()) + { + if (is_array($value)) { + $xml = $this->_serializeArray($value, $tagName, $attributes); + } elseif (is_object($value)) { + $xml = $this->_serializeObject($value, $tagName); + } else { + $tag = array( + 'qname' => $tagName, + 'attributes' => $attributes, + 'content' => $value + ); + $xml = $this->_createXMLTag($tag); + } + return $xml; + } + + /** + * serialize an array + * + * @access private + * @param array $array array to serialize + * @param string $tagName name of the root tag + * @param array $attributes attributes for the root tag + * @return string $string serialized data + * @uses XML_Util::isValidName() to check, whether key has to be substituted + */ + function _serializeArray(&$array, $tagName = null, $attributes = array()) + { + $_content = null; + + /** + * check for special attributes + */ + if ($this->options['attributesArray'] !== null) { + if (isset($array[$this->options['attributesArray']])) { + $attributes = $array[$this->options['attributesArray']]; + unset($array[$this->options['attributesArray']]); + } + /** + * check for special content + */ + if ($this->options['contentName'] !== null) { + if (isset($array[$this->options['contentName']])) { + $_content = $array[$this->options['contentName']]; + unset($array[$this->options['contentName']]); + } + } + } + + /* + * if mode is set to simpleXML, check whether + * the array is associative or indexed + */ + if (is_array($array) && $this->options['mode'] == 'simplexml') { + $indexed = true; + if (!count($array)) { + $indexed = false; + } + foreach ($array as $key => $val) { + if (!is_int($key)) { + $indexed = false; + break; + } + } + + if ($indexed && $this->options['mode'] == 'simplexml') { + $string = ''; + foreach ($array as $key => $val) { + if ($this->options['beautifyFilelist'] && $tagName == 'dir') { + if (!isset($this->_curdir)) { + $this->_curdir = ''; + } + $savedir = $this->_curdir; + if (isset($val['attribs'])) { + if ($val['attribs']['name'] == '/') { + $this->_curdir = '/'; + } else { + if ($this->_curdir == '/') { + $this->_curdir = ''; + } + $this->_curdir .= '/' . $val['attribs']['name']; + } + } + } + $string .= $this->_serializeValue( $val, $tagName, $attributes); + if ($this->options['beautifyFilelist'] && $tagName == 'dir') { + $string .= ' '; + if (empty($savedir)) { + unset($this->_curdir); + } else { + $this->_curdir = $savedir; + } + } + + $string .= $this->options['linebreak']; + // do indentation + if ($this->options['indent'] !== null && $this->_tagDepth > 0) { + $string .= str_repeat($this->options['indent'], $this->_tagDepth); + } + } + return rtrim($string); + } + } + + if ($this->options['scalarAsAttributes'] === true) { + foreach ($array as $key => $value) { + if (is_scalar($value) && (XML_Util::isValidName($key) === true)) { + unset($array[$key]); + $attributes[$this->options['prependAttributes'].$key] = $value; + } + } + } + + // check for empty array => create empty tag + if (empty($array)) { + $tag = array( + 'qname' => $tagName, + 'content' => $_content, + 'attributes' => $attributes + ); + + } else { + $this->_tagDepth++; + $tmp = $this->options['linebreak']; + foreach ($array as $key => $value) { + // do indentation + if ($this->options['indent'] !== null && $this->_tagDepth > 0) { + $tmp .= str_repeat($this->options['indent'], $this->_tagDepth); + } + + // copy key + $origKey = $key; + // key cannot be used as tagname => use default tag + $valid = XML_Util::isValidName($key); + if (PEAR::isError($valid)) { + if ($this->options['classAsTagName'] && is_object($value)) { + $key = get_class($value); + } else { + $key = $this->options['defaultTagName']; + } + } + $atts = array(); + if ($this->options['typeHints'] === true) { + $atts[$this->options['typeAttribute']] = gettype($value); + if ($key !== $origKey) { + $atts[$this->options['keyAttribute']] = (string)$origKey; + } + + } + if ($this->options['beautifyFilelist'] && $key == 'dir') { + if (!isset($this->_curdir)) { + $this->_curdir = ''; + } + $savedir = $this->_curdir; + if (isset($value['attribs'])) { + if ($value['attribs']['name'] == '/') { + $this->_curdir = '/'; + } else { + $this->_curdir .= '/' . $value['attribs']['name']; + } + } + } + + if (is_string($value) && $value && ($value{strlen($value) - 1} == "\n")) { + $value .= str_repeat($this->options['indent'], $this->_tagDepth); + } + $tmp .= $this->_createXMLTag(array( + 'qname' => $key, + 'attributes' => $atts, + 'content' => $value ) + ); + if ($this->options['beautifyFilelist'] && $key == 'dir') { + if (isset($value['attribs'])) { + $tmp .= ' '; + if (empty($savedir)) { + unset($this->_curdir); + } else { + $this->_curdir = $savedir; + } + } + } + $tmp .= $this->options['linebreak']; + } + + $this->_tagDepth--; + if ($this->options['indent']!==null && $this->_tagDepth>0) { + $tmp .= str_repeat($this->options['indent'], $this->_tagDepth); + } + + if (trim($tmp) === '') { + $tmp = null; + } + + $tag = array( + 'qname' => $tagName, + 'content' => $tmp, + 'attributes' => $attributes + ); + } + if ($this->options['typeHints'] === true) { + if (!isset($tag['attributes'][$this->options['typeAttribute']])) { + $tag['attributes'][$this->options['typeAttribute']] = 'array'; + } + } + + $string = $this->_createXMLTag($tag, false); + return $string; + } + + /** + * create a tag from an array + * this method awaits an array in the following format + * array( + * 'qname' => $tagName, + * 'attributes' => array(), + * 'content' => $content, // optional + * 'namespace' => $namespace // optional + * 'namespaceUri' => $namespaceUri // optional + * ) + * + * @access private + * @param array $tag tag definition + * @param boolean $replaceEntities whether to replace XML entities in content or not + * @return string $string XML tag + */ + function _createXMLTag($tag, $replaceEntities = true) + { + if ($this->options['indentAttributes'] !== false) { + $multiline = true; + $indent = str_repeat($this->options['indent'], $this->_tagDepth); + + if ($this->options['indentAttributes'] == '_auto') { + $indent .= str_repeat(' ', (strlen($tag['qname'])+2)); + + } else { + $indent .= $this->options['indentAttributes']; + } + } else { + $indent = $multiline = false; + } + + if (is_array($tag['content'])) { + if (empty($tag['content'])) { + $tag['content'] = ''; + } + } elseif(is_scalar($tag['content']) && (string)$tag['content'] == '') { + $tag['content'] = ''; + } + + if (is_scalar($tag['content']) || is_null($tag['content'])) { + if ($this->options['encoding'] == 'UTF-8' && + version_compare(phpversion(), '5.0.0', 'lt') + ) { + $tag['content'] = utf8_encode($tag['content']); + } + + if ($replaceEntities === true) { + $replaceEntities = XML_UTIL_ENTITIES_XML; + } + + $tag = XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $this->options['linebreak']); + } elseif (is_array($tag['content'])) { + $tag = $this->_serializeArray($tag['content'], $tag['qname'], $tag['attributes']); + } elseif (is_object($tag['content'])) { + $tag = $this->_serializeObject($tag['content'], $tag['qname'], $tag['attributes']); + } elseif (is_resource($tag['content'])) { + settype($tag['content'], 'string'); + $tag = XML_Util::createTagFromArray($tag, $replaceEntities); + } + return $tag; + } +} + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v1.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * package.xml abstraction class + */ +require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v1.php'; +/** + * Parser for package.xml version 1.0 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Parser_v1 +{ + var $_registry; + var $_config; + var $_logger; + /** + * BC hack to allow PEAR_Common::infoFromString() to sort of + * work with the version 2.0 format - there's no filelist though + * @param PEAR_PackageFile_v2 + */ + function fromV2($packagefile) + { + $info = $packagefile->getArray(true); + $ret = new PEAR_PackageFile_v1; + $ret->fromArray($info['old']); + } + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + /** + * @param string contents of package.xml file, version 1.0 + * @return bool success of parsing + */ + function &parse($data, $file, $archive = false) + { + if (!extension_loaded('xml')) { + return PEAR::raiseError('Cannot create xml parser for parsing package.xml, no xml extension'); + } + $xp = xml_parser_create(); + if (!$xp) { + $a = &PEAR::raiseError('Cannot create xml parser for parsing package.xml'); + return $a; + } + xml_set_object($xp, $this); + xml_set_element_handler($xp, '_element_start_1_0', '_element_end_1_0'); + xml_set_character_data_handler($xp, '_pkginfo_cdata_1_0'); + xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false); + + $this->element_stack = array(); + $this->_packageInfo = array('provides' => array()); + $this->current_element = false; + unset($this->dir_install); + $this->_packageInfo['filelist'] = array(); + $this->filelist =& $this->_packageInfo['filelist']; + $this->dir_names = array(); + $this->in_changelog = false; + $this->d_i = 0; + $this->cdata = ''; + $this->_isValid = true; + + if (!xml_parse($xp, $data, 1)) { + $code = xml_get_error_code($xp); + $line = xml_get_current_line_number($xp); + xml_parser_free($xp); + $a = &PEAR::raiseError(sprintf("XML error: %s at line %d", + $str = xml_error_string($code), $line), 2); + return $a; + } + + xml_parser_free($xp); + + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + if (isset($this->_logger)) { + $pf->setLogger($this->_logger); + } + $pf->setPackagefile($file, $archive); + $pf->fromArray($this->_packageInfo); + return $pf; + } + // {{{ _unIndent() + + /** + * Unindent given string + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } elseif (trim(substr($line, 0, $indent_len))) { + $data .= ltrim($line); + } + } + return $data; + } + + // Support for package DTD v1.0: + // {{{ _element_start_1_0() + + /** + * XML parser callback for ending elements. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name name of ending element + * + * @return void + * + * @access private + */ + function _element_start_1_0($xp, $name, $attribs) + { + array_push($this->element_stack, $name); + $this->current_element = $name; + $spos = sizeof($this->element_stack) - 2; + $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : ''; + $this->current_attributes = $attribs; + $this->cdata = ''; + switch ($name) { + case 'dir': + if ($this->in_changelog) { + break; + } + if (array_key_exists('name', $attribs) && $attribs['name'] != '/') { + $attribs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attribs['name']); + if (strrpos($attribs['name'], '/') === strlen($attribs['name']) - 1) { + $attribs['name'] = substr($attribs['name'], 0, + strlen($attribs['name']) - 1); + } + if (strpos($attribs['name'], '/') === 0) { + $attribs['name'] = substr($attribs['name'], 1); + } + $this->dir_names[] = $attribs['name']; + } + if (isset($attribs['baseinstalldir'])) { + $this->dir_install = $attribs['baseinstalldir']; + } + if (isset($attribs['role'])) { + $this->dir_role = $attribs['role']; + } + break; + case 'file': + if ($this->in_changelog) { + break; + } + if (isset($attribs['name'])) { + $path = ''; + if (count($this->dir_names)) { + foreach ($this->dir_names as $dir) { + $path .= $dir . '/'; + } + } + $path .= preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attribs['name']); + unset($attribs['name']); + $this->current_path = $path; + $this->filelist[$path] = $attribs; + // Set the baseinstalldir only if the file don't have this attrib + if (!isset($this->filelist[$path]['baseinstalldir']) && + isset($this->dir_install)) + { + $this->filelist[$path]['baseinstalldir'] = $this->dir_install; + } + // Set the Role + if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) { + $this->filelist[$path]['role'] = $this->dir_role; + } + } + break; + case 'replace': + if (!$this->in_changelog) { + $this->filelist[$this->current_path]['replacements'][] = $attribs; + } + break; + case 'maintainers': + $this->_packageInfo['maintainers'] = array(); + $this->m_i = 0; // maintainers array index + break; + case 'maintainer': + // compatibility check + if (!isset($this->_packageInfo['maintainers'])) { + $this->_packageInfo['maintainers'] = array(); + $this->m_i = 0; + } + $this->_packageInfo['maintainers'][$this->m_i] = array(); + $this->current_maintainer =& $this->_packageInfo['maintainers'][$this->m_i]; + break; + case 'changelog': + $this->_packageInfo['changelog'] = array(); + $this->c_i = 0; // changelog array index + $this->in_changelog = true; + break; + case 'release': + if ($this->in_changelog) { + $this->_packageInfo['changelog'][$this->c_i] = array(); + $this->current_release = &$this->_packageInfo['changelog'][$this->c_i]; + } else { + $this->current_release = &$this->_packageInfo; + } + break; + case 'deps': + if (!$this->in_changelog) { + $this->_packageInfo['release_deps'] = array(); + } + break; + case 'dep': + // dependencies array index + if (!$this->in_changelog) { + $this->d_i++; + isset($attribs['type']) ? ($attribs['type'] = strtolower($attribs['type'])) : false; + $this->_packageInfo['release_deps'][$this->d_i] = $attribs; + } + break; + case 'configureoptions': + if (!$this->in_changelog) { + $this->_packageInfo['configure_options'] = array(); + } + break; + case 'configureoption': + if (!$this->in_changelog) { + $this->_packageInfo['configure_options'][] = $attribs; + } + break; + case 'provides': + if (empty($attribs['type']) || empty($attribs['name'])) { + break; + } + $attribs['explicit'] = true; + $this->_packageInfo['provides']["$attribs[type];$attribs[name]"] = $attribs; + break; + case 'package' : + if (isset($attribs['version'])) { + $this->_packageInfo['xsdversion'] = trim($attribs['version']); + } else { + $this->_packageInfo['xsdversion'] = '1.0'; + } + if (isset($attribs['packagerversion'])) { + $this->_packageInfo['packagerversion'] = $attribs['packagerversion']; + } + break; + } + } + + // }}} + // {{{ _element_end_1_0() + + /** + * XML parser callback for ending elements. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name name of ending element + * + * @return void + * + * @access private + */ + function _element_end_1_0($xp, $name) + { + $data = trim($this->cdata); + switch ($name) { + case 'name': + switch ($this->prev_element) { + case 'package': + $this->_packageInfo['package'] = $data; + break; + case 'maintainer': + $this->current_maintainer['name'] = $data; + break; + } + break; + case 'extends' : + $this->_packageInfo['extends'] = $data; + break; + case 'summary': + $this->_packageInfo['summary'] = $data; + break; + case 'description': + $data = $this->_unIndent($this->cdata); + $this->_packageInfo['description'] = $data; + break; + case 'user': + $this->current_maintainer['handle'] = $data; + break; + case 'email': + $this->current_maintainer['email'] = $data; + break; + case 'role': + $this->current_maintainer['role'] = $data; + break; + case 'version': + if ($this->in_changelog) { + $this->current_release['version'] = $data; + } else { + $this->_packageInfo['version'] = $data; + } + break; + case 'date': + if ($this->in_changelog) { + $this->current_release['release_date'] = $data; + } else { + $this->_packageInfo['release_date'] = $data; + } + break; + case 'notes': + // try to "de-indent" release notes in case someone + // has been over-indenting their xml ;-) + // Trim only on the right side + $data = rtrim($this->_unIndent($this->cdata)); + if ($this->in_changelog) { + $this->current_release['release_notes'] = $data; + } else { + $this->_packageInfo['release_notes'] = $data; + } + break; + case 'warnings': + if ($this->in_changelog) { + $this->current_release['release_warnings'] = $data; + } else { + $this->_packageInfo['release_warnings'] = $data; + } + break; + case 'state': + if ($this->in_changelog) { + $this->current_release['release_state'] = $data; + } else { + $this->_packageInfo['release_state'] = $data; + } + break; + case 'license': + if ($this->in_changelog) { + $this->current_release['release_license'] = $data; + } else { + $this->_packageInfo['release_license'] = $data; + } + break; + case 'dep': + if ($data && !$this->in_changelog) { + $this->_packageInfo['release_deps'][$this->d_i]['name'] = $data; + } + break; + case 'dir': + if ($this->in_changelog) { + break; + } + array_pop($this->dir_names); + break; + case 'file': + if ($this->in_changelog) { + break; + } + if ($data) { + $path = ''; + if (count($this->dir_names)) { + foreach ($this->dir_names as $dir) { + $path .= $dir . '/'; + } + } + $path .= $data; + $this->filelist[$path] = $this->current_attributes; + // Set the baseinstalldir only if the file don't have this attrib + if (!isset($this->filelist[$path]['baseinstalldir']) && + isset($this->dir_install)) + { + $this->filelist[$path]['baseinstalldir'] = $this->dir_install; + } + // Set the Role + if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) { + $this->filelist[$path]['role'] = $this->dir_role; + } + } + break; + case 'maintainer': + if (empty($this->_packageInfo['maintainers'][$this->m_i]['role'])) { + $this->_packageInfo['maintainers'][$this->m_i]['role'] = 'lead'; + } + $this->m_i++; + break; + case 'release': + if ($this->in_changelog) { + $this->c_i++; + } + break; + case 'changelog': + $this->in_changelog = false; + break; + } + array_pop($this->element_stack); + $spos = sizeof($this->element_stack) - 1; + $this->current_element = ($spos > 0) ? $this->element_stack[$spos] : ''; + $this->cdata = ''; + } + + // }}} + // {{{ _pkginfo_cdata_1_0() + + /** + * XML parser callback for character data. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name character data + * + * @return void + * + * @access private + */ + function _pkginfo_cdata_1_0($xp, $data) + { + if (isset($this->cdata)) { + $this->cdata .= $data; + } + } + + // }}} +} +?> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v2.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * base xml parser class + */ +require_once 'phar://go-pear.phar/' . 'PEAR/XMLParser.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v2.php'; +/** + * Parser for package.xml version 2.0 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Parser_v2 extends PEAR_XMLParser +{ + var $_config; + var $_logger; + var $_registry; + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + /** + * Unindent given string + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } else { + $data .= $line . "\n"; + } + } + return $data; + } + + /** + * post-process data + * + * @param string $data + * @param string $element element name + */ + function postProcess($data, $element) + { + if ($element == 'notes') { + return trim($this->_unIndent($data)); + } + return trim($data); + } + + /** + * @param string + * @param string file name of the package.xml + * @param string|false name of the archive this package.xml came from, if any + * @param string class name to instantiate and return. This must be PEAR_PackageFile_v2 or + * a subclass + * @return PEAR_PackageFile_v2 + */ + function &parse($data, $file, $archive = false, $class = 'PEAR_PackageFile_v2') + { + if (PEAR::isError($err = parent::parse($data, $file))) { + return $err; + } + + $ret = new $class; + $ret->encoding = $this->encoding; + $ret->setConfig($this->_config); + if (isset($this->_logger)) { + $ret->setLogger($this->_logger); + } + + $ret->fromArray($this->_unserializedData); + $ret->setPackagefile($file, $archive); + return $ret; + } +} + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v1.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * For error handling + */ +require_once 'phar://go-pear.phar/' . 'PEAR/ErrorStack.php'; + +/** + * Error code if parsing is attempted with no xml extension + */ +define('PEAR_PACKAGEFILE_ERROR_NO_XML_EXT', 3); + +/** + * Error code if creating the xml parser resource fails + */ +define('PEAR_PACKAGEFILE_ERROR_CANT_MAKE_PARSER', 4); + +/** + * Error code used for all sax xml parsing errors + */ +define('PEAR_PACKAGEFILE_ERROR_PARSER_ERROR', 5); + +/** + * Error code used when there is no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_NAME', 6); + +/** + * Error code when a package name is not valid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_NAME', 7); + +/** + * Error code used when no summary is parsed + */ +define('PEAR_PACKAGEFILE_ERROR_NO_SUMMARY', 8); + +/** + * Error code for summaries that are more than 1 line + */ +define('PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY', 9); + +/** + * Error code used when no description is present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION', 10); + +/** + * Error code used when no license is present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_LICENSE', 11); + +/** + * Error code used when a version number is not present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_VERSION', 12); + +/** + * Error code used when a version number is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_VERSION', 13); + +/** + * Error code when release state is missing + */ +define('PEAR_PACKAGEFILE_ERROR_NO_STATE', 14); + +/** + * Error code when release state is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_STATE', 15); + +/** + * Error code when release state is missing + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DATE', 16); + +/** + * Error code when release state is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DATE', 17); + +/** + * Error code when no release notes are found + */ +define('PEAR_PACKAGEFILE_ERROR_NO_NOTES', 18); + +/** + * Error code when no maintainers are found + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS', 19); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE', 20); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE', 21); + +/** + * Error code when a maintainer has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME', 22); + +/** + * Error code when a maintainer has no email + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL', 23); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_MAINTROLE', 24); + +/** + * Error code when a dependency is not a PHP dependency, but has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPNAME', 25); + +/** + * Error code when a dependency has no type (pkg, php, etc.) + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE', 26); + +/** + * Error code when a dependency has no relation (lt, ge, has, etc.) + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPREL', 27); + +/** + * Error code when a dependency is not a 'has' relation, but has no version + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION', 28); + +/** + * Error code when a dependency has an invalid relation + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPREL', 29); + +/** + * Error code when a dependency has an invalid type + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPTYPE', 30); + +/** + * Error code when a dependency has an invalid optional option + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL', 31); + +/** + * Error code when a dependency is a pkg dependency, and has an invalid package name + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPNAME', 32); + +/** + * Error code when a dependency has a channel="foo" attribute, and foo is not a registered channel + */ +define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_DEPCHANNEL', 33); + +/** + * Error code when rel="has" and version attribute is present. + */ +define('PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED', 34); + +/** + * Error code when type="php" and dependency name is present + */ +define('PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED', 35); + +/** + * Error code when a configure option has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_CONFNAME', 36); + +/** + * Error code when a configure option has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT', 37); + +/** + * Error code when a file in the filelist has an invalid role + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE', 38); + +/** + * Error code when a file in the filelist has no role + */ +define('PEAR_PACKAGEFILE_ERROR_NO_FILEROLE', 39); + +/** + * Error code when analyzing a php source file that has parse errors + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE', 40); + +/** + * Error code when analyzing a php source file reveals a source element + * without a package name prefix + */ +define('PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX', 41); + +/** + * Error code when an unknown channel is specified + */ +define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_CHANNEL', 42); + +/** + * Error code when no files are found in the filelist + */ +define('PEAR_PACKAGEFILE_ERROR_NO_FILES', 43); + +/** + * Error code when a file is not valid php according to _analyzeSourceCode() + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_FILE', 44); + +/** + * Error code when the channel validator returns an error or warning + */ +define('PEAR_PACKAGEFILE_ERROR_CHANNELVAL', 45); + +/** + * Error code when a php5 package is packaged in php4 (analysis doesn't work) + */ +define('PEAR_PACKAGEFILE_ERROR_PHP5', 46); + +/** + * Error code when a file is listed in package.xml but does not exist + */ +define('PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND', 47); + +/** + * Error code when a + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_v1 +{ + /** + * @access private + * @var PEAR_ErrorStack + * @access private + */ + var $_stack; + + /** + * A registry object, used to access the package name validation regex for non-standard channels + * @var PEAR_Registry + * @access private + */ + var $_registry; + + /** + * An object that contains a log method that matches PEAR_Common::log's signature + * @var object + * @access private + */ + var $_logger; + + /** + * Parsed package information + * @var array + * @access private + */ + var $_packageInfo; + + /** + * path to package.xml + * @var string + * @access private + */ + var $_packageFile; + + /** + * path to package .tgz or false if this is a local/extracted package.xml + * @var string + * @access private + */ + var $_archiveFile; + + /** + * @var int + * @access private + */ + var $_isValid = 0; + + /** + * Determines whether this packagefile was initialized only with partial package info + * + * If this package file was constructed via parsing REST, it will only contain + * + * - package name + * - channel name + * - dependencies + * @var boolean + * @access private + */ + var $_incomplete = true; + + /** + * @param bool determines whether to return a PEAR_Error object, or use the PEAR_ErrorStack + * @param string Name of Error Stack class to use. + */ + function PEAR_PackageFile_v1() + { + $this->_stack = &new PEAR_ErrorStack('PEAR_PackageFile_v1'); + $this->_stack->setErrorMessageTemplate($this->_getErrorMessage()); + $this->_isValid = 0; + } + + function installBinary($installer) + { + return false; + } + + function isExtension($name) + { + return false; + } + + function setConfig(&$config) + { + $this->_config = &$config; + $this->_registry = &$config->getRegistry(); + } + + function setRequestedGroup() + { + // placeholder + } + + /** + * For saving in the registry. + * + * Set the last version that was installed + * @param string + */ + function setLastInstalledVersion($version) + { + $this->_packageInfo['_lastversion'] = $version; + } + + /** + * @return string|false + */ + function getLastInstalledVersion() + { + if (isset($this->_packageInfo['_lastversion'])) { + return $this->_packageInfo['_lastversion']; + } + return false; + } + + function getInstalledBinary() + { + return false; + } + + function listPostinstallScripts() + { + return false; + } + + function initPostinstallScripts() + { + return false; + } + + function setLogger(&$logger) + { + if ($logger && (!is_object($logger) || !method_exists($logger, 'log'))) { + return PEAR::raiseError('Logger must be compatible with PEAR_Common::log'); + } + $this->_logger = &$logger; + } + + function setPackagefile($file, $archive = false) + { + $this->_packageFile = $file; + $this->_archiveFile = $archive ? $archive : $file; + } + + function getPackageFile() + { + return isset($this->_packageFile) ? $this->_packageFile : false; + } + + function getPackageType() + { + return 'php'; + } + + function getArchiveFile() + { + return $this->_archiveFile; + } + + function packageInfo($field) + { + if (!is_string($field) || empty($field) || + !isset($this->_packageInfo[$field])) { + return false; + } + return $this->_packageInfo[$field]; + } + + function setDirtree($path) + { + if (!isset($this->_packageInfo['dirtree'])) { + $this->_packageInfo['dirtree'] = array(); + } + $this->_packageInfo['dirtree'][$path] = true; + } + + function getDirtree() + { + if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) { + return $this->_packageInfo['dirtree']; + } + return false; + } + + function resetDirtree() + { + unset($this->_packageInfo['dirtree']); + } + + function fromArray($pinfo) + { + $this->_incomplete = false; + $this->_packageInfo = $pinfo; + } + + function isIncomplete() + { + return $this->_incomplete; + } + + function getChannel() + { + return 'pear.php.net'; + } + + function getUri() + { + return false; + } + + function getTime() + { + return false; + } + + function getExtends() + { + if (isset($this->_packageInfo['extends'])) { + return $this->_packageInfo['extends']; + } + return false; + } + + /** + * @return array + */ + function toArray() + { + if (!$this->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + return $this->getArray(); + } + + function getArray() + { + return $this->_packageInfo; + } + + function getName() + { + return $this->getPackage(); + } + + function getPackage() + { + if (isset($this->_packageInfo['package'])) { + return $this->_packageInfo['package']; + } + return false; + } + + /** + * WARNING - don't use this unless you know what you are doing + */ + function setRawPackage($package) + { + $this->_packageInfo['package'] = $package; + } + + function setPackage($package) + { + $this->_packageInfo['package'] = $package; + $this->_isValid = false; + } + + function getVersion() + { + if (isset($this->_packageInfo['version'])) { + return $this->_packageInfo['version']; + } + return false; + } + + function setVersion($version) + { + $this->_packageInfo['version'] = $version; + $this->_isValid = false; + } + + function clearMaintainers() + { + unset($this->_packageInfo['maintainers']); + } + + function getMaintainers() + { + if (isset($this->_packageInfo['maintainers'])) { + return $this->_packageInfo['maintainers']; + } + return false; + } + + /** + * Adds a new maintainer - no checking of duplicates is performed, use + * updatemaintainer for that purpose. + */ + function addMaintainer($role, $handle, $name, $email) + { + $this->_packageInfo['maintainers'][] = + array('handle' => $handle, 'role' => $role, 'email' => $email, 'name' => $name); + $this->_isValid = false; + } + + function updateMaintainer($role, $handle, $name, $email) + { + $found = false; + if (!isset($this->_packageInfo['maintainers']) || + !is_array($this->_packageInfo['maintainers'])) { + return $this->addMaintainer($role, $handle, $name, $email); + } + foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) { + if ($maintainer['handle'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo['maintainers'][$found]); + $this->_packageInfo['maintainers'] = + array_values($this->_packageInfo['maintainers']); + } + $this->addMaintainer($role, $handle, $name, $email); + } + + function deleteMaintainer($handle) + { + $found = false; + foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) { + if ($maintainer['handle'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo['maintainers'][$found]); + $this->_packageInfo['maintainers'] = + array_values($this->_packageInfo['maintainers']); + return true; + } + return false; + } + + function getState() + { + if (isset($this->_packageInfo['release_state'])) { + return $this->_packageInfo['release_state']; + } + return false; + } + + function setRawState($state) + { + $this->_packageInfo['release_state'] = $state; + } + + function setState($state) + { + $this->_packageInfo['release_state'] = $state; + $this->_isValid = false; + } + + function getDate() + { + if (isset($this->_packageInfo['release_date'])) { + return $this->_packageInfo['release_date']; + } + return false; + } + + function setDate($date) + { + $this->_packageInfo['release_date'] = $date; + $this->_isValid = false; + } + + function getLicense() + { + if (isset($this->_packageInfo['release_license'])) { + return $this->_packageInfo['release_license']; + } + return false; + } + + function setLicense($date) + { + $this->_packageInfo['release_license'] = $date; + $this->_isValid = false; + } + + function getSummary() + { + if (isset($this->_packageInfo['summary'])) { + return $this->_packageInfo['summary']; + } + return false; + } + + function setSummary($summary) + { + $this->_packageInfo['summary'] = $summary; + $this->_isValid = false; + } + + function getDescription() + { + if (isset($this->_packageInfo['description'])) { + return $this->_packageInfo['description']; + } + return false; + } + + function setDescription($desc) + { + $this->_packageInfo['description'] = $desc; + $this->_isValid = false; + } + + function getNotes() + { + if (isset($this->_packageInfo['release_notes'])) { + return $this->_packageInfo['release_notes']; + } + return false; + } + + function setNotes($notes) + { + $this->_packageInfo['release_notes'] = $notes; + $this->_isValid = false; + } + + function getDeps() + { + if (isset($this->_packageInfo['release_deps'])) { + return $this->_packageInfo['release_deps']; + } + return false; + } + + /** + * Reset dependencies prior to adding new ones + */ + function clearDeps() + { + unset($this->_packageInfo['release_deps']); + } + + function addPhpDep($version, $rel) + { + $this->_isValid = false; + $this->_packageInfo['release_deps'][] = + array('type' => 'php', + 'rel' => $rel, + 'version' => $version); + } + + function addPackageDep($name, $version, $rel, $optional = 'no') + { + $this->_isValid = false; + $dep = + array('type' => 'pkg', + 'name' => $name, + 'rel' => $rel, + 'optional' => $optional); + if ($rel != 'has' && $rel != 'not') { + $dep['version'] = $version; + } + $this->_packageInfo['release_deps'][] = $dep; + } + + function addExtensionDep($name, $version, $rel, $optional = 'no') + { + $this->_isValid = false; + $this->_packageInfo['release_deps'][] = + array('type' => 'ext', + 'name' => $name, + 'rel' => $rel, + 'version' => $version, + 'optional' => $optional); + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setDeps($deps) + { + $this->_packageInfo['release_deps'] = $deps; + } + + function hasDeps() + { + return isset($this->_packageInfo['release_deps']) && + count($this->_packageInfo['release_deps']); + } + + function getDependencyGroup($group) + { + return false; + } + + function isCompatible($pf) + { + return false; + } + + function isSubpackageOf($p) + { + return $p->isSubpackage($this); + } + + function isSubpackage($p) + { + return false; + } + + function dependsOn($package, $channel) + { + if (strtolower($channel) != 'pear.php.net') { + return false; + } + if (!($deps = $this->getDeps())) { + return false; + } + foreach ($deps as $dep) { + if ($dep['type'] != 'pkg') { + continue; + } + if (strtolower($dep['name']) == strtolower($package)) { + return true; + } + } + return false; + } + + function getConfigureOptions() + { + if (isset($this->_packageInfo['configure_options'])) { + return $this->_packageInfo['configure_options']; + } + return false; + } + + function hasConfigureOptions() + { + return isset($this->_packageInfo['configure_options']) && + count($this->_packageInfo['configure_options']); + } + + function addConfigureOption($name, $prompt, $default = false) + { + $o = array('name' => $name, 'prompt' => $prompt); + if ($default !== false) { + $o['default'] = $default; + } + if (!isset($this->_packageInfo['configure_options'])) { + $this->_packageInfo['configure_options'] = array(); + } + $this->_packageInfo['configure_options'][] = $o; + } + + function clearConfigureOptions() + { + unset($this->_packageInfo['configure_options']); + } + + function getProvides() + { + if (isset($this->_packageInfo['provides'])) { + return $this->_packageInfo['provides']; + } + return false; + } + + function getProvidesExtension() + { + return false; + } + + function addFile($dir, $file, $attrs) + { + $dir = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), $dir); + if ($dir == '/' || $dir == '') { + $dir = ''; + } else { + $dir .= '/'; + } + $file = $dir . $file; + $file = preg_replace('![\\/]+!', '/', $file); + $this->_packageInfo['filelist'][$file] = $attrs; + } + + function getInstallationFilelist() + { + return $this->getFilelist(); + } + + function getFilelist() + { + if (isset($this->_packageInfo['filelist'])) { + return $this->_packageInfo['filelist']; + } + return false; + } + + function setFileAttribute($file, $attr, $value) + { + $this->_packageInfo['filelist'][$file][$attr] = $value; + } + + function resetFilelist() + { + $this->_packageInfo['filelist'] = array(); + } + + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts); + } else { + $this->_packageInfo['filelist'][$file] = $atts; + } + } + + function getChangelog() + { + if (isset($this->_packageInfo['changelog'])) { + return $this->_packageInfo['changelog']; + } + return false; + } + + function getPackagexmlVersion() + { + return '1.0'; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getValidationWarnings($purge = true) + { + return $this->_stack->getErrors($purge); + } + + // }}} + /** + * Validation error. Also marks the object contents as invalid + * @param error code + * @param array error information + * @access private + */ + function _validateError($code, $params = array()) + { + $this->_stack->push($code, 'error', $params, false, false, debug_backtrace()); + $this->_isValid = false; + } + + /** + * Validation warning. Does not mark the object contents invalid. + * @param error code + * @param array error information + * @access private + */ + function _validateWarning($code, $params = array()) + { + $this->_stack->push($code, 'warning', $params, false, false, debug_backtrace()); + } + + /** + * @param integer error code + * @access protected + */ + function _getErrorMessage() + { + return array( + PEAR_PACKAGEFILE_ERROR_NO_NAME => + 'Missing Package Name', + PEAR_PACKAGEFILE_ERROR_NO_SUMMARY => + 'No summary found', + PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY => + 'Summary should be on one line', + PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION => + 'Missing description', + PEAR_PACKAGEFILE_ERROR_NO_LICENSE => + 'Missing license', + PEAR_PACKAGEFILE_ERROR_NO_VERSION => + 'No release version found', + PEAR_PACKAGEFILE_ERROR_NO_STATE => + 'No release state found', + PEAR_PACKAGEFILE_ERROR_NO_DATE => + 'No release date found', + PEAR_PACKAGEFILE_ERROR_NO_NOTES => + 'No release notes found', + PEAR_PACKAGEFILE_ERROR_NO_LEAD => + 'Package must have at least one lead maintainer', + PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS => + 'No maintainers found, at least one must be defined', + PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE => + 'Maintainer %index% has no handle (user ID at channel server)', + PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE => + 'Maintainer %index% has no role', + PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME => + 'Maintainer %index% has no name', + PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL => + 'Maintainer %index% has no email', + PEAR_PACKAGEFILE_ERROR_NO_DEPNAME => + 'Dependency %index% is not a php dependency, and has no name', + PEAR_PACKAGEFILE_ERROR_NO_DEPREL => + 'Dependency %index% has no relation (rel)', + PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE => + 'Dependency %index% has no type', + PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED => + 'PHP Dependency %index% has a name attribute of "%name%" which will be' . + ' ignored!', + PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION => + 'Dependency %index% is not a rel="has" or rel="not" dependency, ' . + 'and has no version', + PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION => + 'Dependency %index% is a type="php" dependency, ' . + 'and has no version', + PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED => + 'Dependency %index% is a rel="%rel%" dependency, versioning is ignored', + PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL => + 'Dependency %index% has invalid optional value "%opt%", should be yes or no', + PEAR_PACKAGEFILE_PHP_NO_NOT => + 'Dependency %index%: php dependencies cannot use "not" rel, use "ne"' . + ' to exclude specific versions', + PEAR_PACKAGEFILE_ERROR_NO_CONFNAME => + 'Configure Option %index% has no name', + PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT => + 'Configure Option %index% has no prompt', + PEAR_PACKAGEFILE_ERROR_NO_FILES => + 'No files in section of package.xml', + PEAR_PACKAGEFILE_ERROR_NO_FILEROLE => + 'File "%file%" has no role, expecting one of "%roles%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE => + 'File "%file%" has invalid role "%role%", expecting one of "%roles%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME => + 'File "%file%" cannot start with ".", cannot package or install', + PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE => + 'Parser error: invalid PHP found in file "%file%"', + PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX => + 'in %file%: %type% "%name%" not prefixed with package name "%package%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILE => + 'Parser error: invalid PHP file "%file%"', + PEAR_PACKAGEFILE_ERROR_CHANNELVAL => + 'Channel validator error: field "%field%" - %reason%', + PEAR_PACKAGEFILE_ERROR_PHP5 => + 'Error, PHP5 token encountered in %file%, analysis should be in PHP5', + PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND => + 'File "%file%" in package.xml does not exist', + PEAR_PACKAGEFILE_ERROR_NON_ISO_CHARS => + 'Package.xml contains non-ISO-8859-1 characters, and may not validate', + ); + } + + /** + * Validate XML package definition file. + * + * @access public + * @return boolean + */ + function validate($state = PEAR_VALIDATE_NORMAL, $nofilechecking = false) + { + if (($this->_isValid & $state) == $state) { + return true; + } + $this->_isValid = true; + $info = $this->_packageInfo; + if (empty($info['package'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NAME); + $this->_packageName = $pn = 'unknown'; + } else { + $this->_packageName = $pn = $info['package']; + } + + if (empty($info['summary'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_SUMMARY); + } elseif (strpos(trim($info['summary']), "\n") !== false) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $info['summary'])); + } + if (empty($info['description'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION); + } + if (empty($info['release_license'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LICENSE); + } + if (empty($info['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_VERSION); + } + if (empty($info['release_state'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_STATE); + } + if (empty($info['release_date'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DATE); + } + if (empty($info['release_notes'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NOTES); + } + if (empty($info['maintainers'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS); + } else { + $haslead = false; + $i = 1; + foreach ($info['maintainers'] as $m) { + if (empty($m['handle'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE, + array('index' => $i)); + } + if (empty($m['role'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE, + array('index' => $i, 'roles' => PEAR_Common::getUserRoles())); + } elseif ($m['role'] == 'lead') { + $haslead = true; + } + if (empty($m['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME, + array('index' => $i)); + } + if (empty($m['email'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL, + array('index' => $i)); + } + $i++; + } + if (!$haslead) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LEAD); + } + } + if (!empty($info['release_deps'])) { + $i = 1; + foreach ($info['release_deps'] as $d) { + if (!isset($d['type']) || empty($d['type'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE, + array('index' => $i, 'types' => PEAR_Common::getDependencyTypes())); + continue; + } + if (!isset($d['rel']) || empty($d['rel'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPREL, + array('index' => $i, 'rels' => PEAR_Common::getDependencyRelations())); + continue; + } + if (!empty($d['optional'])) { + if (!in_array($d['optional'], array('yes', 'no'))) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL, + array('index' => $i, 'opt' => $d['optional'])); + } + } + if ($d['rel'] != 'has' && $d['rel'] != 'not' && empty($d['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION, + array('index' => $i)); + } elseif (($d['rel'] == 'has' || $d['rel'] == 'not') && !empty($d['version'])) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED, + array('index' => $i, 'rel' => $d['rel'])); + } + if ($d['type'] == 'php' && !empty($d['name'])) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED, + array('index' => $i, 'name' => $d['name'])); + } elseif ($d['type'] != 'php' && empty($d['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPNAME, + array('index' => $i)); + } + if ($d['type'] == 'php' && empty($d['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION, + array('index' => $i)); + } + if (($d['rel'] == 'not') && ($d['type'] == 'php')) { + $this->_validateError(PEAR_PACKAGEFILE_PHP_NO_NOT, + array('index' => $i)); + } + $i++; + } + } + if (!empty($info['configure_options'])) { + $i = 1; + foreach ($info['configure_options'] as $c) { + if (empty($c['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFNAME, + array('index' => $i)); + } + if (empty($c['prompt'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT, + array('index' => $i)); + } + $i++; + } + } + if (empty($info['filelist'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILES); + $errors[] = 'no files'; + } else { + foreach ($info['filelist'] as $file => $fa) { + if (empty($fa['role'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILEROLE, + array('file' => $file, 'roles' => PEAR_Common::getFileRoles())); + continue; + } elseif (!in_array($fa['role'], PEAR_Common::getFileRoles())) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE, + array('file' => $file, 'role' => $fa['role'], 'roles' => PEAR_Common::getFileRoles())); + } + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', str_replace('\\', '/', $file))) { + // file contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file)); + } + if (isset($fa['install-as']) && + preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $fa['install-as']))) { + // install-as contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file . ' [installed as ' . $fa['install-as'] . ']')); + } + if (isset($fa['baseinstalldir']) && + preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $fa['baseinstalldir']))) { + // install-as contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file . ' [baseinstalldir ' . $fa['baseinstalldir'] . ']')); + } + } + } + if (isset($this->_registry) && $this->_isValid) { + $chan = $this->_registry->getChannel('pear.php.net'); + if (PEAR::isError($chan)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $chan->getMessage()); + return $this->_isValid = 0; + } + $validator = $chan->getValidationObject(); + $validator->setPackageFile($this); + $validator->validate($state); + $failures = $validator->getFailures(); + foreach ($failures['errors'] as $error) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $error); + } + foreach ($failures['warnings'] as $warning) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $warning); + } + } + if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$nofilechecking) { + if ($this->_analyzePhpFiles()) { + $this->_isValid = true; + } + } + if ($this->_isValid) { + return $this->_isValid = $state; + } + return $this->_isValid = 0; + } + + function _analyzePhpFiles() + { + if (!$this->_isValid) { + return false; + } + if (!isset($this->_packageFile)) { + return false; + } + $dir_prefix = dirname($this->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_logger) ? array(&$this->_logger, 'log') : + array($common, 'log'); + $info = $this->getFilelist(); + foreach ($info as $file => $fa) { + if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND, + array('file' => realpath($dir_prefix) . DIRECTORY_SEPARATOR . $file)); + continue; + } + if ($fa['role'] == 'php' && $dir_prefix) { + call_user_func_array($log, array(1, "Analyzing $file")); + $srcinfo = $this->_analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file); + if ($srcinfo) { + $this->_buildProvidesArray($srcinfo); + } + } + } + $this->_packageName = $pn = $this->getPackage(); + $pnl = strlen($pn); + if (isset($this->_packageInfo['provides'])) { + foreach ((array) $this->_packageInfo['provides'] as $key => $what) { + if (isset($what['explicit'])) { + // skip conformance checks if the provides entry is + // specified in the package.xml file + continue; + } + extract($what); + if ($type == 'class') { + if (!strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX, + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn)); + } elseif ($type == 'function') { + if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX, + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn)); + } + } + } + return $this->_isValid; + } + + /** + * Get the default xml generator object + * + * @return PEAR_PackageFile_Generator_v1 + */ + function &getDefaultGenerator() + { + if (!class_exists('PEAR_PackageFile_Generator_v1')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/Generator/v1.php'; + } + $a = &new PEAR_PackageFile_Generator_v1($this); + return $a; + } + + /** + * Get the contents of a file listed within the package.xml + * @param string + * @return string + */ + function getFileContents($file) + { + if ($this->_archiveFile == $this->_packageFile) { // unpacked + $dir = dirname($this->_packageFile); + $file = $dir . DIRECTORY_SEPARATOR . $file; + $file = str_replace(array('/', '\\'), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file); + if (file_exists($file) && is_readable($file)) { + return implode('', file($file)); + } + } else { // tgz + if (!class_exists('Archive_Tar')) { + require_once 'phar://go-pear.phar/' . 'Archive/Tar.php'; + } + $tar = &new Archive_Tar($this->_archiveFile); + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + if ($file != 'package.xml' && $file != 'package2.xml') { + $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file; + } + $file = $tar->extractInString($file); + $tar->popErrorHandling(); + if (PEAR::isError($file)) { + return PEAR::raiseError("Cannot locate file '$file' in archive"); + } + return $file; + } + } + + // {{{ analyzeSourceCode() + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @return mixed + * @access private + */ + function _analyzeSourceCode($file) + { + if (!function_exists("token_get_all")) { + return false; + } + if (!defined('T_DOC_COMMENT')) { + define('T_DOC_COMMENT', T_COMMENT); + } + if (!defined('T_INTERFACE')) { + define('T_INTERFACE', -1); + } + if (!defined('T_IMPLEMENTS')) { + define('T_IMPLEMENTS', -1); + } + if (!$fp = @fopen($file, "r")) { + return false; + } + fclose($fp); + $contents = file_get_contents($file); + $tokens = token_get_all($contents); +/* + for ($i = 0; $i < sizeof($tokens); $i++) { + @list($token, $data) = $tokens[$i]; + if (is_string($token)) { + var_dump($token); + } else { + print token_name($token) . ' '; + var_dump(rtrim($data)); + } + } +*/ + $look_for = 0; + $paren_level = 0; + $bracket_level = 0; + $brace_level = 0; + $lastphpdoc = ''; + $current_class = ''; + $current_interface = ''; + $current_class_level = -1; + $current_function = ''; + $current_function_level = -1; + $declared_classes = array(); + $declared_interfaces = array(); + $declared_functions = array(); + $declared_methods = array(); + $used_classes = array(); + $used_functions = array(); + $extends = array(); + $implements = array(); + $nodeps = array(); + $inquote = false; + $interface = false; + for ($i = 0; $i < sizeof($tokens); $i++) { + if (is_array($tokens[$i])) { + list($token, $data) = $tokens[$i]; + } else { + $token = $tokens[$i]; + $data = ''; + } + if ($inquote) { + if ($token != '"' && $token != T_END_HEREDOC) { + continue; + } else { + $inquote = false; + continue; + } + } + switch ($token) { + case T_WHITESPACE : + continue; + case ';': + if ($interface) { + $current_function = ''; + $current_function_level = -1; + } + break; + case '"': + case T_START_HEREDOC: + $inquote = true; + break; + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case '{': $brace_level++; continue 2; + case '}': + $brace_level--; + if ($current_class_level == $brace_level) { + $current_class = ''; + $current_class_level = -1; + } + if ($current_function_level == $brace_level) { + $current_function = ''; + $current_function_level = -1; + } + continue 2; + case '[': $bracket_level++; continue 2; + case ']': $bracket_level--; continue 2; + case '(': $paren_level++; continue 2; + case ')': $paren_level--; continue 2; + case T_INTERFACE: + $interface = true; + case T_CLASS: + if (($current_class_level != -1) || ($current_function_level != -1)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE, + array('file' => $file)); + return false; + } + case T_FUNCTION: + case T_NEW: + case T_EXTENDS: + case T_IMPLEMENTS: + $look_for = $token; + continue 2; + case T_STRING: + if (version_compare(zend_version(), '2.0', '<')) { + if (in_array(strtolower($data), + array('public', 'private', 'protected', 'abstract', + 'interface', 'implements', 'throw') + )) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_PHP5, + array($file)); + } + } + if ($look_for == T_CLASS) { + $current_class = $data; + $current_class_level = $brace_level; + $declared_classes[] = $current_class; + } elseif ($look_for == T_INTERFACE) { + $current_interface = $data; + $current_class_level = $brace_level; + $declared_interfaces[] = $current_interface; + } elseif ($look_for == T_IMPLEMENTS) { + $implements[$current_class] = $data; + } elseif ($look_for == T_EXTENDS) { + $extends[$current_class] = $data; + } elseif ($look_for == T_FUNCTION) { + if ($current_class) { + $current_function = "$current_class::$data"; + $declared_methods[$current_class][] = $data; + } elseif ($current_interface) { + $current_function = "$current_interface::$data"; + $declared_methods[$current_interface][] = $data; + } else { + $current_function = $data; + $declared_functions[] = $current_function; + } + $current_function_level = $brace_level; + $m = array(); + } elseif ($look_for == T_NEW) { + $used_classes[$data] = true; + } + $look_for = 0; + continue 2; + case T_VARIABLE: + $look_for = 0; + continue 2; + case T_DOC_COMMENT: + case T_COMMENT: + if (preg_match('!^/\*\*\s!', $data)) { + $lastphpdoc = $data; + if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) { + $nodeps = array_merge($nodeps, $m[1]); + } + } + continue 2; + case T_DOUBLE_COLON: + if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE, + array('file' => $file)); + return false; + } + $class = $tokens[$i - 1][1]; + if (strtolower($class) != 'parent') { + $used_classes[$class] = true; + } + continue 2; + } + } + return array( + "source_file" => $file, + "declared_classes" => $declared_classes, + "declared_interfaces" => $declared_interfaces, + "declared_methods" => $declared_methods, + "declared_functions" => $declared_functions, + "used_classes" => array_diff(array_keys($used_classes), $nodeps), + "inheritance" => $extends, + "implements" => $implements, + ); + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access private + * + */ + function _buildProvidesArray($srcinfo) + { + if (!$this->_isValid) { + return false; + } + $file = basename($srcinfo['source_file']); + $pn = $this->getPackage(); + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($this->_packageInfo['provides'][$key])) { + continue; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $this->_packageInfo['provides'][$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($this->_packageInfo['provides'][$key])) { + continue; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($this->_packageInfo['provides'][$key])) { + continue; + } + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + // }}} +} +?> + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v2.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * For error handling + */ +require_once 'phar://go-pear.phar/' . 'PEAR/ErrorStack.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_v2 +{ + + /** + * Parsed package information + * @var array + * @access private + */ + var $_packageInfo = array(); + + /** + * path to package .tgz or false if this is a local/extracted package.xml + * @var string|false + * @access private + */ + var $_archiveFile; + + /** + * path to package .xml or false if this is an abstract parsed-from-string xml + * @var string|false + * @access private + */ + var $_packageFile; + + /** + * This is used by file analysis routines to log progress information + * @var PEAR_Common + * @access protected + */ + var $_logger; + + /** + * This is set to the highest validation level that has been validated + * + * If the package.xml is invalid or unknown, this is set to 0. If + * normal validation has occurred, this is set to PEAR_VALIDATE_NORMAL. If + * downloading/installation validation has occurred it is set to PEAR_VALIDATE_DOWNLOADING + * or INSTALLING, and so on up to PEAR_VALIDATE_PACKAGING. This allows validation + * "caching" to occur, which is particularly important for package validation, so + * that PHP files are not validated twice + * @var int + * @access private + */ + var $_isValid = 0; + + /** + * True if the filelist has been validated + * @param bool + */ + var $_filesValid = false; + + /** + * @var PEAR_Registry + * @access protected + */ + var $_registry; + + /** + * @var PEAR_Config + * @access protected + */ + var $_config; + + /** + * Optional Dependency group requested for installation + * @var string + * @access private + */ + var $_requestedGroup = false; + + /** + * @var PEAR_ErrorStack + * @access protected + */ + var $_stack; + + /** + * Namespace prefix used for tasks in this package.xml - use tasks: whenever possible + */ + var $_tasksNs; + + /** + * Determines whether this packagefile was initialized only with partial package info + * + * If this package file was constructed via parsing REST, it will only contain + * + * - package name + * - channel name + * - dependencies + * @var boolean + * @access private + */ + var $_incomplete = true; + + /** + * @var PEAR_PackageFile_v2_Validator + */ + var $_v2Validator; + + /** + * The constructor merely sets up the private error stack + */ + function PEAR_PackageFile_v2() + { + $this->_stack = new PEAR_ErrorStack('PEAR_PackageFile_v2', false, null); + $this->_isValid = false; + } + + /** + * To make unit-testing easier + * @param PEAR_Frontend_* + * @param array options + * @param PEAR_Config + * @return PEAR_Downloader + * @access protected + */ + function &getPEARDownloader(&$i, $o, &$c) + { + $z = &new PEAR_Downloader($i, $o, $c); + return $z; + } + + /** + * To make unit-testing easier + * @param PEAR_Config + * @param array options + * @param array package name as returned from {@link PEAR_Registry::parsePackageName()} + * @param int PEAR_VALIDATE_* constant + * @return PEAR_Dependency2 + * @access protected + */ + function &getPEARDependency2(&$c, $o, $p, $s = PEAR_VALIDATE_INSTALLING) + { + if (!class_exists('PEAR_Dependency2')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Dependency2.php'; + } + $z = &new PEAR_Dependency2($c, $o, $p, $s); + return $z; + } + + function getInstalledBinary() + { + return isset($this->_packageInfo['#binarypackage']) ? $this->_packageInfo['#binarypackage'] : + false; + } + + /** + * Installation of source package has failed, attempt to download and install the + * binary version of this package. + * @param PEAR_Installer + * @return array|false + */ + function installBinary(&$installer) + { + if (!OS_WINDOWS) { + $a = false; + return $a; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $releasetype = $this->getPackageType() . 'release'; + if (!is_array($installer->getInstallPackages())) { + $a = false; + return $a; + } + foreach ($installer->getInstallPackages() as $p) { + if ($p->isExtension($this->_packageInfo['providesextension'])) { + if ($p->getPackageType() != 'extsrc' && $p->getPackageType() != 'zendextsrc') { + $a = false; + return $a; // the user probably downloaded it separately + } + } + } + if (isset($this->_packageInfo[$releasetype]['binarypackage'])) { + $installer->log(0, 'Attempting to download binary version of extension "' . + $this->_packageInfo['providesextension'] . '"'); + $params = $this->_packageInfo[$releasetype]['binarypackage']; + if (!is_array($params) || !isset($params[0])) { + $params = array($params); + } + if (isset($this->_packageInfo['channel'])) { + foreach ($params as $i => $param) { + $params[$i] = array('channel' => $this->_packageInfo['channel'], + 'package' => $param, 'version' => $this->getVersion()); + } + } + $dl = &$this->getPEARDownloader($installer->ui, $installer->getOptions(), + $installer->config); + $verbose = $dl->config->get('verbose'); + $dl->config->set('verbose', -1); + foreach ($params as $param) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $dl->download(array($param)); + PEAR::popErrorHandling(); + if (is_array($ret) && count($ret)) { + break; + } + } + $dl->config->set('verbose', $verbose); + if (is_array($ret)) { + if (count($ret) == 1) { + $pf = $ret[0]->getPackageFile(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $installer->install($ret[0]); + PEAR::popErrorHandling(); + if (is_array($err)) { + $this->_packageInfo['#binarypackage'] = $ret[0]->getPackage(); + // "install" self, so all dependencies will work transparently + $this->_registry->addPackage2($this); + $installer->log(0, 'Download and install of binary extension "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pf->getChannel(), + 'package' => $pf->getPackage()), true) . '" successful'); + $a = array($ret[0], $err); + return $a; + } + $installer->log(0, 'Download and install of binary extension "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pf->getChannel(), + 'package' => $pf->getPackage()), true) . '" failed'); + } + } + } + } + $a = false; + return $a; + } + + /** + * @return string|false Extension name + */ + function getProvidesExtension() + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + if (isset($this->_packageInfo['providesextension'])) { + return $this->_packageInfo['providesextension']; + } + } + return false; + } + + /** + * @param string Extension name + * @return bool + */ + function isExtension($extension) + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + return $this->_packageInfo['providesextension'] == $extension; + } + return false; + } + + /** + * Tests whether every part of the package.xml 1.0 is represented in + * this package.xml 2.0 + * @param PEAR_PackageFile_v1 + * @return bool + */ + function isEquivalent($pf1) + { + if (!$pf1) { + return true; + } + if ($this->getPackageType() == 'bundle') { + return false; + } + $this->_stack->getErrors(true); + if (!$pf1->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + $pass = true; + if ($pf1->getPackage() != $this->getPackage()) { + $this->_differentPackage($pf1->getPackage()); + $pass = false; + } + if ($pf1->getVersion() != $this->getVersion()) { + $this->_differentVersion($pf1->getVersion()); + $pass = false; + } + if (trim($pf1->getSummary()) != $this->getSummary()) { + $this->_differentSummary($pf1->getSummary()); + $pass = false; + } + if (preg_replace('/\s+/', '', $pf1->getDescription()) != + preg_replace('/\s+/', '', $this->getDescription())) { + $this->_differentDescription($pf1->getDescription()); + $pass = false; + } + if ($pf1->getState() != $this->getState()) { + $this->_differentState($pf1->getState()); + $pass = false; + } + if (!strstr(preg_replace('/\s+/', '', $this->getNotes()), + preg_replace('/\s+/', '', $pf1->getNotes()))) { + $this->_differentNotes($pf1->getNotes()); + $pass = false; + } + $mymaintainers = $this->getMaintainers(); + $yourmaintainers = $pf1->getMaintainers(); + for ($i1 = 0; $i1 < count($yourmaintainers); $i1++) { + $reset = false; + for ($i2 = 0; $i2 < count($mymaintainers); $i2++) { + if ($mymaintainers[$i2]['handle'] == $yourmaintainers[$i1]['handle']) { + if ($mymaintainers[$i2]['role'] != $yourmaintainers[$i1]['role']) { + $this->_differentRole($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['role'], $mymaintainers[$i2]['role']); + $pass = false; + } + if ($mymaintainers[$i2]['email'] != $yourmaintainers[$i1]['email']) { + $this->_differentEmail($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['email'], $mymaintainers[$i2]['email']); + $pass = false; + } + if ($mymaintainers[$i2]['name'] != $yourmaintainers[$i1]['name']) { + $this->_differentName($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['name'], $mymaintainers[$i2]['name']); + $pass = false; + } + unset($mymaintainers[$i2]); + $mymaintainers = array_values($mymaintainers); + unset($yourmaintainers[$i1]); + $yourmaintainers = array_values($yourmaintainers); + $reset = true; + break; + } + } + if ($reset) { + $i1 = -1; + } + } + $this->_unmatchedMaintainers($mymaintainers, $yourmaintainers); + $filelist = $this->getFilelist(); + foreach ($pf1->getFilelist() as $file => $atts) { + if (!isset($filelist[$file])) { + $this->_missingFile($file); + $pass = false; + } + } + return $pass; + } + + function _differentPackage($package) + { + $this->_stack->push(__FUNCTION__, 'error', array('package' => $package, + 'self' => $this->getPackage()), + 'package.xml 1.0 package "%package%" does not match "%self%"'); + } + + function _differentVersion($version) + { + $this->_stack->push(__FUNCTION__, 'error', array('version' => $version, + 'self' => $this->getVersion()), + 'package.xml 1.0 version "%version%" does not match "%self%"'); + } + + function _differentState($state) + { + $this->_stack->push(__FUNCTION__, 'error', array('state' => $state, + 'self' => $this->getState()), + 'package.xml 1.0 state "%state%" does not match "%self%"'); + } + + function _differentRole($handle, $role, $selfrole) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'role' => $role, 'self' => $selfrole), + 'package.xml 1.0 maintainer "%handle%" role "%role%" does not match "%self%"'); + } + + function _differentEmail($handle, $email, $selfemail) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'email' => $email, 'self' => $selfemail), + 'package.xml 1.0 maintainer "%handle%" email "%email%" does not match "%self%"'); + } + + function _differentName($handle, $name, $selfname) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'name' => $name, 'self' => $selfname), + 'package.xml 1.0 maintainer "%handle%" name "%name%" does not match "%self%"'); + } + + function _unmatchedMaintainers($my, $yours) + { + if ($my) { + array_walk($my, create_function('&$i, $k', '$i = $i["handle"];')); + $this->_stack->push(__FUNCTION__, 'error', array('handles' => $my), + 'package.xml 2.0 has unmatched extra maintainers "%handles%"'); + } + if ($yours) { + array_walk($yours, create_function('&$i, $k', '$i = $i["handle"];')); + $this->_stack->push(__FUNCTION__, 'error', array('handles' => $yours), + 'package.xml 1.0 has unmatched extra maintainers "%handles%"'); + } + } + + function _differentNotes($notes) + { + $truncnotes = strlen($notes) < 25 ? $notes : substr($notes, 0, 24) . '...'; + $truncmynotes = strlen($this->getNotes()) < 25 ? $this->getNotes() : + substr($this->getNotes(), 0, 24) . '...'; + $this->_stack->push(__FUNCTION__, 'error', array('notes' => $truncnotes, + 'self' => $truncmynotes), + 'package.xml 1.0 release notes "%notes%" do not match "%self%"'); + } + + function _differentSummary($summary) + { + $truncsummary = strlen($summary) < 25 ? $summary : substr($summary, 0, 24) . '...'; + $truncmysummary = strlen($this->getsummary()) < 25 ? $this->getSummary() : + substr($this->getsummary(), 0, 24) . '...'; + $this->_stack->push(__FUNCTION__, 'error', array('summary' => $truncsummary, + 'self' => $truncmysummary), + 'package.xml 1.0 summary "%summary%" does not match "%self%"'); + } + + function _differentDescription($description) + { + $truncdescription = trim(strlen($description) < 25 ? $description : substr($description, 0, 24) . '...'); + $truncmydescription = trim(strlen($this->getDescription()) < 25 ? $this->getDescription() : + substr($this->getdescription(), 0, 24) . '...'); + $this->_stack->push(__FUNCTION__, 'error', array('description' => $truncdescription, + 'self' => $truncmydescription), + 'package.xml 1.0 description "%description%" does not match "%self%"'); + } + + function _missingFile($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'package.xml 1.0 file "%file%" is not present in '); + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawState($state) + { + if (!isset($this->_packageInfo['stability'])) { + $this->_packageInfo['stability'] = array(); + } + $this->_packageInfo['stability']['release'] = $state; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawCompatible($compatible) + { + $this->_packageInfo['compatible'] = $compatible; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawPackage($package) + { + $this->_packageInfo['name'] = $package; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawChannel($channel) + { + $this->_packageInfo['channel'] = $channel; + } + + function setRequestedGroup($group) + { + $this->_requestedGroup = $group; + } + + function getRequestedGroup() + { + if (isset($this->_requestedGroup)) { + return $this->_requestedGroup; + } + return false; + } + + /** + * For saving in the registry. + * + * Set the last version that was installed + * @param string + */ + function setLastInstalledVersion($version) + { + $this->_packageInfo['_lastversion'] = $version; + } + + /** + * @return string|false + */ + function getLastInstalledVersion() + { + if (isset($this->_packageInfo['_lastversion'])) { + return $this->_packageInfo['_lastversion']; + } + return false; + } + + /** + * Determines whether this package.xml has post-install scripts or not + * @return array|false + */ + function listPostinstallScripts() + { + $filelist = $this->getFilelist(); + $contents = $this->getContents(); + $contents = $contents['dir']['file']; + if (!is_array($contents) || !isset($contents[0])) { + $contents = array($contents); + } + $taskfiles = array(); + foreach ($contents as $file) { + $atts = $file['attribs']; + unset($file['attribs']); + if (count($file)) { + $taskfiles[$atts['name']] = $file; + } + } + $common = new PEAR_Common; + $common->debug = $this->_config->get('verbose'); + $this->_scripts = array(); + $ret = array(); + foreach ($taskfiles as $name => $tasks) { + if (!isset($filelist[$name])) { + // ignored files will not be in the filelist + continue; + } + $atts = $filelist[$name]; + foreach ($tasks as $tag => $raw) { + $task = $this->getTask($tag); + $task = &new $task($this->_config, $common, PEAR_TASK_INSTALL); + if ($task->isScript()) { + $ret[] = $filelist[$name]['installed_as']; + } + } + } + if (count($ret)) { + return $ret; + } + return false; + } + + /** + * Initialize post-install scripts for running + * + * This method can be used to detect post-install scripts, as the return value + * indicates whether any exist + * @return bool + */ + function initPostinstallScripts() + { + $filelist = $this->getFilelist(); + $contents = $this->getContents(); + $contents = $contents['dir']['file']; + if (!is_array($contents) || !isset($contents[0])) { + $contents = array($contents); + } + $taskfiles = array(); + foreach ($contents as $file) { + $atts = $file['attribs']; + unset($file['attribs']); + if (count($file)) { + $taskfiles[$atts['name']] = $file; + } + } + $common = new PEAR_Common; + $common->debug = $this->_config->get('verbose'); + $this->_scripts = array(); + foreach ($taskfiles as $name => $tasks) { + if (!isset($filelist[$name])) { + // file was not installed due to installconditions + continue; + } + $atts = $filelist[$name]; + foreach ($tasks as $tag => $raw) { + $taskname = $this->getTask($tag); + $task = &new $taskname($this->_config, $common, PEAR_TASK_INSTALL); + if (!$task->isScript()) { + continue; // scripts are only handled after installation + } + $lastversion = isset($this->_packageInfo['_lastversion']) ? + $this->_packageInfo['_lastversion'] : null; + $task->init($raw, $atts, $lastversion); + $res = $task->startSession($this, $atts['installed_as']); + if (!$res) { + continue; // skip this file + } + if (PEAR::isError($res)) { + return $res; + } + $assign = &$task; + $this->_scripts[] = &$assign; + } + } + if (count($this->_scripts)) { + return true; + } + return false; + } + + function runPostinstallScripts() + { + if ($this->initPostinstallScripts()) { + $ui = &PEAR_Frontend::singleton(); + if ($ui) { + $ui->runPostinstallScripts($this->_scripts, $this); + } + } + } + + + /** + * Convert a recursive set of and tags into a single tag with + * tags. + */ + function flattenFilelist() + { + if (isset($this->_packageInfo['bundle'])) { + return; + } + $filelist = array(); + if (isset($this->_packageInfo['contents']['dir']['dir'])) { + $this->_getFlattenedFilelist($filelist, $this->_packageInfo['contents']['dir']); + if (!isset($filelist[1])) { + $filelist = $filelist[0]; + } + $this->_packageInfo['contents']['dir']['file'] = $filelist; + unset($this->_packageInfo['contents']['dir']['dir']); + } else { + // else already flattened but check for baseinstalldir propagation + if (isset($this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'])) { + if (isset($this->_packageInfo['contents']['dir']['file'][0])) { + foreach ($this->_packageInfo['contents']['dir']['file'] as $i => $file) { + if (isset($file['attribs']['baseinstalldir'])) { + continue; + } + $this->_packageInfo['contents']['dir']['file'][$i]['attribs']['baseinstalldir'] + = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir']; + } + } else { + if (!isset($this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'])) { + $this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'] + = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir']; + } + } + } + } + } + + /** + * @param array the final flattened file list + * @param array the current directory being processed + * @param string|false any recursively inherited baeinstalldir attribute + * @param string private recursion variable + * @return array + * @access protected + */ + function _getFlattenedFilelist(&$files, $dir, $baseinstall = false, $path = '') + { + if (isset($dir['attribs']) && isset($dir['attribs']['baseinstalldir'])) { + $baseinstall = $dir['attribs']['baseinstalldir']; + } + if (isset($dir['dir'])) { + if (!isset($dir['dir'][0])) { + $dir['dir'] = array($dir['dir']); + } + foreach ($dir['dir'] as $subdir) { + if (!isset($subdir['attribs']) || !isset($subdir['attribs']['name'])) { + $name = '*unknown*'; + } else { + $name = $subdir['attribs']['name']; + } + $newpath = empty($path) ? $name : + $path . '/' . $name; + $this->_getFlattenedFilelist($files, $subdir, + $baseinstall, $newpath); + } + } + if (isset($dir['file'])) { + if (!isset($dir['file'][0])) { + $dir['file'] = array($dir['file']); + } + foreach ($dir['file'] as $file) { + $attrs = $file['attribs']; + $name = $attrs['name']; + if ($baseinstall && !isset($attrs['baseinstalldir'])) { + $attrs['baseinstalldir'] = $baseinstall; + } + $attrs['name'] = empty($path) ? $name : $path . '/' . $name; + $attrs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attrs['name']); + $file['attribs'] = $attrs; + $files[] = $file; + } + } + } + + function setConfig(&$config) + { + $this->_config = &$config; + $this->_registry = &$config->getRegistry(); + } + + function setLogger(&$logger) + { + if (!is_object($logger) || !method_exists($logger, 'log')) { + return PEAR::raiseError('Logger must be compatible with PEAR_Common::log'); + } + $this->_logger = &$logger; + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setDeps($deps) + { + $this->_packageInfo['dependencies'] = $deps; + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setCompatible($compat) + { + $this->_packageInfo['compatible'] = $compat; + } + + function setPackagefile($file, $archive = false) + { + $this->_packageFile = $file; + $this->_archiveFile = $archive ? $archive : $file; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getValidationWarnings($purge = true) + { + return $this->_stack->getErrors($purge); + } + + function getPackageFile() + { + return $this->_packageFile; + } + + function getArchiveFile() + { + return $this->_archiveFile; + } + + + /** + * Directly set the array that defines this packagefile + * + * WARNING: no validation. This should only be performed by internal methods + * inside PEAR or by inputting an array saved from an existing PEAR_PackageFile_v2 + * @param array + */ + function fromArray($pinfo) + { + unset($pinfo['old']); + unset($pinfo['xsdversion']); + $this->_incomplete = false; + $this->_packageInfo = $pinfo; + } + + function isIncomplete() + { + return $this->_incomplete; + } + + /** + * @return array + */ + function toArray($forreg = false) + { + if (!$this->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + return $this->getArray($forreg); + } + + function getArray($forReg = false) + { + if ($forReg) { + $arr = $this->_packageInfo; + $arr['old'] = array(); + $arr['old']['version'] = $this->getVersion(); + $arr['old']['release_date'] = $this->getDate(); + $arr['old']['release_state'] = $this->getState(); + $arr['old']['release_license'] = $this->getLicense(); + $arr['old']['release_notes'] = $this->getNotes(); + $arr['old']['release_deps'] = $this->getDeps(); + $arr['old']['maintainers'] = $this->getMaintainers(); + $arr['xsdversion'] = '2.0'; + return $arr; + } else { + $info = $this->_packageInfo; + unset($info['dirtree']); + if (isset($info['_lastversion'])) { + unset($info['_lastversion']); + } + if (isset($info['#binarypackage'])) { + unset($info['#binarypackage']); + } + return $info; + } + } + + function packageInfo($field) + { + $arr = $this->getArray(true); + if ($field == 'state') { + return $arr['stability']['release']; + } + if ($field == 'api-version') { + return $arr['version']['api']; + } + if ($field == 'api-state') { + return $arr['stability']['api']; + } + if (isset($arr['old'][$field])) { + if (!is_string($arr['old'][$field])) { + return null; + } + return $arr['old'][$field]; + } + if (isset($arr[$field])) { + if (!is_string($arr[$field])) { + return null; + } + return $arr[$field]; + } + return null; + } + + function getName() + { + return $this->getPackage(); + } + + function getPackage() + { + if (isset($this->_packageInfo['name'])) { + return $this->_packageInfo['name']; + } + return false; + } + + function getChannel() + { + if (isset($this->_packageInfo['uri'])) { + return '__uri'; + } + if (isset($this->_packageInfo['channel'])) { + return strtolower($this->_packageInfo['channel']); + } + return false; + } + + function getUri() + { + if (isset($this->_packageInfo['uri'])) { + return $this->_packageInfo['uri']; + } + return false; + } + + function getExtends() + { + if (isset($this->_packageInfo['extends'])) { + return $this->_packageInfo['extends']; + } + return false; + } + + function getSummary() + { + if (isset($this->_packageInfo['summary'])) { + return $this->_packageInfo['summary']; + } + return false; + } + + function getDescription() + { + if (isset($this->_packageInfo['description'])) { + return $this->_packageInfo['description']; + } + return false; + } + + function getMaintainers($raw = false) + { + if (!isset($this->_packageInfo['lead'])) { + return false; + } + if ($raw) { + $ret = array('lead' => $this->_packageInfo['lead']); + (isset($this->_packageInfo['developer'])) ? + $ret['developer'] = $this->_packageInfo['developer'] :null; + (isset($this->_packageInfo['contributor'])) ? + $ret['contributor'] = $this->_packageInfo['contributor'] :null; + (isset($this->_packageInfo['helper'])) ? + $ret['helper'] = $this->_packageInfo['helper'] :null; + return $ret; + } else { + $ret = array(); + $leads = isset($this->_packageInfo['lead'][0]) ? $this->_packageInfo['lead'] : + array($this->_packageInfo['lead']); + foreach ($leads as $lead) { + $s = $lead; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'lead'; + $ret[] = $s; + } + if (isset($this->_packageInfo['developer'])) { + $leads = isset($this->_packageInfo['developer'][0]) ? + $this->_packageInfo['developer'] : + array($this->_packageInfo['developer']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'developer'; + $ret[] = $s; + } + } + if (isset($this->_packageInfo['contributor'])) { + $leads = isset($this->_packageInfo['contributor'][0]) ? + $this->_packageInfo['contributor'] : + array($this->_packageInfo['contributor']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'contributor'; + $ret[] = $s; + } + } + if (isset($this->_packageInfo['helper'])) { + $leads = isset($this->_packageInfo['helper'][0]) ? + $this->_packageInfo['helper'] : + array($this->_packageInfo['helper']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'helper'; + $ret[] = $s; + } + } + return $ret; + } + return false; + } + + function getLeads() + { + if (isset($this->_packageInfo['lead'])) { + return $this->_packageInfo['lead']; + } + return false; + } + + function getDevelopers() + { + if (isset($this->_packageInfo['developer'])) { + return $this->_packageInfo['developer']; + } + return false; + } + + function getContributors() + { + if (isset($this->_packageInfo['contributor'])) { + return $this->_packageInfo['contributor']; + } + return false; + } + + function getHelpers() + { + if (isset($this->_packageInfo['helper'])) { + return $this->_packageInfo['helper']; + } + return false; + } + + function setDate($date) + { + if (!isset($this->_packageInfo['date'])) { + // ensure that the extends tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', + 'zendextbinrelease', 'bundle', 'changelog'), array(), 'date'); + } + $this->_packageInfo['date'] = $date; + $this->_isValid = 0; + } + + function setTime($time) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['time'])) { + // ensure that the time tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', + 'zendextbinrelease', 'bundle', 'changelog'), $time, 'time'); + } + $this->_packageInfo['time'] = $time; + } + + function getDate() + { + if (isset($this->_packageInfo['date'])) { + return $this->_packageInfo['date']; + } + return false; + } + + function getTime() + { + if (isset($this->_packageInfo['time'])) { + return $this->_packageInfo['time']; + } + return false; + } + + /** + * @param package|api version category to return + */ + function getVersion($key = 'release') + { + if (isset($this->_packageInfo['version'][$key])) { + return $this->_packageInfo['version'][$key]; + } + return false; + } + + function getStability() + { + if (isset($this->_packageInfo['stability'])) { + return $this->_packageInfo['stability']; + } + return false; + } + + function getState($key = 'release') + { + if (isset($this->_packageInfo['stability'][$key])) { + return $this->_packageInfo['stability'][$key]; + } + return false; + } + + function getLicense($raw = false) + { + if (isset($this->_packageInfo['license'])) { + if ($raw) { + return $this->_packageInfo['license']; + } + if (is_array($this->_packageInfo['license'])) { + return $this->_packageInfo['license']['_content']; + } else { + return $this->_packageInfo['license']; + } + } + return false; + } + + function getLicenseLocation() + { + if (!isset($this->_packageInfo['license']) || !is_array($this->_packageInfo['license'])) { + return false; + } + return $this->_packageInfo['license']['attribs']; + } + + function getNotes() + { + if (isset($this->_packageInfo['notes'])) { + return $this->_packageInfo['notes']; + } + return false; + } + + /** + * Return the tag contents, if any + * @return array|false + */ + function getUsesrole() + { + if (isset($this->_packageInfo['usesrole'])) { + return $this->_packageInfo['usesrole']; + } + return false; + } + + /** + * Return the tag contents, if any + * @return array|false + */ + function getUsestask() + { + if (isset($this->_packageInfo['usestask'])) { + return $this->_packageInfo['usestask']; + } + return false; + } + + /** + * This should only be used to retrieve filenames and install attributes + */ + function getFilelist($preserve = false) + { + if (isset($this->_packageInfo['filelist']) && !$preserve) { + return $this->_packageInfo['filelist']; + } + $this->flattenFilelist(); + if ($contents = $this->getContents()) { + $ret = array(); + if (!isset($contents['dir'])) { + return false; + } + if (!isset($contents['dir']['file'][0])) { + $contents['dir']['file'] = array($contents['dir']['file']); + } + foreach ($contents['dir']['file'] as $file) { + $name = $file['attribs']['name']; + if (!$preserve) { + $file = $file['attribs']; + } + $ret[$name] = $file; + } + if (!$preserve) { + $this->_packageInfo['filelist'] = $ret; + } + return $ret; + } + return false; + } + + /** + * Return configure options array, if any + * + * @return array|false + */ + function getConfigureOptions() + { + if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') { + return false; + } + + $releases = $this->getReleases(); + if (isset($releases[0])) { + $releases = $releases[0]; + } + + if (isset($releases['configureoption'])) { + if (!isset($releases['configureoption'][0])) { + $releases['configureoption'] = array($releases['configureoption']); + } + + for ($i = 0; $i < count($releases['configureoption']); $i++) { + $releases['configureoption'][$i] = $releases['configureoption'][$i]['attribs']; + } + + return $releases['configureoption']; + } + + return false; + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function resetFilelist() + { + $this->_packageInfo['filelist'] = array(); + } + + /** + * Retrieve a list of files that should be installed on this computer + * @return array + */ + function getInstallationFilelist($forfilecheck = false) + { + $contents = $this->getFilelist(true); + if (isset($contents['dir']['attribs']['baseinstalldir'])) { + $base = $contents['dir']['attribs']['baseinstalldir']; + } + if (isset($this->_packageInfo['bundle'])) { + return PEAR::raiseError( + 'Exception: bundles should be handled in download code only'); + } + $release = $this->getReleases(); + if ($release) { + if (!isset($release[0])) { + if (!isset($release['installconditions']) && !isset($release['filelist'])) { + if ($forfilecheck) { + return $this->getFilelist(); + } + return $contents; + } + $release = array($release); + } + $depchecker = &$this->getPEARDependency2($this->_config, array(), + array('channel' => $this->getChannel(), 'package' => $this->getPackage()), + PEAR_VALIDATE_INSTALLING); + foreach ($release as $instance) { + if (isset($instance['installconditions'])) { + $installconditions = $instance['installconditions']; + if (is_array($installconditions)) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($installconditions as $type => $conditions) { + if (!isset($conditions[0])) { + $conditions = array($conditions); + } + foreach ($conditions as $condition) { + $ret = $depchecker->{"validate{$type}Dependency"}($condition); + if (PEAR::isError($ret)) { + PEAR::popErrorHandling(); + continue 3; // skip this release + } + } + } + PEAR::popErrorHandling(); + } + } + // this is the release to use + if (isset($instance['filelist'])) { + // ignore files + if (isset($instance['filelist']['ignore'])) { + $ignore = isset($instance['filelist']['ignore'][0]) ? + $instance['filelist']['ignore'] : + array($instance['filelist']['ignore']); + foreach ($ignore as $ig) { + unset ($contents[$ig['attribs']['name']]); + } + } + // install files as this name + if (isset($instance['filelist']['install'])) { + $installas = isset($instance['filelist']['install'][0]) ? + $instance['filelist']['install'] : + array($instance['filelist']['install']); + foreach ($installas as $as) { + $contents[$as['attribs']['name']]['attribs']['install-as'] = + $as['attribs']['as']; + } + } + } + if ($forfilecheck) { + foreach ($contents as $file => $attrs) { + $contents[$file] = $attrs['attribs']; + } + } + return $contents; + } + } else { // simple release - no installconditions or install-as + if ($forfilecheck) { + return $this->getFilelist(); + } + return $contents; + } + // no releases matched + return PEAR::raiseError('No releases in package.xml matched the existing operating ' . + 'system, extensions installed, or architecture, cannot install'); + } + + /** + * This is only used at install-time, after all serialization + * is over. + * @param string file name + * @param string installed path + */ + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + function getInstalledLocation($file) + { + if (isset($this->_packageInfo['filelist'][$file]['installed_as'])) { + return $this->_packageInfo['filelist'][$file]['installed_as']; + } + return false; + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']); + } else { + $this->_packageInfo['filelist'][$file] = $atts['attribs']; + } + } + + /** + * Retrieve the contents tag + */ + function getContents() + { + if (isset($this->_packageInfo['contents'])) { + return $this->_packageInfo['contents']; + } + return false; + } + + /** + * @param string full path to file + * @param string attribute name + * @param string attribute value + * @param int risky but fast - use this to choose a file based on its position in the list + * of files. Index is zero-based like PHP arrays. + * @return bool success of operation + */ + function setFileAttribute($filename, $attr, $value, $index = false) + { + $this->_isValid = 0; + if (in_array($attr, array('role', 'name', 'baseinstalldir'))) { + $this->_filesValid = false; + } + if ($index !== false && + isset($this->_packageInfo['contents']['dir']['file'][$index]['attribs'])) { + $this->_packageInfo['contents']['dir']['file'][$index]['attribs'][$attr] = $value; + return true; + } + if (!isset($this->_packageInfo['contents']['dir']['file'])) { + return false; + } + $files = $this->_packageInfo['contents']['dir']['file']; + if (!isset($files[0])) { + $files = array($files); + $ind = false; + } else { + $ind = true; + } + foreach ($files as $i => $file) { + if (isset($file['attribs'])) { + if ($file['attribs']['name'] == $filename) { + if ($ind) { + $this->_packageInfo['contents']['dir']['file'][$i]['attribs'][$attr] = $value; + } else { + $this->_packageInfo['contents']['dir']['file']['attribs'][$attr] = $value; + } + return true; + } + } + } + return false; + } + + function setDirtree($path) + { + if (!isset($this->_packageInfo['dirtree'])) { + $this->_packageInfo['dirtree'] = array(); + } + $this->_packageInfo['dirtree'][$path] = true; + } + + function getDirtree() + { + if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) { + return $this->_packageInfo['dirtree']; + } + return false; + } + + function resetDirtree() + { + unset($this->_packageInfo['dirtree']); + } + + /** + * Determines whether this package claims it is compatible with the version of + * the package that has a recommended version dependency + * @param PEAR_PackageFile_v2|PEAR_PackageFile_v1|PEAR_Downloader_Package + * @return boolean + */ + function isCompatible($pf) + { + if (!isset($this->_packageInfo['compatible'])) { + return false; + } + if (!isset($this->_packageInfo['channel'])) { + return false; + } + $me = $pf->getVersion(); + $compatible = $this->_packageInfo['compatible']; + if (!isset($compatible[0])) { + $compatible = array($compatible); + } + $found = false; + foreach ($compatible as $info) { + if (strtolower($info['name']) == strtolower($pf->getPackage())) { + if (strtolower($info['channel']) == strtolower($pf->getChannel())) { + $found = true; + break; + } + } + } + if (!$found) { + return false; + } + if (isset($info['exclude'])) { + if (!isset($info['exclude'][0])) { + $info['exclude'] = array($info['exclude']); + } + foreach ($info['exclude'] as $exclude) { + if (version_compare($me, $exclude, '==')) { + return false; + } + } + } + if (version_compare($me, $info['min'], '>=') && version_compare($me, $info['max'], '<=')) { + return true; + } + return false; + } + + /** + * @return array|false + */ + function getCompatible() + { + if (isset($this->_packageInfo['compatible'])) { + return $this->_packageInfo['compatible']; + } + return false; + } + + function getDependencies() + { + if (isset($this->_packageInfo['dependencies'])) { + return $this->_packageInfo['dependencies']; + } + return false; + } + + function isSubpackageOf($p) + { + return $p->isSubpackage($this); + } + + /** + * Determines whether the passed in package is a subpackage of this package. + * + * No version checking is done, only name verification. + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + */ + function isSubpackage($p) + { + $sub = array(); + if (isset($this->_packageInfo['dependencies']['required']['subpackage'])) { + $sub = $this->_packageInfo['dependencies']['required']['subpackage']; + if (!isset($sub[0])) { + $sub = array($sub); + } + } + if (isset($this->_packageInfo['dependencies']['optional']['subpackage'])) { + $sub1 = $this->_packageInfo['dependencies']['optional']['subpackage']; + if (!isset($sub1[0])) { + $sub1 = array($sub1); + } + $sub = array_merge($sub, $sub1); + } + if (isset($this->_packageInfo['dependencies']['group'])) { + $group = $this->_packageInfo['dependencies']['group']; + if (!isset($group[0])) { + $group = array($group); + } + foreach ($group as $deps) { + if (isset($deps['subpackage'])) { + $sub2 = $deps['subpackage']; + if (!isset($sub2[0])) { + $sub2 = array($sub2); + } + $sub = array_merge($sub, $sub2); + } + } + } + foreach ($sub as $dep) { + if (strtolower($dep['name']) == strtolower($p->getPackage())) { + if (isset($dep['channel'])) { + if (strtolower($dep['channel']) == strtolower($p->getChannel())) { + return true; + } + } else { + if ($dep['uri'] == $p->getURI()) { + return true; + } + } + } + } + return false; + } + + function dependsOn($package, $channel) + { + if (!($deps = $this->getDependencies())) { + return false; + } + foreach (array('package', 'subpackage') as $type) { + foreach (array('required', 'optional') as $needed) { + if (isset($deps[$needed][$type])) { + if (!isset($deps[$needed][$type][0])) { + $deps[$needed][$type] = array($deps[$needed][$type]); + } + foreach ($deps[$needed][$type] as $dep) { + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (strtolower($dep['name']) == strtolower($package) && + $depchannel == $channel) { + return true; + } + } + } + } + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $dep['group'] = array($deps['group']); + } + foreach ($deps['group'] as $group) { + if (isset($group[$type])) { + if (!is_array($group[$type])) { + $group[$type] = array($group[$type]); + } + foreach ($group[$type] as $dep) { + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (strtolower($dep['name']) == strtolower($package) && + $depchannel == $channel) { + return true; + } + } + } + } + } + } + return false; + } + + /** + * Get the contents of a dependency group + * @param string + * @return array|false + */ + function getDependencyGroup($name) + { + $name = strtolower($name); + if (!isset($this->_packageInfo['dependencies']['group'])) { + return false; + } + $groups = $this->_packageInfo['dependencies']['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + foreach ($groups as $group) { + if (strtolower($group['attribs']['name']) == $name) { + return $group; + } + } + return false; + } + + /** + * Retrieve a partial package.xml 1.0 representation of dependencies + * + * a very limited representation of dependencies is returned by this method. + * The tag for excluding certain versions of a dependency is + * completely ignored. In addition, dependency groups are ignored, with the + * assumption that all dependencies in dependency groups are also listed in + * the optional group that work with all dependency groups + * @param boolean return package.xml 2.0 tag + * @return array|false + */ + function getDeps($raw = false, $nopearinstaller = false) + { + if (isset($this->_packageInfo['dependencies'])) { + if ($raw) { + return $this->_packageInfo['dependencies']; + } + $ret = array(); + $map = array( + 'php' => 'php', + 'package' => 'pkg', + 'subpackage' => 'pkg', + 'extension' => 'ext', + 'os' => 'os', + 'pearinstaller' => 'pkg', + ); + foreach (array('required', 'optional') as $type) { + $optional = ($type == 'optional') ? 'yes' : 'no'; + if (!isset($this->_packageInfo['dependencies'][$type]) + || empty($this->_packageInfo['dependencies'][$type])) { + continue; + } + foreach ($this->_packageInfo['dependencies'][$type] as $dtype => $deps) { + if ($dtype == 'pearinstaller' && $nopearinstaller) { + continue; + } + if (!isset($deps[0])) { + $deps = array($deps); + } + foreach ($deps as $dep) { + if (!isset($map[$dtype])) { + // no support for arch type + continue; + } + if ($dtype == 'pearinstaller') { + $dep['name'] = 'PEAR'; + $dep['channel'] = 'pear.php.net'; + } + $s = array('type' => $map[$dtype]); + if (isset($dep['channel'])) { + $s['channel'] = $dep['channel']; + } + if (isset($dep['uri'])) { + $s['uri'] = $dep['uri']; + } + if (isset($dep['name'])) { + $s['name'] = $dep['name']; + } + if (isset($dep['conflicts'])) { + $s['rel'] = 'not'; + } else { + if (!isset($dep['min']) && + !isset($dep['max'])) { + $s['rel'] = 'has'; + $s['optional'] = $optional; + } elseif (isset($dep['min']) && + isset($dep['max'])) { + $s['rel'] = 'ge'; + $s1 = $s; + $s1['rel'] = 'le'; + $s['version'] = $dep['min']; + $s1['version'] = $dep['max']; + if (isset($dep['channel'])) { + $s1['channel'] = $dep['channel']; + } + if ($dtype != 'php') { + $s['name'] = $dep['name']; + $s1['name'] = $dep['name']; + } + $s['optional'] = $optional; + $s1['optional'] = $optional; + $ret[] = $s1; + } elseif (isset($dep['min'])) { + if (isset($dep['exclude']) && + $dep['exclude'] == $dep['min']) { + $s['rel'] = 'gt'; + } else { + $s['rel'] = 'ge'; + } + $s['version'] = $dep['min']; + $s['optional'] = $optional; + if ($dtype != 'php') { + $s['name'] = $dep['name']; + } + } elseif (isset($dep['max'])) { + if (isset($dep['exclude']) && + $dep['exclude'] == $dep['max']) { + $s['rel'] = 'lt'; + } else { + $s['rel'] = 'le'; + } + $s['version'] = $dep['max']; + $s['optional'] = $optional; + if ($dtype != 'php') { + $s['name'] = $dep['name']; + } + } + } + $ret[] = $s; + } + } + } + if (count($ret)) { + return $ret; + } + } + return false; + } + + /** + * @return php|extsrc|extbin|zendextsrc|zendextbin|bundle|false + */ + function getPackageType() + { + if (isset($this->_packageInfo['phprelease'])) { + return 'php'; + } + if (isset($this->_packageInfo['extsrcrelease'])) { + return 'extsrc'; + } + if (isset($this->_packageInfo['extbinrelease'])) { + return 'extbin'; + } + if (isset($this->_packageInfo['zendextsrcrelease'])) { + return 'zendextsrc'; + } + if (isset($this->_packageInfo['zendextbinrelease'])) { + return 'zendextbin'; + } + if (isset($this->_packageInfo['bundle'])) { + return 'bundle'; + } + return false; + } + + /** + * @return array|false + */ + function getReleases() + { + $type = $this->getPackageType(); + if ($type != 'bundle') { + $type .= 'release'; + } + if ($this->getPackageType() && isset($this->_packageInfo[$type])) { + return $this->_packageInfo[$type]; + } + return false; + } + + /** + * @return array + */ + function getChangelog() + { + if (isset($this->_packageInfo['changelog'])) { + return $this->_packageInfo['changelog']; + } + return false; + } + + function hasDeps() + { + return isset($this->_packageInfo['dependencies']); + } + + function getPackagexmlVersion() + { + if (isset($this->_packageInfo['zendextsrcrelease'])) { + return '2.1'; + } + if (isset($this->_packageInfo['zendextbinrelease'])) { + return '2.1'; + } + return '2.0'; + } + + /** + * @return array|false + */ + function getSourcePackage() + { + if (isset($this->_packageInfo['extbinrelease']) || + isset($this->_packageInfo['zendextbinrelease'])) { + return array('channel' => $this->_packageInfo['srcchannel'], + 'package' => $this->_packageInfo['srcpackage']); + } + return false; + } + + function getBundledPackages() + { + if (isset($this->_packageInfo['bundle'])) { + return $this->_packageInfo['contents']['bundledpackage']; + } + return false; + } + + function getLastModified() + { + if (isset($this->_packageInfo['_lastmodified'])) { + return $this->_packageInfo['_lastmodified']; + } + return false; + } + + /** + * Get the contents of a file listed within the package.xml + * @param string + * @return string + */ + function getFileContents($file) + { + if ($this->_archiveFile == $this->_packageFile) { // unpacked + $dir = dirname($this->_packageFile); + $file = $dir . DIRECTORY_SEPARATOR . $file; + $file = str_replace(array('/', '\\'), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file); + if (file_exists($file) && is_readable($file)) { + return implode('', file($file)); + } + } else { // tgz + $tar = &new Archive_Tar($this->_archiveFile); + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + if ($file != 'package.xml' && $file != 'package2.xml') { + $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file; + } + $file = $tar->extractInString($file); + $tar->popErrorHandling(); + if (PEAR::isError($file)) { + return PEAR::raiseError("Cannot locate file '$file' in archive"); + } + return $file; + } + } + + function &getRW() + { + if (!class_exists('PEAR_PackageFile_v2_rw')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v2/rw.php'; + } + $a = new PEAR_PackageFile_v2_rw; + foreach (get_object_vars($this) as $name => $unused) { + if (!isset($this->$name)) { + continue; + } + if ($name == '_config' || $name == '_logger'|| $name == '_registry' || + $name == '_stack') { + $a->$name = &$this->$name; + } else { + $a->$name = $this->$name; + } + } + return $a; + } + + function &getDefaultGenerator() + { + if (!class_exists('PEAR_PackageFile_Generator_v2')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/Generator/v2.php'; + } + $a = &new PEAR_PackageFile_Generator_v2($this); + return $a; + } + + function analyzeSourceCode($file, $string = false) + { + if (!isset($this->_v2Validator) || + !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v2/Validator.php'; + } + $this->_v2Validator = new PEAR_PackageFile_v2_Validator; + } + return $this->_v2Validator->analyzeSourceCode($file, $string); + } + + function validate($state = PEAR_VALIDATE_NORMAL) + { + if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) { + return false; + } + if (!isset($this->_v2Validator) || + !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v2/Validator.php'; + } + $this->_v2Validator = new PEAR_PackageFile_v2_Validator; + } + if (isset($this->_packageInfo['xsdversion'])) { + unset($this->_packageInfo['xsdversion']); + } + return $this->_v2Validator->validate($this, $state); + } + + function getTasksNs() + { + if (!isset($this->_tasksNs)) { + if (isset($this->_packageInfo['attribs'])) { + foreach ($this->_packageInfo['attribs'] as $name => $value) { + if ($value == 'http://pear.php.net/dtd/tasks-1.0') { + $this->_tasksNs = str_replace('xmlns:', '', $name); + break; + } + } + } + } + return $this->_tasksNs; + } + + /** + * Determine whether a task name is a valid task. Custom tasks may be defined + * using subdirectories by putting a "-" in the name, as in + * + * Note that this method will auto-load the task class file and test for the existence + * of the name with "-" replaced by "_" as in PEAR/Task/mycustom/task.php makes class + * PEAR_Task_mycustom_task + * @param string + * @return boolean + */ + function getTask($task) + { + $this->getTasksNs(); + // transform all '-' to '/' and 'tasks:' to '' so tasks:replace becomes replace + $task = str_replace(array($this->_tasksNs . ':', '-'), array('', ' '), $task); + $taskfile = str_replace(' ', '/', ucwords($task)); + $task = str_replace(array(' ', '/'), '_', ucwords($task)); + if (class_exists("PEAR_Task_$task")) { + return "PEAR_Task_$task"; + } + $fp = @fopen("phar://go-pear.phar/PEAR/Task/$taskfile.php", 'r', true); + if ($fp) { + fclose($fp); + require_once 'phar://go-pear.phar/' . "PEAR/Task/$taskfile.php"; + return "PEAR_Task_$task"; + } + return false; + } + + /** + * Key-friendly array_splice + * @param tagname to splice a value in before + * @param mixed the value to splice in + * @param string the new tag name + */ + function _ksplice($array, $key, $value, $newkey) + { + $offset = array_search($key, array_keys($array)); + $after = array_slice($array, $offset); + $before = array_slice($array, 0, $offset); + $before[$newkey] = $value; + return array_merge($before, $after); + } + + /** + * @param array a list of possible keys, in the order they may occur + * @param mixed contents of the new package.xml tag + * @param string tag name + * @access private + */ + function _insertBefore($array, $keys, $contents, $newkey) + { + foreach ($keys as $key) { + if (isset($array[$key])) { + return $array = $this->_ksplice($array, $key, $contents, $newkey); + } + } + $array[$newkey] = $contents; + return $array; + } + + /** + * @param subsection of {@link $_packageInfo} + * @param array|string tag contents + * @param array format: + *
    +     * array(
    +     *   tagname => array(list of tag names that follow this one),
    +     *   childtagname => array(list of child tag names that follow this one),
    +     * )
    +     * 
    + * + * This allows construction of nested tags + * @access private + */ + function _mergeTag($manip, $contents, $order) + { + if (count($order)) { + foreach ($order as $tag => $curorder) { + if (!isset($manip[$tag])) { + // ensure that the tag is set up + $manip = $this->_insertBefore($manip, $curorder, array(), $tag); + } + if (count($order) > 1) { + $manip[$tag] = $this->_mergeTag($manip[$tag], $contents, array_slice($order, 1)); + return $manip; + } + } + } else { + return $manip; + } + if (is_array($manip[$tag]) && !empty($manip[$tag]) && isset($manip[$tag][0])) { + $manip[$tag][] = $contents; + } else { + if (!count($manip[$tag])) { + $manip[$tag] = $contents; + } else { + $manip[$tag] = array($manip[$tag]); + $manip[$tag][] = $contents; + } + } + return $manip; + } +} +?> + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Validator.php 277885 2009-03-27 19:29:31Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a8 + */ +/** + * Private validation class used by PEAR_PackageFile_v2 - do not use directly, its + * sole purpose is to split up the PEAR/PackageFile/v2.php file to make it smaller + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a8 + * @access private + */ +class PEAR_PackageFile_v2_Validator +{ + /** + * @var array + */ + var $_packageInfo; + /** + * @var PEAR_PackageFile_v2 + */ + var $_pf; + /** + * @var PEAR_ErrorStack + */ + var $_stack; + /** + * @var int + */ + var $_isValid = 0; + /** + * @var int + */ + var $_filesValid = 0; + /** + * @var int + */ + var $_curState = 0; + /** + * @param PEAR_PackageFile_v2 + * @param int + */ + function validate(&$pf, $state = PEAR_VALIDATE_NORMAL) + { + $this->_pf = &$pf; + $this->_curState = $state; + $this->_packageInfo = $this->_pf->getArray(); + $this->_isValid = $this->_pf->_isValid; + $this->_filesValid = $this->_pf->_filesValid; + $this->_stack = &$pf->_stack; + $this->_stack->getErrors(true); + if (($this->_isValid & $state) == $state) { + return true; + } + if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) { + return false; + } + if (!isset($this->_packageInfo['attribs']['version']) || + ($this->_packageInfo['attribs']['version'] != '2.0' && + $this->_packageInfo['attribs']['version'] != '2.1') + ) { + $this->_noPackageVersion(); + } + $structure = + array( + 'name', + 'channel|uri', + '*extends', // can't be multiple, but this works fine + 'summary', + 'description', + '+lead', // these all need content checks + '*developer', + '*contributor', + '*helper', + 'date', + '*time', + 'version', + 'stability', + 'license->?uri->?filesource', + 'notes', + 'contents', //special validation needed + '*compatible', + 'dependencies', //special validation needed + '*usesrole', + '*usestask', // reserve these for 1.4.0a1 to implement + // this will allow a package.xml to gracefully say it + // needs a certain package installed in order to implement a role or task + '*providesextension', + '*srcpackage|*srcuri', + '+phprelease|+extsrcrelease|+extbinrelease|' . + '+zendextsrcrelease|+zendextbinrelease|bundle', //special validation needed + '*changelog', + ); + $test = $this->_packageInfo; + if (isset($test['dependencies']) && + isset($test['dependencies']['required']) && + isset($test['dependencies']['required']['pearinstaller']) && + isset($test['dependencies']['required']['pearinstaller']['min']) && + version_compare('1.9.0', + $test['dependencies']['required']['pearinstaller']['min'], '<') + ) { + $this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']); + return false; + } + // ignore post-installation array fields + if (array_key_exists('filelist', $test)) { + unset($test['filelist']); + } + if (array_key_exists('_lastmodified', $test)) { + unset($test['_lastmodified']); + } + if (array_key_exists('#binarypackage', $test)) { + unset($test['#binarypackage']); + } + if (array_key_exists('old', $test)) { + unset($test['old']); + } + if (array_key_exists('_lastversion', $test)) { + unset($test['_lastversion']); + } + if (!$this->_stupidSchemaValidate($structure, $test, '')) { + return false; + } + if (empty($this->_packageInfo['name'])) { + $this->_tagCannotBeEmpty('name'); + } + $test = isset($this->_packageInfo['uri']) ? 'uri' :'channel'; + if (empty($this->_packageInfo[$test])) { + $this->_tagCannotBeEmpty($test); + } + if (is_array($this->_packageInfo['license']) && + (!isset($this->_packageInfo['license']['_content']) || + empty($this->_packageInfo['license']['_content']))) { + $this->_tagCannotBeEmpty('license'); + } elseif (empty($this->_packageInfo['license'])) { + $this->_tagCannotBeEmpty('license'); + } + if (empty($this->_packageInfo['summary'])) { + $this->_tagCannotBeEmpty('summary'); + } + if (empty($this->_packageInfo['description'])) { + $this->_tagCannotBeEmpty('description'); + } + if (empty($this->_packageInfo['date'])) { + $this->_tagCannotBeEmpty('date'); + } + if (empty($this->_packageInfo['notes'])) { + $this->_tagCannotBeEmpty('notes'); + } + if (isset($this->_packageInfo['time']) && empty($this->_packageInfo['time'])) { + $this->_tagCannotBeEmpty('time'); + } + if (isset($this->_packageInfo['dependencies'])) { + $this->_validateDependencies(); + } + if (isset($this->_packageInfo['compatible'])) { + $this->_validateCompatible(); + } + if (!isset($this->_packageInfo['bundle'])) { + if (empty($this->_packageInfo['contents'])) { + $this->_tagCannotBeEmpty('contents'); + } + if (!isset($this->_packageInfo['contents']['dir'])) { + $this->_filelistMustContainDir('contents'); + return false; + } + if (isset($this->_packageInfo['contents']['file'])) { + $this->_filelistCannotContainFile('contents'); + return false; + } + } + $this->_validateMaintainers(); + $this->_validateStabilityVersion(); + $fail = false; + if (array_key_exists('usesrole', $this->_packageInfo)) { + $roles = $this->_packageInfo['usesrole']; + if (!is_array($roles) || !isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if (!isset($role['role'])) { + $this->_usesroletaskMustHaveRoleTask('usesrole', 'role'); + $fail = true; + } else { + if (!isset($role['channel'])) { + if (!isset($role['uri'])) { + $this->_usesroletaskMustHaveChannelOrUri($role['role'], 'usesrole'); + $fail = true; + } + } elseif (!isset($role['package'])) { + $this->_usesroletaskMustHavePackage($role['role'], 'usesrole'); + $fail = true; + } + } + } + } + if (array_key_exists('usestask', $this->_packageInfo)) { + $roles = $this->_packageInfo['usestask']; + if (!is_array($roles) || !isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if (!isset($role['task'])) { + $this->_usesroletaskMustHaveRoleTask('usestask', 'task'); + $fail = true; + } else { + if (!isset($role['channel'])) { + if (!isset($role['uri'])) { + $this->_usesroletaskMustHaveChannelOrUri($role['task'], 'usestask'); + $fail = true; + } + } elseif (!isset($role['package'])) { + $this->_usesroletaskMustHavePackage($role['task'], 'usestask'); + $fail = true; + } + } + } + } + + if ($fail) { + return false; + } + + $list = $this->_packageInfo['contents']; + if (isset($list['dir']) && is_array($list['dir']) && isset($list['dir'][0])) { + $this->_multipleToplevelDirNotAllowed(); + return $this->_isValid = 0; + } + + $this->_validateFilelist(); + $this->_validateRelease(); + if (!$this->_stack->hasErrors()) { + $chan = $this->_pf->_registry->getChannel($this->_pf->getChannel(), true); + if (PEAR::isError($chan)) { + $this->_unknownChannel($this->_pf->getChannel()); + } else { + $valpack = $chan->getValidationPackage(); + // for channel validator packages, always use the default PEAR validator. + // otherwise, they can't be installed or packaged + $validator = $chan->getValidationObject($this->_pf->getPackage()); + if (!$validator) { + $this->_stack->push(__FUNCTION__, 'error', + array_merge( + array('channel' => $chan->getName(), + 'package' => $this->_pf->getPackage()), + $valpack + ), + 'package "%channel%/%package%" cannot be properly validated without ' . + 'validation package "%channel%/%name%-%version%"'); + return $this->_isValid = 0; + } + $validator->setPackageFile($this->_pf); + $validator->validate($state); + $failures = $validator->getFailures(); + foreach ($failures['errors'] as $error) { + $this->_stack->push(__FUNCTION__, 'error', $error, + 'Channel validator error: field "%field%" - %reason%'); + } + foreach ($failures['warnings'] as $warning) { + $this->_stack->push(__FUNCTION__, 'warning', $warning, + 'Channel validator warning: field "%field%" - %reason%'); + } + } + } + + $this->_pf->_isValid = $this->_isValid = !$this->_stack->hasErrors('error'); + if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$this->_filesValid) { + if ($this->_pf->getPackageType() == 'bundle') { + if ($this->_analyzeBundledPackages()) { + $this->_filesValid = $this->_pf->_filesValid = true; + } else { + $this->_pf->_isValid = $this->_isValid = 0; + } + } else { + if (!$this->_analyzePhpFiles()) { + $this->_pf->_isValid = $this->_isValid = 0; + } else { + $this->_filesValid = $this->_pf->_filesValid = true; + } + } + } + + if ($this->_isValid) { + return $this->_pf->_isValid = $this->_isValid = $state; + } + + return $this->_pf->_isValid = $this->_isValid = 0; + } + + function _stupidSchemaValidate($structure, $xml, $root) + { + if (!is_array($xml)) { + $xml = array(); + } + $keys = array_keys($xml); + reset($keys); + $key = current($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + $unfoundtags = $optionaltags = array(); + $ret = true; + $mismatch = false; + foreach ($structure as $struc) { + if ($key) { + $tag = $xml[$key]; + } + $test = $this->_processStructure($struc); + if (isset($test['choices'])) { + $loose = true; + foreach ($test['choices'] as $choice) { + if ($key == $choice['tag']) { + $key = next($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + $unfoundtags = $optionaltags = array(); + $mismatch = false; + if ($key && $key != $choice['tag'] && isset($choice['multiple'])) { + $unfoundtags[] = $choice['tag']; + $optionaltags[] = $choice['tag']; + if ($key) { + $mismatch = true; + } + } + $ret &= $this->_processAttribs($choice, $tag, $root); + continue 2; + } else { + $unfoundtags[] = $choice['tag']; + $mismatch = true; + } + if (!isset($choice['multiple']) || $choice['multiple'] != '*') { + $loose = false; + } else { + $optionaltags[] = $choice['tag']; + } + } + if (!$loose) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } + } else { + if ($key != $test['tag']) { + if (isset($test['multiple']) && $test['multiple'] != '*') { + $unfoundtags[] = $test['tag']; + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } else { + if ($key) { + $mismatch = true; + } + $unfoundtags[] = $test['tag']; + $optionaltags[] = $test['tag']; + } + if (!isset($test['multiple'])) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } + continue; + } else { + $unfoundtags = $optionaltags = array(); + $mismatch = false; + } + $key = next($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + if ($key && $key != $test['tag'] && isset($test['multiple'])) { + $unfoundtags[] = $test['tag']; + $optionaltags[] = $test['tag']; + $mismatch = true; + } + $ret &= $this->_processAttribs($test, $tag, $root); + continue; + } + } + if (!$mismatch && count($optionaltags)) { + // don't error out on any optional tags + $unfoundtags = array_diff($unfoundtags, $optionaltags); + } + if (count($unfoundtags)) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + } elseif ($key) { + // unknown tags + $this->_invalidTagOrder('*no tags allowed here*', $key, $root); + while ($key = next($keys)) { + $this->_invalidTagOrder('*no tags allowed here*', $key, $root); + } + } + return $ret; + } + + function _processAttribs($choice, $tag, $context) + { + if (isset($choice['attribs'])) { + if (!is_array($tag)) { + $tag = array($tag); + } + $tags = $tag; + if (!isset($tags[0])) { + $tags = array($tags); + } + $ret = true; + foreach ($tags as $i => $tag) { + if (!is_array($tag) || !isset($tag['attribs'])) { + foreach ($choice['attribs'] as $attrib) { + if ($attrib{0} != '?') { + $ret &= $this->_tagHasNoAttribs($choice['tag'], + $context); + continue 2; + } + } + } + foreach ($choice['attribs'] as $attrib) { + if ($attrib{0} != '?') { + if (!isset($tag['attribs'][$attrib])) { + $ret &= $this->_tagMissingAttribute($choice['tag'], + $attrib, $context); + } + } + } + } + return $ret; + } + return true; + } + + function _processStructure($key) + { + $ret = array(); + if (count($pieces = explode('|', $key)) > 1) { + $ret['choices'] = array(); + foreach ($pieces as $piece) { + $ret['choices'][] = $this->_processStructure($piece); + } + return $ret; + } + $multi = $key{0}; + if ($multi == '+' || $multi == '*') { + $ret['multiple'] = $key{0}; + $key = substr($key, 1); + } + if (count($attrs = explode('->', $key)) > 1) { + $ret['tag'] = array_shift($attrs); + $ret['attribs'] = $attrs; + } else { + $ret['tag'] = $key; + } + return $ret; + } + + function _validateStabilityVersion() + { + $structure = array('release', 'api'); + $a = $this->_stupidSchemaValidate($structure, $this->_packageInfo['version'], ''); + $a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], ''); + if ($a) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $this->_packageInfo['version']['release'])) { + $this->_invalidVersion('release', $this->_packageInfo['version']['release']); + } + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $this->_packageInfo['version']['api'])) { + $this->_invalidVersion('api', $this->_packageInfo['version']['api']); + } + if (!in_array($this->_packageInfo['stability']['release'], + array('snapshot', 'devel', 'alpha', 'beta', 'stable'))) { + $this->_invalidState('release', $this->_packageInfo['stability']['release']); + } + if (!in_array($this->_packageInfo['stability']['api'], + array('devel', 'alpha', 'beta', 'stable'))) { + $this->_invalidState('api', $this->_packageInfo['stability']['api']); + } + } + } + + function _validateMaintainers() + { + $structure = + array( + 'name', + 'user', + 'email', + 'active', + ); + foreach (array('lead', 'developer', 'contributor', 'helper') as $type) { + if (!isset($this->_packageInfo[$type])) { + continue; + } + if (isset($this->_packageInfo[$type][0])) { + foreach ($this->_packageInfo[$type] as $lead) { + $this->_stupidSchemaValidate($structure, $lead, '<' . $type . '>'); + } + } else { + $this->_stupidSchemaValidate($structure, $this->_packageInfo[$type], + '<' . $type . '>'); + } + } + } + + function _validatePhpDep($dep, $installcondition = false) + { + $structure = array( + 'min', + '*max', + '*exclude', + ); + $type = $installcondition ? '' : ''; + $this->_stupidSchemaValidate($structure, $dep, $type); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $dep['min'])) { + $this->_invalidVersion($type . '', $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $dep['max'])) { + $this->_invalidVersion($type . '', $dep['max']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match( + '/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $exclude)) { + $this->_invalidVersion($type . '', $exclude); + } + } + } + } + + function _validatePearinstallerDep($dep) + { + $structure = array( + 'min', + '*max', + '*recommended', + '*exclude', + ); + $this->_stupidSchemaValidate($structure, $dep, ''); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion('', + $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['max'])) { + $this->_invalidVersion('', + $dep['max']); + } + } + if (isset($dep['recommended'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['recommended'])) { + $this->_invalidVersion('', + $dep['recommended']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $exclude)) { + $this->_invalidVersion('', + $exclude); + } + } + } + } + + function _validatePackageDep($dep, $group, $type = '') + { + if (isset($dep['uri'])) { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + 'uri', + 'conflicts', + '*providesextension', + ); + } else { + $structure = array( + 'name', + 'uri', + '*providesextension', + ); + } + } else { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + 'channel', + '*min', + '*max', + '*exclude', + 'conflicts', + '*providesextension', + ); + } else { + $structure = array( + 'name', + 'channel', + '*min', + '*max', + '*recommended', + '*exclude', + '*nodefault', + '*providesextension', + ); + } + } + if (isset($dep['name'])) { + $type .= '' . $dep['name'] . ''; + } + $this->_stupidSchemaValidate($structure, $dep, '' . $group . $type); + if (isset($dep['uri']) && (isset($dep['min']) || isset($dep['max']) || + isset($dep['recommended']) || isset($dep['exclude']))) { + $this->_uriDepsCannotHaveVersioning('' . $group . $type); + } + if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') { + $this->_DepchannelCannotBeUri('' . $group . $type); + } + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion('' . $group . $type . '', $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['max'])) { + $this->_invalidVersion('' . $group . $type . '', $dep['max']); + } + } + if (isset($dep['recommended'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['recommended'])) { + $this->_invalidVersion('' . $group . $type . '', + $dep['recommended']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $exclude)) { + $this->_invalidVersion('' . $group . $type . '', + $exclude); + } + } + } + } + + function _validateSubpackageDep($dep, $group) + { + $this->_validatePackageDep($dep, $group, ''); + if (isset($dep['providesextension'])) { + $this->_subpackageCannotProvideExtension(isset($dep['name']) ? $dep['name'] : ''); + } + if (isset($dep['conflicts'])) { + $this->_subpackagesCannotConflict(isset($dep['name']) ? $dep['name'] : ''); + } + } + + function _validateExtensionDep($dep, $group = false, $installcondition = false) + { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + '*min', + '*max', + '*exclude', + 'conflicts', + ); + } else { + $structure = array( + 'name', + '*min', + '*max', + '*recommended', + '*exclude', + ); + } + if ($installcondition) { + $type = ''; + } else { + $type = '' . $group . ''; + } + if (isset($dep['name'])) { + $type .= '' . $dep['name'] . ''; + } + $this->_stupidSchemaValidate($structure, $dep, $type); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '' : ''; + if ($this->_stupidSchemaValidate($structure, $dep, $type)) { + if ($dep['name'] == '*') { + if (array_key_exists('conflicts', $dep)) { + $this->_cannotConflictWithAllOs($type); + } + } + } + } + + function _validateArchDep($dep, $installcondition = false) + { + $structure = array( + 'pattern', + '*conflicts', + ); + $type = $installcondition ? '' : ''; + $this->_stupidSchemaValidate($structure, $dep, $type); + } + + function _validateInstallConditions($cond, $release) + { + $structure = array( + '*php', + '*extension', + '*os', + '*arch', + ); + if (!$this->_stupidSchemaValidate($structure, + $cond, $release)) { + return false; + } + foreach (array('php', 'extension', 'os', 'arch') as $type) { + if (isset($cond[$type])) { + $iter = $cond[$type]; + if (!is_array($iter) || !isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type == 'extension') { + $this->{"_validate{$type}Dep"}($package, false, true); + } else { + $this->{"_validate{$type}Dep"}($package, true); + } + } + } + } + } + + function _validateDependencies() + { + $structure = array( + 'required', + '*optional', + '*group->name->hint' + ); + if (!$this->_stupidSchemaValidate($structure, + $this->_packageInfo['dependencies'], '')) { + return false; + } + foreach (array('required', 'optional') as $simpledep) { + if (isset($this->_packageInfo['dependencies'][$simpledep])) { + if ($simpledep == 'optional') { + $structure = array( + '*package', + '*subpackage', + '*extension', + ); + } else { + $structure = array( + 'php', + 'pearinstaller', + '*package', + '*subpackage', + '*extension', + '*os', + '*arch', + ); + } + if ($this->_stupidSchemaValidate($structure, + $this->_packageInfo['dependencies'][$simpledep], + "<$simpledep>")) { + foreach (array('package', 'subpackage', 'extension') as $type) { + if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) { + $iter = $this->_packageInfo['dependencies'][$simpledep][$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type != 'extension') { + if (isset($package['uri'])) { + if (isset($package['channel'])) { + $this->_UrlOrChannel($type, + $package['name']); + } + } else { + if (!isset($package['channel'])) { + $this->_NoChannel($type, $package['name']); + } + } + } + $this->{"_validate{$type}Dep"}($package, "<$simpledep>"); + } + } + } + if ($simpledep == 'optional') { + continue; + } + foreach (array('php', 'pearinstaller', 'os', 'arch') as $type) { + if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) { + $iter = $this->_packageInfo['dependencies'][$simpledep][$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + $this->{"_validate{$type}Dep"}($package); + } + } + } + } + } + } + if (isset($this->_packageInfo['dependencies']['group'])) { + $groups = $this->_packageInfo['dependencies']['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + $structure = array( + '*package', + '*subpackage', + '*extension', + ); + foreach ($groups as $group) { + if ($this->_stupidSchemaValidate($structure, $group, '')) { + if (!PEAR_Validate::validGroupName($group['attribs']['name'])) { + $this->_invalidDepGroupName($group['attribs']['name']); + } + foreach (array('package', 'subpackage', 'extension') as $type) { + if (isset($group[$type])) { + $iter = $group[$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type != 'extension') { + if (isset($package['uri'])) { + if (isset($package['channel'])) { + $this->_UrlOrChannelGroup($type, + $package['name'], + $group['name']); + } + } else { + if (!isset($package['channel'])) { + $this->_NoChannelGroup($type, + $package['name'], + $group['name']); + } + } + } + $this->{"_validate{$type}Dep"}($package, ''); + } + } + } + } + } + } + } + + function _validateCompatible() + { + $compat = $this->_packageInfo['compatible']; + if (!isset($compat[0])) { + $compat = array($compat); + } + $required = array('name', 'channel', 'min', 'max', '*exclude'); + foreach ($compat as $package) { + $type = ''; + if (is_array($package) && array_key_exists('name', $package)) { + $type .= '' . $package['name'] . ''; + } + $this->_stupidSchemaValidate($required, $package, $type); + if (is_array($package) && array_key_exists('min', $package)) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $package['min'])) { + $this->_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_NoBundledPackages(); + } + if (!is_array($list['bundledpackage']) || !isset($list['bundledpackage'][0])) { + return $this->_AtLeast2BundledPackages(); + } + foreach ($list['bundledpackage'] as $package) { + if (!is_string($package)) { + $this->_bundledPackagesMustBeFilename(); + } + } + } + + function _validateFilelist($list = false, $allowignore = false, $dirs = '') + { + $iscontents = false; + if (!$list) { + $iscontents = true; + $list = $this->_packageInfo['contents']; + if (isset($this->_packageInfo['bundle'])) { + return $this->_validateBundle($list); + } + } + if ($allowignore) { + $struc = array( + '*install->name->as', + '*ignore->name' + ); + } else { + $struc = array( + '*dir->name->?baseinstalldir', + '*file->name->role->?baseinstalldir->?md5sum' + ); + if (isset($list['dir']) && isset($list['file'])) { + // stave off validation errors without requiring a set order. + $_old = $list; + if (isset($list['attribs'])) { + $list = array('attribs' => $_old['attribs']); + } + $list['dir'] = $_old['dir']; + $list['file'] = $_old['file']; + } + } + if (!isset($list['attribs']) || !isset($list['attribs']['name'])) { + $unknown = $allowignore ? '' : ''; + $dirname = $iscontents ? '' : $unknown; + } else { + $dirname = ''; + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $list['attribs']['name']))) { + // file contains .. parent directory or . cur directory + $this->_invalidDirName($list['attribs']['name']); + } + } + $res = $this->_stupidSchemaValidate($struc, $list, $dirname); + if ($allowignore && $res) { + $ignored_or_installed = array(); + $this->_pf->getFilelist(); + $fcontents = $this->_pf->getContents(); + $filelist = array(); + if (!isset($fcontents['dir']['file'][0])) { + $fcontents['dir']['file'] = array($fcontents['dir']['file']); + } + foreach ($fcontents['dir']['file'] as $file) { + $filelist[$file['attribs']['name']] = true; + } + if (isset($list['install'])) { + if (!isset($list['install'][0])) { + $list['install'] = array($list['install']); + } + foreach ($list['install'] as $file) { + if (!isset($filelist[$file['attribs']['name']])) { + $this->_notInContents($file['attribs']['name'], 'install'); + continue; + } + if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) { + $this->_multipleInstallAs($file['attribs']['name']); + } + if (!isset($ignored_or_installed[$file['attribs']['name']])) { + $ignored_or_installed[$file['attribs']['name']] = array(); + } + $ignored_or_installed[$file['attribs']['name']][] = 1; + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $file['attribs']['as']))) { + // file contains .. parent directory or . cur directory references + $this->_invalidFileInstallAs($file['attribs']['name'], + $file['attribs']['as']); + } + } + } + if (isset($list['ignore'])) { + if (!isset($list['ignore'][0])) { + $list['ignore'] = array($list['ignore']); + } + foreach ($list['ignore'] as $file) { + if (!isset($filelist[$file['attribs']['name']])) { + $this->_notInContents($file['attribs']['name'], 'ignore'); + continue; + } + if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) { + $this->_ignoreAndInstallAs($file['attribs']['name']); + } + } + } + } + if (!$allowignore && isset($list['file'])) { + if (is_string($list['file'])) { + $this->_oldStyleFileNotAllowed(); + return false; + } + if (!isset($list['file'][0])) { + // single file + $list['file'] = array($list['file']); + } + foreach ($list['file'] as $i => $file) + { + if (isset($file['attribs']) && isset($file['attribs']['name'])) { + if ($file['attribs']['name']{0} == '.' && + $file['attribs']['name']{1} == '/') { + // name is something like "./doc/whatever.txt" + $this->_invalidFileName($file['attribs']['name'], $dirname); + } + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $file['attribs']['name']))) { + // file contains .. parent directory or . cur directory + $this->_invalidFileName($file['attribs']['name'], $dirname); + } + } + if (isset($file['attribs']) && isset($file['attribs']['role'])) { + if (!$this->_validateRole($file['attribs']['role'])) { + if (isset($this->_packageInfo['usesrole'])) { + $roles = $this->_packageInfo['usesrole']; + if (!isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if ($role['role'] = $file['attribs']['role']) { + $msg = 'This package contains role "%role%" and requires ' . + 'package "%package%" to be used'; + if (isset($role['uri'])) { + $params = array('role' => $role['role'], + 'package' => $role['uri']); + } else { + $params = array('role' => $role['role'], + 'package' => $this->_pf->_registry-> + parsedPackageNameToString(array('package' => + $role['package'], 'channel' => $role['channel']), + true)); + } + $this->_stack->push('_mustInstallRole', 'error', $params, $msg); + } + } + } + $this->_invalidFileRole($file['attribs']['name'], + $dirname, $file['attribs']['role']); + } + } + if (!isset($file['attribs'])) { + continue; + } + $save = $file['attribs']; + if ($dirs) { + $save['name'] = $dirs . '/' . $save['name']; + } + unset($file['attribs']); + if (count($file) && $this->_curState != PEAR_VALIDATE_DOWNLOADING) { // has tasks + foreach ($file as $task => $value) { + if ($tagClass = $this->_pf->getTask($task)) { + if (!is_array($value) || !isset($value[0])) { + $value = array($value); + } + foreach ($value as $v) { + $ret = call_user_func(array($tagClass, 'validateXml'), + $this->_pf, $v, $this->_pf->_config, $save); + if (is_array($ret)) { + $this->_invalidTask($task, $ret, isset($save['name']) ? + $save['name'] : ''); + } + } + } else { + if (isset($this->_packageInfo['usestask'])) { + $roles = $this->_packageInfo['usestask']; + if (!isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if ($role['task'] = $task) { + $msg = 'This package contains task "%task%" and requires ' . + 'package "%package%" to be used'; + if (isset($role['uri'])) { + $params = array('task' => $role['task'], + 'package' => $role['uri']); + } else { + $params = array('task' => $role['task'], + 'package' => $this->_pf->_registry-> + parsedPackageNameToString(array('package' => + $role['package'], 'channel' => $role['channel']), + true)); + } + $this->_stack->push('_mustInstallTask', 'error', + $params, $msg); + } + } + } + $this->_unknownTask($task, $save['name']); + } + } + } + } + } + if (isset($list['ignore'])) { + if (!$allowignore) { + $this->_ignoreNotAllowed('ignore'); + } + } + if (isset($list['install'])) { + if (!$allowignore) { + $this->_ignoreNotAllowed('install'); + } + } + if (isset($list['file'])) { + if ($allowignore) { + $this->_fileNotAllowed('file'); + } + } + if (isset($list['dir'])) { + if ($allowignore) { + $this->_fileNotAllowed('dir'); + } else { + if (!isset($list['dir'][0])) { + $list['dir'] = array($list['dir']); + } + foreach ($list['dir'] as $dir) { + if (isset($dir['attribs']) && isset($dir['attribs']['name'])) { + if ($dir['attribs']['name'] == '/' || + !isset($this->_packageInfo['contents']['dir']['dir'])) { + // always use nothing if the filelist has already been flattened + $newdirs = ''; + } elseif ($dirs == '') { + $newdirs = $dir['attribs']['name']; + } else { + $newdirs = $dirs . '/' . $dir['attribs']['name']; + } + } else { + $newdirs = $dirs; + } + $this->_validateFilelist($dir, $allowignore, $newdirs); + } + } + } + } + + function _validateRelease() + { + if (isset($this->_packageInfo['phprelease'])) { + $release = 'phprelease'; + if (isset($this->_packageInfo['providesextension'])) { + $this->_cannotProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo['phprelease']; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, ''); + } + } + foreach (array('', 'zend') as $prefix) { + $releasetype = $prefix . 'extsrcrelease'; + if (isset($this->_packageInfo[$releasetype])) { + $release = $releasetype; + if (!isset($this->_packageInfo['providesextension'])) { + $this->_mustProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo[$releasetype]; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*configureoption->name->prompt->?default', + '*binarypackage', + '*filelist', + ), $rel, '<' . $releasetype . '>'); + if (isset($rel['binarypackage'])) { + if (!is_array($rel['binarypackage']) || !isset($rel['binarypackage'][0])) { + $rel['binarypackage'] = array($rel['binarypackage']); + } + foreach ($rel['binarypackage'] as $bin) { + if (!is_string($bin)) { + $this->_binaryPackageMustBePackagename(); + } + } + } + } + } + $releasetype = 'extbinrelease'; + if (isset($this->_packageInfo[$releasetype])) { + $release = $releasetype; + if (!isset($this->_packageInfo['providesextension'])) { + $this->_mustProvideExtension($release); + } + if (isset($this->_packageInfo['channel']) && + !isset($this->_packageInfo['srcpackage'])) { + $this->_mustSrcPackage($release); + } + if (isset($this->_packageInfo['uri']) && !isset($this->_packageInfo['srcuri'])) { + $this->_mustSrcuri($release); + } + $releases = $this->_packageInfo[$releasetype]; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, '<' . $releasetype . '>'); + } + } + } + if (isset($this->_packageInfo['bundle'])) { + $release = 'bundle'; + if (isset($this->_packageInfo['providesextension'])) { + $this->_cannotProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo['bundle']; + if (!is_array($releases) || !isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, ''); + } + } + foreach ($releases as $rel) { + if (is_array($rel) && array_key_exists('installconditions', $rel)) { + $this->_validateInstallConditions($rel['installconditions'], + "<$release>"); + } + if (is_array($rel) && array_key_exists('filelist', $rel)) { + if ($rel['filelist']) { + + $this->_validateFilelist($rel['filelist'], true); + } + } + } + } + + /** + * This is here to allow role extension through plugins + * @param string + */ + function _validateRole($role) + { + return in_array($role, PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())); + } + + function _pearVersionTooLow($version) + { + $this->_stack->push(__FUNCTION__, 'error', + array('version' => $version), + 'This package.xml requires PEAR version %version% to parse properly, we are ' . + 'version 1.9.0'); + } + + function _invalidTagOrder($oktags, $actual, $root) + { + $this->_stack->push(__FUNCTION__, 'error', + array('oktags' => $oktags, 'actual' => $actual, 'root' => $root), + 'Invalid tag order in %root%, found <%actual%> expected one of "%oktags%"'); + } + + function _ignoreNotAllowed($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '<%type%> is not allowed inside global , only inside ' . + '//, use and only'); + } + + function _fileNotAllowed($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '<%type%> is not allowed inside release , only inside ' . + ', use and only'); + } + + function _oldStyleFileNotAllowed() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Old-style name is not allowed. Use' . + ''); + } + + function _tagMissingAttribute($tag, $attr, $context) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, + 'attribute' => $attr, 'context' => $context), + 'tag <%tag%> in context "%context%" has no attribute "%attribute%"'); + } + + function _tagHasNoAttribs($tag, $context) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, + 'context' => $context), + 'tag <%tag%> has no attributes in context "%context%"'); + } + + function _invalidInternalStructure() + { + $this->_stack->push(__FUNCTION__, 'exception', array(), + 'internal array was not generated by compatible parser, or extreme parser error, cannot continue'); + } + + function _invalidFileRole($file, $dir, $role) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file, 'dir' => $dir, 'role' => $role, + 'roles' => PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())), + 'File "%file%" in directory "%dir%" has invalid role "%role%", should be one of %roles%'); + } + + function _invalidFileName($file, $dir) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file), + 'File "%file%" in directory "%dir%" cannot begin with "./" or contain ".."'); + } + + function _invalidFileInstallAs($file, $as) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file, 'as' => $as), + 'File "%file%" cannot contain "./" or contain ".."'); + } + + function _invalidDirName($dir) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'dir' => $file), + 'Directory "%dir%" cannot begin with "./" or contain ".."'); + } + + function _filelistCannotContainFile($filelist) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist), + '<%tag%> can only contain , contains . Use ' . + ' as the first dir element'); + } + + function _filelistMustContainDir($filelist) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist), + '<%tag%> must contain . Use as the ' . + 'first dir element'); + } + + function _tagCannotBeEmpty($tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag), + '<%tag%> cannot be empty (<%tag%/>)'); + } + + function _UrlOrChannel($type, $name) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name), + 'Required dependency <%type%> "%name%" can have either url OR ' . + 'channel attributes, and not both'); + } + + function _NoChannel($type, $name) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name), + 'Required dependency <%type%> "%name%" must have either url OR ' . + 'channel attributes'); + } + + function _UrlOrChannelGroup($type, $name, $group) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name, 'group' => $group), + 'Group "%group%" dependency <%type%> "%name%" can have either url OR ' . + 'channel attributes, and not both'); + } + + function _NoChannelGroup($type, $name, $group) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name, 'group' => $group), + 'Group "%group%" dependency <%type%> "%name%" must have either url OR ' . + 'channel attributes'); + } + + function _unknownChannel($channel) + { + $this->_stack->push(__FUNCTION__, 'error', array('channel' => $channel), + 'Unknown channel "%channel%"'); + } + + function _noPackageVersion() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'package.xml tag has no version attribute, or version is not 2.0'); + } + + function _NoBundledPackages() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'No tag was found in , required for bundle packages'); + } + + function _AtLeast2BundledPackages() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'At least 2 packages must be bundled in a bundle package'); + } + + function _ChannelOrUri($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Bundled package "%name%" can have either a uri or a channel, not both'); + } + + function _noChildTag($child, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('child' => $child, 'tag' => $tag), + 'Tag <%tag%> is missing child tag <%child%>'); + } + + function _invalidVersion($type, $value) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value), + 'Version type <%type%> is not a valid version (%value%)'); + } + + function _invalidState($type, $value) + { + $states = array('stable', 'beta', 'alpha', 'devel'); + if ($type != 'api') { + $states[] = 'snapshot'; + } + if (strtolower($value) == 'rc') { + $this->_stack->push(__FUNCTION__, 'error', + array('version' => $this->_packageInfo['version']['release']), + 'RC is not a state, it is a version postfix, try %version%RC1, stability beta'); + } + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value, + 'types' => $states), + 'Stability type <%type%> is not a valid stability (%value%), must be one of ' . + '%types%'); + } + + function _invalidTask($task, $ret, $file) + { + switch ($ret[0]) { + case PEAR_TASK_ERROR_MISSING_ATTRIB : + $info = array('attrib' => $ret[1], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> is missing attribute "%attrib%" in file %file%'; + break; + case PEAR_TASK_ERROR_NOATTRIBS : + $info = array('task' => $task, 'file' => $file); + $msg = 'task <%task%> has no attributes in file %file%'; + break; + case PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE : + $info = array('attrib' => $ret[1], 'values' => $ret[3], + 'was' => $ret[2], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> attribute "%attrib%" has the wrong value "%was%" '. + 'in file %file%, expecting one of "%values%"'; + break; + case PEAR_TASK_ERROR_INVALID : + $info = array('reason' => $ret[1], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> in file %file% is invalid because of "%reason%"'; + break; + } + $this->_stack->push(__FUNCTION__, 'error', $info, $msg); + } + + function _unknownTask($task, $file) + { + $this->_stack->push(__FUNCTION__, 'error', array('task' => $task, 'file' => $file), + 'Unknown task "%task%" passed in file '); + } + + function _subpackageCannotProvideExtension($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Subpackage dependency "%name%" cannot use , ' . + 'only package dependencies can use this tag'); + } + + function _subpackagesCannotConflict($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Subpackage dependency "%name%" cannot use , ' . + 'only package dependencies can use this tag'); + } + + function _cannotProvideExtension($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages cannot use , only extbinrelease, extsrcrelease, zendextsrcrelease, and zendextbinrelease can provide a PHP extension'); + } + + function _mustProvideExtension($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages must use to indicate which PHP extension is provided'); + } + + function _cannotHaveSrcpackage($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages cannot specify a source code package, only extension binaries may use the tag'); + } + + function _mustSrcPackage($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '/ packages must specify a source code package with '); + } + + function _mustSrcuri($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '/ packages must specify a source code package with '); + } + + function _uriDepsCannotHaveVersioning($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: dependencies with a tag cannot have any versioning information'); + } + + function _conflictingDepsCannotHaveVersioning($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: conflicting dependencies cannot have versioning info, use to ' . + 'exclude specific versions of a dependency'); + } + + function _DepchannelCannotBeUri($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: channel cannot be __uri, this is a pseudo-channel reserved for uri ' . + 'dependencies only'); + } + + function _bundledPackagesMustBeFilename() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + ' tags must contain only the filename of a package release ' . + 'in the bundle'); + } + + function _binaryPackageMustBePackagename() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + ' tags must contain the name of a package that is ' . + 'a compiled version of this extsrc/zendextsrc package'); + } + + function _fileNotFound($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'File "%file%" in package.xml does not exist'); + } + + function _notInContents($file, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file, 'tag' => $tag), + '<%tag% name="%file%"> is invalid, file is not in '); + } + + function _cannotValidateNoPathSet() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Cannot validate files, no path to package file is set (use setPackageFile())'); + } + + function _usesroletaskMustHaveChannelOrUri($role, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag), + '<%tag%> for role "%role%" must contain either , or and '); + } + + function _usesroletaskMustHavePackage($role, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag), + '<%tag%> for role "%role%" must contain '); + } + + function _usesroletaskMustHaveRoleTask($tag, $type) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'type' => $type), + '<%tag%> must contain <%type%> defining the %type% to be used'); + } + + function _cannotConflictWithAllOs($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag), + '%tag% cannot conflict with all OSes'); + } + + function _invalidDepGroupName($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Invalid dependency group name "%name%"'); + } + + function _multipleToplevelDirNotAllowed() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Multiple top-level tags are not allowed. Enclose them ' . + 'in a '); + } + + function _multipleInstallAs($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Only one tag is allowed for file "%file%"'); + } + + function _ignoreAndInstallAs($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Cannot have both and tags for file "%file%"'); + } + + function _analyzeBundledPackages() + { + if (!$this->_isValid) { + return false; + } + if (!$this->_pf->getPackageType() == 'bundle') { + return false; + } + if (!isset($this->_pf->_packageFile)) { + return false; + } + $dir_prefix = dirname($this->_pf->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') : + array($common, 'log'); + $info = $this->_pf->getContents(); + $info = $info['bundledpackage']; + if (!is_array($info)) { + $info = array($info); + } + $pkg = &new PEAR_PackageFile($this->_pf->_config); + foreach ($info as $package) { + if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $package)) { + $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $package); + $this->_isValid = 0; + continue; + } + call_user_func_array($log, array(1, "Analyzing bundled package $package")); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $pkg->fromAnyFile($dir_prefix . DIRECTORY_SEPARATOR . $package, + PEAR_VALIDATE_NORMAL); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + call_user_func_array($log, array(0, "ERROR: package $package is not a valid " . + 'package')); + $inf = $ret->getUserInfo(); + if (is_array($inf)) { + foreach ($inf as $err) { + call_user_func_array($log, array(1, $err['message'])); + } + } + return false; + } + } + return true; + } + + function _analyzePhpFiles() + { + if (!$this->_isValid) { + return false; + } + if (!isset($this->_pf->_packageFile)) { + $this->_cannotValidateNoPathSet(); + return false; + } + $dir_prefix = dirname($this->_pf->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') : + array(&$common, 'log'); + $info = $this->_pf->getContents(); + if (!$info || !isset($info['dir']['file'])) { + $this->_tagCannotBeEmpty('contents>_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $file); + $this->_isValid = 0; + continue; + } + if (in_array($fa['role'], PEAR_Installer_Role::getPhpRoles()) && $dir_prefix) { + call_user_func_array($log, array(1, "Analyzing $file")); + $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file); + if ($srcinfo) { + $provides = array_merge($provides, $this->_buildProvidesArray($srcinfo)); + } + } + } + $this->_packageName = $pn = $this->_pf->getPackage(); + $pnl = strlen($pn); + foreach ($provides as $key => $what) { + if (isset($what['explicit']) || !$what) { + // skip conformance checks if the provides entry is + // specified in the package.xml file + continue; + } + extract($what); + if ($type == 'class') { + if (!strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn), + 'in %file%: %type% "%name%" not prefixed with package name "%package%"'); + } elseif ($type == 'function') { + if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn), + 'in %file%: %type% "%name%" not prefixed with package name "%package%"'); + } + } + return $this->_isValid; + } + + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @param boolean whether to analyze $file as the file contents + * @return mixed + */ + function analyzeSourceCode($file, $string = false) + { + if (!function_exists("token_get_all")) { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Parser error: token_get_all() function must exist to analyze source code, PHP may have been compiled with --disable-tokenizer'); + return false; + } + + if (!defined('T_DOC_COMMENT')) { + define('T_DOC_COMMENT', T_COMMENT); + } + + if (!defined('T_INTERFACE')) { + define('T_INTERFACE', -1); + } + + if (!defined('T_IMPLEMENTS')) { + define('T_IMPLEMENTS', -1); + } + + if ($string) { + $contents = $file; + } else { + if (!$fp = @fopen($file, "r")) { + return false; + } + fclose($fp); + $contents = file_get_contents($file); + } + + // Silence this function so we can catch PHP Warnings and show our own custom message + $tokens = @token_get_all($contents); + if (isset($php_errormsg)) { + if (isset($this->_stack)) { + $pn = $this->_pf->getPackage(); + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'package' => $pn), + 'in %file%: Could not process file for unkown reasons,' . + ' possibly a PHP parse error in %file% from %package%'); + } + } +/* + for ($i = 0; $i < sizeof($tokens); $i++) { + @list($token, $data) = $tokens[$i]; + if (is_string($token)) { + var_dump($token); + } else { + print token_name($token) . ' '; + var_dump(rtrim($data)); + } + } +*/ + $look_for = 0; + $paren_level = 0; + $bracket_level = 0; + $brace_level = 0; + $lastphpdoc = ''; + $current_class = ''; + $current_interface = ''; + $current_class_level = -1; + $current_function = ''; + $current_function_level = -1; + $declared_classes = array(); + $declared_interfaces = array(); + $declared_functions = array(); + $declared_methods = array(); + $used_classes = array(); + $used_functions = array(); + $extends = array(); + $implements = array(); + $nodeps = array(); + $inquote = false; + $interface = false; + for ($i = 0; $i < sizeof($tokens); $i++) { + if (is_array($tokens[$i])) { + list($token, $data) = $tokens[$i]; + } else { + $token = $tokens[$i]; + $data = ''; + } + + if ($inquote) { + if ($token != '"' && $token != T_END_HEREDOC) { + continue; + } else { + $inquote = false; + continue; + } + } + + switch ($token) { + case T_WHITESPACE : + continue; + case ';': + if ($interface) { + $current_function = ''; + $current_function_level = -1; + } + break; + case '"': + case T_START_HEREDOC: + $inquote = true; + break; + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case '{': $brace_level++; continue 2; + case '}': + $brace_level--; + if ($current_class_level == $brace_level) { + $current_class = ''; + $current_class_level = -1; + } + if ($current_function_level == $brace_level) { + $current_function = ''; + $current_function_level = -1; + } + continue 2; + case '[': $bracket_level++; continue 2; + case ']': $bracket_level--; continue 2; + case '(': $paren_level++; continue 2; + case ')': $paren_level--; continue 2; + case T_INTERFACE: + $interface = true; + case T_CLASS: + if (($current_class_level != -1) || ($current_function_level != -1)) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Parser error: invalid PHP found in file "%file%"'); + } else { + PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"", + PEAR_COMMON_ERROR_INVALIDPHP); + } + + return false; + } + case T_FUNCTION: + case T_NEW: + case T_EXTENDS: + case T_IMPLEMENTS: + $look_for = $token; + continue 2; + case T_STRING: + if (version_compare(zend_version(), '2.0', '<')) { + if (in_array(strtolower($data), + array('public', 'private', 'protected', 'abstract', + 'interface', 'implements', 'throw') + ) + ) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'warning', array( + 'file' => $file), + 'Error, PHP5 token encountered in %file%,' . + ' analysis should be in PHP5'); + } else { + PEAR::raiseError('Error: PHP5 token encountered in ' . $file . + 'packaging should be done in PHP 5'); + return false; + } + } + } + + if ($look_for == T_CLASS) { + $current_class = $data; + $current_class_level = $brace_level; + $declared_classes[] = $current_class; + } elseif ($look_for == T_INTERFACE) { + $current_interface = $data; + $current_class_level = $brace_level; + $declared_interfaces[] = $current_interface; + } elseif ($look_for == T_IMPLEMENTS) { + $implements[$current_class] = $data; + } elseif ($look_for == T_EXTENDS) { + $extends[$current_class] = $data; + } elseif ($look_for == T_FUNCTION) { + if ($current_class) { + $current_function = "$current_class::$data"; + $declared_methods[$current_class][] = $data; + } elseif ($current_interface) { + $current_function = "$current_interface::$data"; + $declared_methods[$current_interface][] = $data; + } else { + $current_function = $data; + $declared_functions[] = $current_function; + } + + $current_function_level = $brace_level; + $m = array(); + } elseif ($look_for == T_NEW) { + $used_classes[$data] = true; + } + + $look_for = 0; + continue 2; + case T_VARIABLE: + $look_for = 0; + continue 2; + case T_DOC_COMMENT: + case T_COMMENT: + if (preg_match('!^/\*\*\s!', $data)) { + $lastphpdoc = $data; + if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) { + $nodeps = array_merge($nodeps, $m[1]); + } + } + continue 2; + case T_DOUBLE_COLON: + if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file), + 'Parser error: invalid PHP found in file "%file%"'); + } else { + PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"", + PEAR_COMMON_ERROR_INVALIDPHP); + } + + return false; + } + + $class = $tokens[$i - 1][1]; + if (strtolower($class) != 'parent') { + $used_classes[$class] = true; + } + + continue 2; + } + } + + return array( + "source_file" => $file, + "declared_classes" => $declared_classes, + "declared_interfaces" => $declared_interfaces, + "declared_methods" => $declared_methods, + "declared_functions" => $declared_functions, + "used_classes" => array_diff(array_keys($used_classes), $nodeps), + "inheritance" => $extends, + "implements" => $implements, + ); + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access private + * + */ + function _buildProvidesArray($srcinfo) + { + if (!$this->_isValid) { + return array(); + } + + $providesret = array(); + $file = basename($srcinfo['source_file']); + $pn = isset($this->_pf) ? $this->_pf->getPackage() : ''; + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($providesret[$key])) { + continue; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $providesret[$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($providesret[$key])) { + continue; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($providesret[$key])) { + continue; + } + + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + + return $providesret; + } +} + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Registry.php 287555 2009-08-21 21:27:27Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * for PEAR_Error + */ +require_once 'phar://go-pear.phar/' . 'PEAR.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/DependencyDB.php'; + +define('PEAR_REGISTRY_ERROR_LOCK', -2); +define('PEAR_REGISTRY_ERROR_FORMAT', -3); +define('PEAR_REGISTRY_ERROR_FILE', -4); +define('PEAR_REGISTRY_ERROR_CONFLICT', -5); +define('PEAR_REGISTRY_ERROR_CHANNEL_FILE', -6); + +/** + * Administration class used to maintain the installed package database. + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Registry extends PEAR +{ + /** + * File containing all channel information. + * @var string + */ + var $channels = ''; + + /** Directory where registry files are stored. + * @var string + */ + var $statedir = ''; + + /** File where the file map is stored + * @var string + */ + var $filemap = ''; + + /** Directory where registry files for channels are stored. + * @var string + */ + var $channelsdir = ''; + + /** Name of file used for locking the registry + * @var string + */ + var $lockfile = ''; + + /** File descriptor used during locking + * @var resource + */ + var $lock_fp = null; + + /** Mode used during locking + * @var int + */ + var $lock_mode = 0; // XXX UNUSED + + /** Cache of package information. Structure: + * array( + * 'package' => array('id' => ... ), + * ... ) + * @var array + */ + var $pkginfo_cache = array(); + + /** Cache of file map. Structure: + * array( '/path/to/file' => 'package', ... ) + * @var array + */ + var $filemap_cache = array(); + + /** + * @var false|PEAR_ChannelFile + */ + var $_pearChannel; + + /** + * @var false|PEAR_ChannelFile + */ + var $_peclChannel; + + /** + * @var false|PEAR_ChannelFile + */ + var $_docChannel; + + /** + * @var PEAR_DependencyDB + */ + var $_dependencyDB; + + /** + * @var PEAR_Config + */ + var $_config; + + /** + * PEAR_Registry constructor. + * + * @param string (optional) PEAR install directory (for .php files) + * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PEAR channel, if + * default values are not desired. Only used the very first time a PEAR + * repository is initialized + * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PECL channel, if + * default values are not desired. Only used the very first time a PEAR + * repository is initialized + * + * @access public + */ + function PEAR_Registry($pear_install_dir = PEAR_INSTALL_DIR, $pear_channel = false, + $pecl_channel = false) + { + parent::PEAR(); + $this->setInstallDir($pear_install_dir); + $this->_pearChannel = $pear_channel; + $this->_peclChannel = $pecl_channel; + $this->_config = false; + } + + function setInstallDir($pear_install_dir = PEAR_INSTALL_DIR) + { + $ds = DIRECTORY_SEPARATOR; + $this->install_dir = $pear_install_dir; + $this->channelsdir = $pear_install_dir.$ds.'.channels'; + $this->statedir = $pear_install_dir.$ds.'.registry'; + $this->filemap = $pear_install_dir.$ds.'.filemap'; + $this->lockfile = $pear_install_dir.$ds.'.lock'; + } + + function hasWriteAccess() + { + if (!file_exists($this->install_dir)) { + $dir = $this->install_dir; + while ($dir && $dir != '.') { + $olddir = $dir; + $dir = dirname($dir); + if ($dir != '.' && file_exists($dir)) { + if (is_writeable($dir)) { + return true; + } + + return false; + } + + if ($dir == $olddir) { // this can happen in safe mode + return @is_writable($dir); + } + } + + return false; + } + + return is_writeable($this->install_dir); + } + + function setConfig(&$config, $resetInstallDir = true) + { + $this->_config = &$config; + if ($resetInstallDir) { + $this->setInstallDir($config->get('php_dir')); + } + } + + function _initializeChannelDirs() + { + static $running = false; + if (!$running) { + $running = true; + $ds = DIRECTORY_SEPARATOR; + if (!is_dir($this->channelsdir) || + !file_exists($this->channelsdir . $ds . 'pear.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') || + !file_exists($this->channelsdir . $ds . '__uri.reg')) { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $pear_channel = $this->_pearChannel; + if (!is_a($pear_channel, 'PEAR_ChannelFile') || !$pear_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'phar://go-pear.phar/' . 'PEAR/ChannelFile.php'; + } + + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setAlias('pear'); + $pear_channel->setServer('pear.php.net'); + $pear_channel->setSummary('PHP Extension and Application Repository'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/'); + //$pear_channel->setBaseURL('REST1.4', 'http://pear.php.net/rest/'); + } else { + $pear_channel->setServer('pear.php.net'); + $pear_channel->setAlias('pear'); + } + + $pear_channel->validate(); + $this->_addChannel($pear_channel); + } + + if (!file_exists($this->channelsdir . $ds . 'pecl.php.net.reg')) { + $pecl_channel = $this->_peclChannel; + if (!is_a($pecl_channel, 'PEAR_ChannelFile') || !$pecl_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'phar://go-pear.phar/' . 'PEAR/ChannelFile.php'; + } + + $pecl_channel = new PEAR_ChannelFile; + $pecl_channel->setAlias('pecl'); + $pecl_channel->setServer('pecl.php.net'); + $pecl_channel->setSummary('PHP Extension Community Library'); + $pecl_channel->setDefaultPEARProtocols(); + $pecl_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/'); + $pecl_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/'); + $pecl_channel->setValidationPackage('PEAR_Validator_PECL', '1.0'); + } else { + $pecl_channel->setServer('pecl.php.net'); + $pecl_channel->setAlias('pecl'); + } + + $pecl_channel->validate(); + $this->_addChannel($pecl_channel); + } + + if (!file_exists($this->channelsdir . $ds . 'doc.php.net.reg')) { + $doc_channel = $this->_docChannel; + if (!is_a($doc_channel, 'PEAR_ChannelFile') || !$doc_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'phar://go-pear.phar/' . 'PEAR/ChannelFile.php'; + } + + $doc_channel = new PEAR_ChannelFile; + $doc_channel->setAlias('phpdocs'); + $doc_channel->setServer('doc.php.net'); + $doc_channel->setSummary('PHP Documentation Team'); + $doc_channel->setDefaultPEARProtocols(); + $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/'); + } else { + $doc_channel->setServer('doc.php.net'); + $doc_channel->setAlias('doc'); + } + + $doc_channel->validate(); + $this->_addChannel($doc_channel); + } + + if (!file_exists($this->channelsdir . $ds . '__uri.reg')) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'phar://go-pear.phar/' . 'PEAR/ChannelFile.php'; + } + + $private = new PEAR_ChannelFile; + $private->setName('__uri'); + $private->setDefaultPEARProtocols(); + $private->setBaseURL('REST1.0', '****'); + $private->setSummary('Pseudo-channel for static packages'); + $this->_addChannel($private); + } + $this->_rebuildFileMap(); + } + + $running = false; + } + } + + function _initializeDirs() + { + $ds = DIRECTORY_SEPARATOR; + // XXX Compatibility code should be removed in the future + // rename all registry files if any to lowercase + if (!OS_WINDOWS && file_exists($this->statedir) && is_dir($this->statedir) && + $handle = opendir($this->statedir)) { + $dest = $this->statedir . $ds; + while (false !== ($file = readdir($handle))) { + if (preg_match('/^.*[A-Z].*\.reg\\z/', $file)) { + rename($dest . $file, $dest . strtolower($file)); + } + } + closedir($handle); + } + + $this->_initializeChannelDirs(); + if (!file_exists($this->filemap)) { + $this->_rebuildFileMap(); + } + $this->_initializeDepDB(); + } + + function _initializeDepDB() + { + if (!isset($this->_dependencyDB)) { + static $initializing = false; + if (!$initializing) { + $initializing = true; + if (!$this->_config) { // never used? + $file = OS_WINDOWS ? 'pear.ini' : '.pearrc'; + $this->_config = &new PEAR_Config($this->statedir . DIRECTORY_SEPARATOR . + $file); + $this->_config->setRegistry($this); + $this->_config->set('php_dir', $this->install_dir); + } + + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config); + if (PEAR::isError($this->_dependencyDB)) { + // attempt to recover by removing the dep db + if (file_exists($this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb')) { + @unlink($this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb'); + } + + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config); + if (PEAR::isError($this->_dependencyDB)) { + echo $this->_dependencyDB->getMessage(); + echo 'Unrecoverable error'; + exit(1); + } + } + + $initializing = false; + } + } + } + + /** + * PEAR_Registry destructor. Makes sure no locks are forgotten. + * + * @access private + */ + function _PEAR_Registry() + { + parent::_PEAR(); + if (is_resource($this->lock_fp)) { + $this->_unlock(); + } + } + + /** + * Make sure the directory where we keep registry files exists. + * + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertStateDir($channel = false) + { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_assertChannelStateDir($channel); + } + + static $init = false; + if (!file_exists($this->statedir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'phar://go-pear.phar/' . 'System.php'; + if (!System::mkdir(array('-p', $this->statedir))) { + return $this->raiseError("could not create directory '{$this->statedir}'"); + } + $init = true; + } elseif (!is_dir($this->statedir)) { + return $this->raiseError('Cannot create directory ' . $this->statedir . ', ' . + 'it already exists and is not a directory'); + } + + $ds = DIRECTORY_SEPARATOR; + if (!file_exists($this->channelsdir)) { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') || + !file_exists($this->channelsdir . $ds . '__uri.reg')) { + $init = true; + } + } elseif (!is_dir($this->channelsdir)) { + return $this->raiseError('Cannot create directory ' . $this->channelsdir . ', ' . + 'it already exists and is not a directory'); + } + + if ($init) { + static $running = false; + if (!$running) { + $running = true; + $this->_initializeDirs(); + $running = false; + $init = false; + } + } else { + $this->_initializeDepDB(); + } + + return true; + } + + /** + * Make sure the directory where we keep registry files exists for a non-standard channel. + * + * @param string channel name + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertChannelStateDir($channel) + { + $ds = DIRECTORY_SEPARATOR; + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $this->_initializeChannelDirs(); + } + return $this->_assertStateDir($channel); + } + + $channelDir = $this->_channelDirectoryName($channel); + if (!is_dir($this->channelsdir) || + !file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $this->_initializeChannelDirs(); + } + + if (!file_exists($channelDir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'phar://go-pear.phar/' . 'System.php'; + if (!System::mkdir(array('-p', $channelDir))) { + return $this->raiseError("could not create directory '" . $channelDir . + "'"); + } + } elseif (!is_dir($channelDir)) { + return $this->raiseError("could not create directory '" . $channelDir . + "', already exists and is not a directory"); + } + + return true; + } + + /** + * Make sure the directory where we keep registry files for channels exists + * + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertChannelDir() + { + if (!file_exists($this->channelsdir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'phar://go-pear.phar/' . 'System.php'; + if (!System::mkdir(array('-p', $this->channelsdir))) { + return $this->raiseError("could not create directory '{$this->channelsdir}'"); + } + } elseif (!is_dir($this->channelsdir)) { + return $this->raiseError("could not create directory '{$this->channelsdir}" . + "', it already exists and is not a directory"); + } + + if (!file_exists($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'phar://go-pear.phar/' . 'System.php'; + if (!System::mkdir(array('-p', $this->channelsdir . DIRECTORY_SEPARATOR . '.alias'))) { + return $this->raiseError("could not create directory '{$this->channelsdir}/.alias'"); + } + } elseif (!is_dir($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) { + return $this->raiseError("could not create directory '{$this->channelsdir}" . + "/.alias', it already exists and is not a directory"); + } + + return true; + } + + /** + * Get the name of the file where data for a given package is stored. + * + * @param string channel name, or false if this is a PEAR package + * @param string package name + * + * @return string registry file name + * + * @access public + */ + function _packageFileName($package, $channel = false) + { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_channelDirectoryName($channel) . DIRECTORY_SEPARATOR . + strtolower($package) . '.reg'; + } + + return $this->statedir . DIRECTORY_SEPARATOR . strtolower($package) . '.reg'; + } + + /** + * Get the name of the file where data for a given channel is stored. + * @param string channel name + * @return string registry file name + */ + function _channelFileName($channel, $noaliases = false) + { + if (!$noaliases) { + if (file_exists($this->_getChannelAliasFileName($channel))) { + $channel = implode('', file($this->_getChannelAliasFileName($channel))); + } + } + return $this->channelsdir . DIRECTORY_SEPARATOR . str_replace('/', '_', + strtolower($channel)) . '.reg'; + } + + /** + * @param string + * @return string + */ + function _getChannelAliasFileName($alias) + { + return $this->channelsdir . DIRECTORY_SEPARATOR . '.alias' . + DIRECTORY_SEPARATOR . str_replace('/', '_', strtolower($alias)) . '.txt'; + } + + /** + * Get the name of a channel from its alias + */ + function _getChannelFromAlias($channel) + { + if (!$this->_channelExists($channel)) { + if ($channel == 'pear.php.net') { + return 'pear.php.net'; + } + + if ($channel == 'pecl.php.net') { + return 'pecl.php.net'; + } + + if ($channel == 'doc.php.net') { + return 'doc.php.net'; + } + + if ($channel == '__uri') { + return '__uri'; + } + + return false; + } + + $channel = strtolower($channel); + if (file_exists($this->_getChannelAliasFileName($channel))) { + // translate an alias to an actual channel + return implode('', file($this->_getChannelAliasFileName($channel))); + } + + return $channel; + } + + /** + * Get the alias of a channel from its alias or its name + */ + function _getAlias($channel) + { + if (!$this->_channelExists($channel)) { + if ($channel == 'pear.php.net') { + return 'pear'; + } + + if ($channel == 'pecl.php.net') { + return 'pecl'; + } + + if ($channel == 'doc.php.net') { + return 'phpdocs'; + } + + return false; + } + + $channel = $this->_getChannel($channel); + if (PEAR::isError($channel)) { + return $channel; + } + + return $channel->getAlias(); + } + + /** + * Get the name of the file where data for a given package is stored. + * + * @param string channel name, or false if this is a PEAR package + * @param string package name + * + * @return string registry file name + * + * @access public + */ + function _channelDirectoryName($channel) + { + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + return $this->statedir; + } + + $ch = $this->_getChannelFromAlias($channel); + if (!$ch) { + $ch = $channel; + } + + return $this->statedir . DIRECTORY_SEPARATOR . strtolower('.channel.' . + str_replace('/', '_', $ch)); + } + + function _openPackageFile($package, $mode, $channel = false) + { + if (!$this->_assertStateDir($channel)) { + return null; + } + + if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) { + return null; + } + + $file = $this->_packageFileName($package, $channel); + if (!file_exists($file) && $mode == 'r' || $mode == 'rb') { + return null; + } + + $fp = @fopen($file, $mode); + if (!$fp) { + return null; + } + + return $fp; + } + + function _closePackageFile($fp) + { + fclose($fp); + } + + function _openChannelFile($channel, $mode) + { + if (!$this->_assertChannelDir()) { + return null; + } + + if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) { + return null; + } + + $file = $this->_channelFileName($channel); + if (!file_exists($file) && $mode == 'r' || $mode == 'rb') { + return null; + } + + $fp = @fopen($file, $mode); + if (!$fp) { + return null; + } + + return $fp; + } + + function _closeChannelFile($fp) + { + fclose($fp); + } + + function _rebuildFileMap() + { + if (!class_exists('PEAR_Installer_Role')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Installer/Role.php'; + } + + $channels = $this->_listAllPackages(); + $files = array(); + foreach ($channels as $channel => $packages) { + foreach ($packages as $package) { + $version = $this->_packageInfo($package, 'version', $channel); + $filelist = $this->_packageInfo($package, 'filelist', $channel); + if (!is_array($filelist)) { + continue; + } + + foreach ($filelist as $name => $attrs) { + if (isset($attrs['attribs'])) { + $attrs = $attrs['attribs']; + } + + // it is possible for conflicting packages in different channels to + // conflict with data files/doc files + if ($name == 'dirtree') { + continue; + } + + if (isset($attrs['role']) && !in_array($attrs['role'], + PEAR_Installer_Role::getInstallableRoles())) { + // these are not installed + continue; + } + + if (isset($attrs['role']) && !in_array($attrs['role'], + PEAR_Installer_Role::getBaseinstallRoles())) { + $attrs['baseinstalldir'] = $package; + } + + if (isset($attrs['baseinstalldir'])) { + $file = $attrs['baseinstalldir'].DIRECTORY_SEPARATOR.$name; + } else { + $file = $name; + } + + $file = preg_replace(',^/+,', '', $file); + if ($channel != 'pear.php.net') { + if (!isset($files[$attrs['role']])) { + $files[$attrs['role']] = array(); + } + $files[$attrs['role']][$file] = array(strtolower($channel), + strtolower($package)); + } else { + if (!isset($files[$attrs['role']])) { + $files[$attrs['role']] = array(); + } + $files[$attrs['role']][$file] = strtolower($package); + } + } + } + } + + + $this->_assertStateDir(); + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->filemap, 'wb'); + if (!$fp) { + return false; + } + + $this->filemap_cache = $files; + fwrite($fp, serialize($files)); + fclose($fp); + return true; + } + + function _readFileMap() + { + if (!file_exists($this->filemap)) { + return array(); + } + + $fp = @fopen($this->filemap, 'r'); + if (!$fp) { + return $this->raiseError('PEAR_Registry: could not open filemap "' . $this->filemap . '"', PEAR_REGISTRY_ERROR_FILE, null, null, $php_errormsg); + } + + clearstatcache(); + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + $fsize = filesize($this->filemap); + fclose($fp); + $data = file_get_contents($this->filemap); + set_magic_quotes_runtime($rt); + $tmp = unserialize($data); + if (!$tmp && $fsize > 7) { + return $this->raiseError('PEAR_Registry: invalid filemap data', PEAR_REGISTRY_ERROR_FORMAT, null, null, $data); + } + + $this->filemap_cache = $tmp; + return true; + } + + /** + * Lock the registry. + * + * @param integer lock mode, one of LOCK_EX, LOCK_SH or LOCK_UN. + * See flock manual for more information. + * + * @return bool TRUE on success, FALSE if locking failed, or a + * PEAR error if some other error occurs (such as the + * lock file not being writable). + * + * @access private + */ + function _lock($mode = LOCK_EX) + { + if (stristr(php_uname(), 'Windows 9')) { + return true; + } + + if ($mode != LOCK_UN && is_resource($this->lock_fp)) { + // XXX does not check type of lock (LOCK_SH/LOCK_EX) + return true; + } + + if (!$this->_assertStateDir()) { + if ($mode == LOCK_EX) { + return $this->raiseError('Registry directory is not writeable by the current user'); + } + + return true; + } + + $open_mode = 'w'; + // XXX People reported problems with LOCK_SH and 'w' + if ($mode === LOCK_SH || $mode === LOCK_UN) { + if (!file_exists($this->lockfile)) { + touch($this->lockfile); + } + $open_mode = 'r'; + } + + if (!is_resource($this->lock_fp)) { + $this->lock_fp = @fopen($this->lockfile, $open_mode); + } + + if (!is_resource($this->lock_fp)) { + $this->lock_fp = null; + return $this->raiseError("could not create lock file" . + (isset($php_errormsg) ? ": " . $php_errormsg : "")); + } + + if (!(int)flock($this->lock_fp, $mode)) { + switch ($mode) { + case LOCK_SH: $str = 'shared'; break; + case LOCK_EX: $str = 'exclusive'; break; + case LOCK_UN: $str = 'unlock'; break; + default: $str = 'unknown'; break; + } + + //is resource at this point, close it on error. + fclose($this->lock_fp); + $this->lock_fp = null; + return $this->raiseError("could not acquire $str lock ($this->lockfile)", + PEAR_REGISTRY_ERROR_LOCK); + } + + return true; + } + + function _unlock() + { + $ret = $this->_lock(LOCK_UN); + if (is_resource($this->lock_fp)) { + fclose($this->lock_fp); + } + + $this->lock_fp = null; + return $ret; + } + + function _packageExists($package, $channel = false) + { + return file_exists($this->_packageFileName($package, $channel)); + } + + /** + * Determine whether a channel exists in the registry + * + * @param string Channel name + * @param bool if true, then aliases will be ignored + * @return boolean + */ + function _channelExists($channel, $noaliases = false) + { + $a = file_exists($this->_channelFileName($channel, $noaliases)); + if (!$a && $channel == 'pear.php.net') { + return true; + } + + if (!$a && $channel == 'pecl.php.net') { + return true; + } + + if (!$a && $channel == 'doc.php.net') { + return true; + } + + return $a; + } + + /** + * Determine whether a mirror exists within the deafult channel in the registry + * + * @param string Channel name + * @param string Mirror name + * + * @return boolean + */ + function _mirrorExists($channel, $mirror) + { + $data = $this->_channelInfo($channel); + if (!isset($data['servers']['mirror'])) { + return false; + } + + foreach ($data['servers']['mirror'] as $m) { + if ($m['attribs']['host'] == $mirror) { + return true; + } + } + + return false; + } + + /** + * @param PEAR_ChannelFile Channel object + * @param donotuse + * @param string Last-Modified HTTP tag from remote request + * @return boolean|PEAR_Error True on creation, false if it already exists + */ + function _addChannel($channel, $update = false, $lastmodified = false) + { + if (!is_a($channel, 'PEAR_ChannelFile')) { + return false; + } + + if (!$channel->validate()) { + return false; + } + + if (file_exists($this->_channelFileName($channel->getName()))) { + if (!$update) { + return false; + } + + $checker = $this->_getChannel($channel->getName()); + if (PEAR::isError($checker)) { + return $checker; + } + + if ($channel->getAlias() != $checker->getAlias()) { + if (file_exists($this->_getChannelAliasFileName($checker->getAlias()))) { + @unlink($this->_getChannelAliasFileName($checker->getAlias())); + } + } + } else { + if ($update && !in_array($channel->getName(), array('pear.php.net', 'pecl.php.net', 'doc.php.net'))) { + return false; + } + } + + $ret = $this->_assertChannelDir(); + if (PEAR::isError($ret)) { + return $ret; + } + + $ret = $this->_assertChannelStateDir($channel->getName()); + if (PEAR::isError($ret)) { + return $ret; + } + + if ($channel->getAlias() != $channel->getName()) { + if (file_exists($this->_getChannelAliasFileName($channel->getAlias())) && + $this->_getChannelFromAlias($channel->getAlias()) != $channel->getName()) { + $channel->setAlias($channel->getName()); + } + + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->_getChannelAliasFileName($channel->getAlias()), 'w'); + if (!$fp) { + return false; + } + + fwrite($fp, $channel->getName()); + fclose($fp); + } + + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->_channelFileName($channel->getName()), 'wb'); + if (!$fp) { + return false; + } + + $info = $channel->toArray(); + if ($lastmodified) { + $info['_lastmodified'] = $lastmodified; + } else { + $info['_lastmodified'] = date('r'); + } + + fwrite($fp, serialize($info)); + fclose($fp); + return true; + } + + /** + * Deletion fails if there are any packages installed from the channel + * @param string|PEAR_ChannelFile channel name + * @return boolean|PEAR_Error True on deletion, false if it doesn't exist + */ + function _deleteChannel($channel) + { + if (!is_string($channel)) { + if (!is_a($channel, 'PEAR_ChannelFile')) { + return false; + } + + if (!$channel->validate()) { + return false; + } + $channel = $channel->getName(); + } + + if ($this->_getChannelFromAlias($channel) == '__uri') { + return false; + } + + if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') { + return false; + } + + if ($this->_getChannelFromAlias($channel) == 'doc.php.net') { + return false; + } + + if (!$this->_channelExists($channel)) { + return false; + } + + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + return false; + } + + $channel = $this->_getChannelFromAlias($channel); + if ($channel == 'pear.php.net') { + return false; + } + + $test = $this->_listChannelPackages($channel); + if (count($test)) { + return false; + } + + $test = @rmdir($this->_channelDirectoryName($channel)); + if (!$test) { + return false; + } + + $file = $this->_getChannelAliasFileName($this->_getAlias($channel)); + if (file_exists($file)) { + $test = @unlink($file); + if (!$test) { + return false; + } + } + + $file = $this->_channelFileName($channel); + $ret = true; + if (file_exists($file)) { + $ret = @unlink($file); + } + + return $ret; + } + + /** + * Determine whether a channel exists in the registry + * @param string Channel Alias + * @return boolean + */ + function _isChannelAlias($alias) + { + return file_exists($this->_getChannelAliasFileName($alias)); + } + + /** + * @param string|null + * @param string|null + * @param string|null + * @return array|null + * @access private + */ + function _packageInfo($package = null, $key = null, $channel = 'pear.php.net') + { + if ($package === null) { + if ($channel === null) { + $channels = $this->_listChannels(); + $ret = array(); + foreach ($channels as $channel) { + $channel = strtolower($channel); + $ret[$channel] = array(); + $packages = $this->_listPackages($channel); + foreach ($packages as $package) { + $ret[$channel][] = $this->_packageInfo($package, null, $channel); + } + } + + return $ret; + } + + $ps = $this->_listPackages($channel); + if (!count($ps)) { + return array(); + } + return array_map(array(&$this, '_packageInfo'), + $ps, array_fill(0, count($ps), null), + array_fill(0, count($ps), $channel)); + } + + $fp = $this->_openPackageFile($package, 'r', $channel); + if ($fp === null) { + return null; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + $this->_closePackageFile($fp); + $data = file_get_contents($this->_packageFileName($package, $channel)); + set_magic_quotes_runtime($rt); + $data = unserialize($data); + if ($key === null) { + return $data; + } + + // compatibility for package.xml version 2.0 + if (isset($data['old'][$key])) { + return $data['old'][$key]; + } + + if (isset($data[$key])) { + return $data[$key]; + } + + return null; + } + + /** + * @param string Channel name + * @param bool whether to strictly retrieve info of channels, not just aliases + * @return array|null + */ + function _channelInfo($channel, $noaliases = false) + { + if (!$this->_channelExists($channel, $noaliases)) { + return null; + } + + $fp = $this->_openChannelFile($channel, 'r'); + if ($fp === null) { + return null; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + $this->_closeChannelFile($fp); + $data = file_get_contents($this->_channelFileName($channel)); + set_magic_quotes_runtime($rt); + $data = unserialize($data); + return $data; + } + + function _listChannels() + { + $channellist = array(); + if (!file_exists($this->channelsdir) || !is_dir($this->channelsdir)) { + return array('pear.php.net', 'pecl.php.net', 'doc.php.net', '__uri'); + } + + $dp = opendir($this->channelsdir); + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + + if ($ent == '__uri.reg') { + $channellist[] = '__uri'; + continue; + } + + $channellist[] = str_replace('_', '/', substr($ent, 0, -4)); + } + + closedir($dp); + if (!in_array('pear.php.net', $channellist)) { + $channellist[] = 'pear.php.net'; + } + + if (!in_array('pecl.php.net', $channellist)) { + $channellist[] = 'pecl.php.net'; + } + + if (!in_array('doc.php.net', $channellist)) { + $channellist[] = 'doc.php.net'; + } + + + if (!in_array('__uri', $channellist)) { + $channellist[] = '__uri'; + } + + natsort($channellist); + return $channellist; + } + + function _listPackages($channel = false) + { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_listChannelPackages($channel); + } + + if (!file_exists($this->statedir) || !is_dir($this->statedir)) { + return array(); + } + + $pkglist = array(); + $dp = opendir($this->statedir); + if (!$dp) { + return $pkglist; + } + + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + + $pkglist[] = substr($ent, 0, -4); + } + closedir($dp); + return $pkglist; + } + + function _listChannelPackages($channel) + { + $pkglist = array(); + if (!file_exists($this->_channelDirectoryName($channel)) || + !is_dir($this->_channelDirectoryName($channel))) { + return array(); + } + + $dp = opendir($this->_channelDirectoryName($channel)); + if (!$dp) { + return $pkglist; + } + + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + $pkglist[] = substr($ent, 0, -4); + } + + closedir($dp); + return $pkglist; + } + + function _listAllPackages() + { + $ret = array(); + foreach ($this->_listChannels() as $channel) { + $ret[$channel] = $this->_listPackages($channel); + } + + return $ret; + } + + /** + * Add an installed package to the registry + * @param string package name + * @param array package info (parsed by PEAR_Common::infoFrom*() methods) + * @return bool success of saving + * @access private + */ + function _addPackage($package, $info) + { + if ($this->_packageExists($package)) { + return false; + } + + $fp = $this->_openPackageFile($package, 'wb'); + if ($fp === null) { + return false; + } + + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + if (isset($info['filelist'])) { + $this->_rebuildFileMap(); + } + + return true; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + * @access private + */ + function _addPackage2($info) + { + if (!is_a($info, 'PEAR_PackageFile_v1') && !is_a($info, 'PEAR_PackageFile_v2')) { + return false; + } + + if (!$info->validate()) { + if (class_exists('PEAR_Common')) { + $ui = PEAR_Frontend::singleton(); + if ($ui) { + foreach ($info->getValidationWarnings() as $err) { + $ui->log($err['message'], true); + } + } + } + return false; + } + + $channel = $info->getChannel(); + $package = $info->getPackage(); + $save = $info; + if ($this->_packageExists($package, $channel)) { + return false; + } + + if (!$this->_channelExists($channel, true)) { + return false; + } + + $info = $info->toArray(true); + if (!$info) { + return false; + } + + $fp = $this->_openPackageFile($package, 'wb', $channel); + if ($fp === null) { + return false; + } + + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + $this->_rebuildFileMap(); + return true; + } + + /** + * @param string Package name + * @param array parsed package.xml 1.0 + * @param bool this parameter is only here for BC. Don't use it. + * @access private + */ + function _updatePackage($package, $info, $merge = true) + { + $oldinfo = $this->_packageInfo($package); + if (empty($oldinfo)) { + return false; + } + + $fp = $this->_openPackageFile($package, 'w'); + if ($fp === null) { + return false; + } + + if (is_object($info)) { + $info = $info->toArray(); + } + $info['_lastmodified'] = time(); + + $newinfo = $info; + if ($merge) { + $info = array_merge($oldinfo, $info); + } else { + $diff = $info; + } + + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + if (isset($newinfo['filelist'])) { + $this->_rebuildFileMap(); + } + + return true; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + * @access private + */ + function _updatePackage2($info) + { + if (!$this->_packageExists($info->getPackage(), $info->getChannel())) { + return false; + } + + $fp = $this->_openPackageFile($info->getPackage(), 'w', $info->getChannel()); + if ($fp === null) { + return false; + } + + $save = $info; + $info = $save->getArray(true); + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + $this->_rebuildFileMap(); + return true; + } + + /** + * @param string Package name + * @param string Channel name + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null + * @access private + */ + function &_getPackage($package, $channel = 'pear.php.net') + { + $info = $this->_packageInfo($package, null, $channel); + if ($info === null) { + return $info; + } + + $a = $this->_config; + if (!$a) { + $this->_config = &new PEAR_Config; + $this->_config->set('php_dir', $this->statedir); + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile.php'; + } + + $pkg = &new PEAR_PackageFile($this->_config); + $pf = &$pkg->fromArray($info); + return $pf; + } + + /** + * @param string channel name + * @param bool whether to strictly retrieve channel names + * @return PEAR_ChannelFile|PEAR_Error + * @access private + */ + function &_getChannel($channel, $noaliases = false) + { + $ch = false; + if ($this->_channelExists($channel, $noaliases)) { + $chinfo = $this->_channelInfo($channel, $noaliases); + if ($chinfo) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'phar://go-pear.phar/' . 'PEAR/ChannelFile.php'; + } + + $ch = &PEAR_ChannelFile::fromArrayWithErrors($chinfo); + } + } + + if ($ch) { + if ($ch->validate()) { + return $ch; + } + + foreach ($ch->getErrors(true) as $err) { + $message = $err['message'] . "\n"; + } + + $ch = PEAR::raiseError($message); + return $ch; + } + + if ($this->_getChannelFromAlias($channel) == 'pear.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'phar://go-pear.phar/' . 'PEAR/ChannelFile.php'; + } + + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setServer('pear.php.net'); + $pear_channel->setAlias('pear'); + $pear_channel->setSummary('PHP Extension and Application Repository'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/'); + return $pear_channel; + } + + if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'phar://go-pear.phar/' . 'PEAR/ChannelFile.php'; + } + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setServer('pecl.php.net'); + $pear_channel->setAlias('pecl'); + $pear_channel->setSummary('PHP Extension Community Library'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/'); + $pear_channel->setValidationPackage('PEAR_Validator_PECL', '1.0'); + return $pear_channel; + } + + if ($this->_getChannelFromAlias($channel) == 'doc.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'phar://go-pear.phar/' . 'PEAR/ChannelFile.php'; + } + + $doc_channel = new PEAR_ChannelFile; + $doc_channel->setServer('doc.php.net'); + $doc_channel->setAlias('phpdocs'); + $doc_channel->setSummary('PHP Documentation Team'); + $doc_channel->setDefaultPEARProtocols(); + $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/'); + return $doc_channel; + } + + + if ($this->_getChannelFromAlias($channel) == '__uri') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'phar://go-pear.phar/' . 'PEAR/ChannelFile.php'; + } + + $private = new PEAR_ChannelFile; + $private->setName('__uri'); + $private->setDefaultPEARProtocols(); + $private->setBaseURL('REST1.0', '****'); + $private->setSummary('Pseudo-channel for static packages'); + return $private; + } + + return $ch; + } + + /** + * @param string Package name + * @param string Channel name + * @return bool + */ + function packageExists($package, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_packageExists($package, $channel); + $this->_unlock(); + return $ret; + } + + // }}} + + // {{{ channelExists() + + /** + * @param string channel name + * @param bool if true, then aliases will be ignored + * @return bool + */ + function channelExists($channel, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_channelExists($channel, $noaliases); + $this->_unlock(); + return $ret; + } + + // }}} + + /** + * @param string channel name mirror is in + * @param string mirror name + * + * @return bool + */ + function mirrorExists($channel, $mirror) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + + $ret = $this->_mirrorExists($channel, $mirror); + $this->_unlock(); + return $ret; + } + + // {{{ isAlias() + + /** + * Determines whether the parameter is an alias of a channel + * @param string + * @return bool + */ + function isAlias($alias) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_isChannelAlias($alias); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ packageInfo() + + /** + * @param string|null + * @param string|null + * @param string + * @return array|null + */ + function packageInfo($package = null, $key = null, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_packageInfo($package, $key, $channel); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ channelInfo() + + /** + * Retrieve a raw array of channel data. + * + * Do not use this, instead use {@link getChannel()} for normal + * operations. Array structure is undefined in this method + * @param string channel name + * @param bool whether to strictly retrieve information only on non-aliases + * @return array|null|PEAR_Error + */ + function channelInfo($channel = null, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_channelInfo($channel, $noaliases); + $this->_unlock(); + return $ret; + } + + // }}} + + /** + * @param string + */ + function channelName($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_getChannelFromAlias($channel); + $this->_unlock(); + return $ret; + } + + /** + * @param string + */ + function channelAlias($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_getAlias($channel); + $this->_unlock(); + return $ret; + } + // {{{ listPackages() + + function listPackages($channel = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listPackages($channel); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ listAllPackages() + + function listAllPackages() + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listAllPackages(); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ listChannel() + + function listChannels() + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listChannels(); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ addPackage() + + /** + * Add an installed package to the registry + * @param string|PEAR_PackageFile_v1|PEAR_PackageFile_v2 package name or object + * that will be passed to {@link addPackage2()} + * @param array package info (parsed by PEAR_Common::infoFrom*() methods) + * @return bool success of saving + */ + function addPackage($package, $info) + { + if (is_object($info)) { + return $this->addPackage2($info); + } + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + $ret = $this->_addPackage($package, $info); + $this->_unlock(); + if ($ret) { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + $pf->fromArray($info); + $this->_dependencyDB->uninstallPackage($pf); + $this->_dependencyDB->installPackage($pf); + } + return $ret; + } + + // }}} + // {{{ addPackage2() + + function addPackage2($info) + { + if (!is_object($info)) { + return $this->addPackage($info['package'], $info); + } + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + $ret = $this->_addPackage2($info); + $this->_unlock(); + if ($ret) { + $this->_dependencyDB->uninstallPackage($info); + $this->_dependencyDB->installPackage($info); + } + return $ret; + } + + // }}} + // {{{ updateChannel() + + /** + * For future expandibility purposes, separate this + * @param PEAR_ChannelFile + */ + function updateChannel($channel, $lastmodified = null) + { + if ($channel->getName() == '__uri') { + return false; + } + return $this->addChannel($channel, $lastmodified, true); + } + + // }}} + // {{{ deleteChannel() + + /** + * Deletion fails if there are any packages installed from the channel + * @param string|PEAR_ChannelFile channel name + * @return boolean|PEAR_Error True on deletion, false if it doesn't exist + */ + function deleteChannel($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_deleteChannel($channel); + $this->_unlock(); + if ($ret && is_a($this->_config, 'PEAR_Config')) { + $this->_config->setChannels($this->listChannels()); + } + + return $ret; + } + + // }}} + // {{{ addChannel() + + /** + * @param PEAR_ChannelFile Channel object + * @param string Last-Modified header from HTTP for caching + * @return boolean|PEAR_Error True on creation, false if it already exists + */ + function addChannel($channel, $lastmodified = false, $update = false) + { + if (!is_a($channel, 'PEAR_ChannelFile') || !$channel->validate()) { + return false; + } + + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_addChannel($channel, $update, $lastmodified); + $this->_unlock(); + if (!$update && $ret && is_a($this->_config, 'PEAR_Config')) { + $this->_config->setChannels($this->listChannels()); + } + + return $ret; + } + + // }}} + // {{{ deletePackage() + + function deletePackage($package, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $file = $this->_packageFileName($package, $channel); + $ret = file_exists($file) ? @unlink($file) : false; + $this->_rebuildFileMap(); + $this->_unlock(); + $p = array('channel' => $channel, 'package' => $package); + $this->_dependencyDB->uninstallPackage($p); + return $ret; + } + + // }}} + // {{{ updatePackage() + + function updatePackage($package, $info, $merge = true) + { + if (is_object($info)) { + return $this->updatePackage2($info, $merge); + } + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + $ret = $this->_updatePackage($package, $info, $merge); + $this->_unlock(); + if ($ret) { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + $pf->fromArray($this->packageInfo($package)); + $this->_dependencyDB->uninstallPackage($pf); + $this->_dependencyDB->installPackage($pf); + } + return $ret; + } + + // }}} + // {{{ updatePackage2() + + function updatePackage2($info) + { + + if (!is_object($info)) { + return $this->updatePackage($info['package'], $info, $merge); + } + + if (!$info->validate(PEAR_VALIDATE_DOWNLOADING)) { + return false; + } + + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_updatePackage2($info); + $this->_unlock(); + if ($ret) { + $this->_dependencyDB->uninstallPackage($info); + $this->_dependencyDB->installPackage($info); + } + + return $ret; + } + + // }}} + // {{{ getChannel() + /** + * @param string channel name + * @param bool whether to strictly return raw channels (no aliases) + * @return PEAR_ChannelFile|PEAR_Error + */ + function &getChannel($channel, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = &$this->_getChannel($channel, $noaliases); + $this->_unlock(); + if (!$ret) { + return PEAR::raiseError('Unknown channel: ' . $channel); + } + return $ret; + } + + // }}} + // {{{ getPackage() + /** + * @param string package name + * @param string channel name + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null + */ + function &getPackage($package, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $pf = &$this->_getPackage($package, $channel); + $this->_unlock(); + return $pf; + } + + // }}} + + /** + * Get PEAR_PackageFile_v[1/2] objects representing the contents of + * a dependency group that are installed. + * + * This is used at uninstall-time + * @param array + * @return array|false + */ + function getInstalledGroup($group) + { + $ret = array(); + if (isset($group['package'])) { + if (!isset($group['package'][0])) { + $group['package'] = array($group['package']); + } + foreach ($group['package'] as $package) { + $depchannel = isset($package['channel']) ? $package['channel'] : '__uri'; + $p = &$this->getPackage($package['name'], $depchannel); + if ($p) { + $save = &$p; + $ret[] = &$save; + } + } + } + if (isset($group['subpackage'])) { + if (!isset($group['subpackage'][0])) { + $group['subpackage'] = array($group['subpackage']); + } + foreach ($group['subpackage'] as $package) { + $depchannel = isset($package['channel']) ? $package['channel'] : '__uri'; + $p = &$this->getPackage($package['name'], $depchannel); + if ($p) { + $save = &$p; + $ret[] = &$save; + } + } + } + if (!count($ret)) { + return false; + } + return $ret; + } + + // {{{ getChannelValidator() + /** + * @param string channel name + * @return PEAR_Validate|false + */ + function &getChannelValidator($channel) + { + $chan = $this->getChannel($channel); + if (PEAR::isError($chan)) { + return $chan; + } + $val = $chan->getValidationObject(); + return $val; + } + // }}} + // {{{ getChannels() + /** + * @param string channel name + * @return array an array of PEAR_ChannelFile objects representing every installed channel + */ + function &getChannels() + { + $ret = array(); + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + foreach ($this->_listChannels() as $channel) { + $e = &$this->_getChannel($channel); + if (!$e || PEAR::isError($e)) { + continue; + } + $ret[] = $e; + } + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ checkFileMap() + + /** + * Test whether a file or set of files belongs to a package. + * + * If an array is passed in + * @param string|array file path, absolute or relative to the pear + * install dir + * @param string|array name of PEAR package or array('package' => name, 'channel' => + * channel) of a package that will be ignored + * @param string API version - 1.1 will exclude any files belonging to a package + * @param array private recursion variable + * @return array|false which package and channel the file belongs to, or an empty + * string if the file does not belong to an installed package, + * or belongs to the second parameter's package + */ + function checkFileMap($path, $package = false, $api = '1.0', $attrs = false) + { + if (is_array($path)) { + static $notempty; + if (empty($notempty)) { + if (!class_exists('PEAR_Installer_Role')) { + require_once 'phar://go-pear.phar/' . 'PEAR/Installer/Role.php'; + } + $notempty = create_function('$a','return !empty($a);'); + } + $package = is_array($package) ? array(strtolower($package[0]), strtolower($package[1])) + : strtolower($package); + $pkgs = array(); + foreach ($path as $name => $attrs) { + if (is_array($attrs)) { + if (isset($attrs['install-as'])) { + $name = $attrs['install-as']; + } + if (!in_array($attrs['role'], PEAR_Installer_Role::getInstallableRoles())) { + // these are not installed + continue; + } + if (!in_array($attrs['role'], PEAR_Installer_Role::getBaseinstallRoles())) { + $attrs['baseinstalldir'] = is_array($package) ? $package[1] : $package; + } + if (isset($attrs['baseinstalldir'])) { + $name = $attrs['baseinstalldir'] . DIRECTORY_SEPARATOR . $name; + } + } + $pkgs[$name] = $this->checkFileMap($name, $package, $api, $attrs); + if (PEAR::isError($pkgs[$name])) { + return $pkgs[$name]; + } + } + return array_filter($pkgs, $notempty); + } + if (empty($this->filemap_cache)) { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $err = $this->_readFileMap(); + $this->_unlock(); + if (PEAR::isError($err)) { + return $err; + } + } + if (!$attrs) { + $attrs = array('role' => 'php'); // any old call would be for PHP role only + } + if (isset($this->filemap_cache[$attrs['role']][$path])) { + if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) { + return false; + } + return $this->filemap_cache[$attrs['role']][$path]; + } + $l = strlen($this->install_dir); + if (substr($path, 0, $l) == $this->install_dir) { + $path = preg_replace('!^'.DIRECTORY_SEPARATOR.'+!', '', substr($path, $l)); + } + if (isset($this->filemap_cache[$attrs['role']][$path])) { + if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) { + return false; + } + return $this->filemap_cache[$attrs['role']][$path]; + } + return false; + } + + // }}} + // {{{ flush() + /** + * Force a reload of the filemap + * @since 1.5.0RC3 + */ + function flushFileMap() + { + $this->filemap_cache = null; + clearstatcache(); // ensure that the next read gets the full, current filemap + } + + // }}} + // {{{ apiVersion() + /** + * Get the expected API version. Channels API is version 1.1, as it is backwards + * compatible with 1.0 + * @return string + */ + function apiVersion() + { + return '1.1'; + } + // }}} + + + /** + * Parse a package name, or validate a parsed package name array + * @param string|array pass in an array of format + * array( + * 'package' => 'pname', + * ['channel' => 'channame',] + * ['version' => 'version',] + * ['state' => 'state',] + * ['group' => 'groupname']) + * or a string of format + * [channel://][channame/]pname[-version|-state][/group=groupname] + * @return array|PEAR_Error + */ + function parsePackageName($param, $defaultchannel = 'pear.php.net') + { + $saveparam = $param; + if (is_array($param)) { + // convert to string for error messages + $saveparam = $this->parsedPackageNameToString($param); + // process the array + if (!isset($param['package'])) { + return PEAR::raiseError('parsePackageName(): array $param ' . + 'must contain a valid package name in index "param"', + 'package', null, null, $param); + } + if (!isset($param['uri'])) { + if (!isset($param['channel'])) { + $param['channel'] = $defaultchannel; + } + } else { + $param['channel'] = '__uri'; + } + } else { + $components = @parse_url((string) $param); + if (isset($components['scheme'])) { + if ($components['scheme'] == 'http') { + // uri package + $param = array('uri' => $param, 'channel' => '__uri'); + } elseif($components['scheme'] != 'channel') { + return PEAR::raiseError('parsePackageName(): only channel:// uris may ' . + 'be downloaded, not "' . $param . '"', 'invalid', null, null, $param); + } + } + if (!isset($components['path'])) { + return PEAR::raiseError('parsePackageName(): array $param ' . + 'must contain a valid package name in "' . $param . '"', + 'package', null, null, $param); + } + if (isset($components['host'])) { + // remove the leading "/" + $components['path'] = substr($components['path'], 1); + } + if (!isset($components['scheme'])) { + if (strpos($components['path'], '/') !== false) { + if ($components['path']{0} == '/') { + return PEAR::raiseError('parsePackageName(): this is not ' . + 'a package name, it begins with "/" in "' . $param . '"', + 'invalid', null, null, $param); + } + $parts = explode('/', $components['path']); + $components['host'] = array_shift($parts); + if (count($parts) > 1) { + $components['path'] = array_pop($parts); + $components['host'] .= '/' . implode('/', $parts); + } else { + $components['path'] = implode('/', $parts); + } + } else { + $components['host'] = $defaultchannel; + } + } else { + if (strpos($components['path'], '/')) { + $parts = explode('/', $components['path']); + $components['path'] = array_pop($parts); + $components['host'] .= '/' . implode('/', $parts); + } + } + + if (is_array($param)) { + $param['package'] = $components['path']; + } else { + $param = array( + 'package' => $components['path'] + ); + if (isset($components['host'])) { + $param['channel'] = $components['host']; + } + } + if (isset($components['fragment'])) { + $param['group'] = $components['fragment']; + } + if (isset($components['user'])) { + $param['user'] = $components['user']; + } + if (isset($components['pass'])) { + $param['pass'] = $components['pass']; + } + if (isset($components['query'])) { + parse_str($components['query'], $param['opts']); + } + // check for extension + $pathinfo = pathinfo($param['package']); + if (isset($pathinfo['extension']) && + in_array(strtolower($pathinfo['extension']), array('tgz', 'tar'))) { + $param['extension'] = $pathinfo['extension']; + $param['package'] = substr($pathinfo['basename'], 0, + strlen($pathinfo['basename']) - 4); + } + // check for version + if (strpos($param['package'], '-')) { + $test = explode('-', $param['package']); + if (count($test) != 2) { + return PEAR::raiseError('parsePackageName(): only one version/state ' . + 'delimiter "-" is allowed in "' . $saveparam . '"', + 'version', null, null, $param); + } + list($param['package'], $param['version']) = $test; + } + } + // validation + $info = $this->channelExists($param['channel']); + if (PEAR::isError($info)) { + return $info; + } + if (!$info) { + return PEAR::raiseError('unknown channel "' . $param['channel'] . + '" in "' . $saveparam . '"', 'channel', null, null, $param); + } + $chan = $this->getChannel($param['channel']); + if (PEAR::isError($chan)) { + return $chan; + } + if (!$chan) { + return PEAR::raiseError("Exception: corrupt registry, could not " . + "retrieve channel " . $param['channel'] . " information", + 'registry', null, null, $param); + } + $param['channel'] = $chan->getName(); + $validate = $chan->getValidationObject(); + $vpackage = $chan->getValidationPackage(); + // validate package name + if (!$validate->validPackageName($param['package'], $vpackage['_content'])) { + return PEAR::raiseError('parsePackageName(): invalid package name "' . + $param['package'] . '" in "' . $saveparam . '"', + 'package', null, null, $param); + } + if (isset($param['group'])) { + if (!PEAR_Validate::validGroupName($param['group'])) { + return PEAR::raiseError('parsePackageName(): dependency group "' . $param['group'] . + '" is not a valid group name in "' . $saveparam . '"', 'group', null, null, + $param); + } + } + if (isset($param['state'])) { + if (!in_array(strtolower($param['state']), $validate->getValidStates())) { + return PEAR::raiseError('parsePackageName(): state "' . $param['state'] + . '" is not a valid state in "' . $saveparam . '"', + 'state', null, null, $param); + } + } + if (isset($param['version'])) { + if (isset($param['state'])) { + return PEAR::raiseError('parsePackageName(): cannot contain both ' . + 'a version and a stability (state) in "' . $saveparam . '"', + 'version/state', null, null, $param); + } + // check whether version is actually a state + if (in_array(strtolower($param['version']), $validate->getValidStates())) { + $param['state'] = strtolower($param['version']); + unset($param['version']); + } else { + if (!$validate->validVersion($param['version'])) { + return PEAR::raiseError('parsePackageName(): "' . $param['version'] . + '" is neither a valid version nor a valid state in "' . + $saveparam . '"', 'version/state', null, null, $param); + } + } + } + return $param; + } + + /** + * @param array + * @return string + */ + function parsedPackageNameToString($parsed, $brief = false) + { + if (is_string($parsed)) { + return $parsed; + } + if (is_object($parsed)) { + $p = $parsed; + $parsed = array( + 'package' => $p->getPackage(), + 'channel' => $p->getChannel(), + 'version' => $p->getVersion(), + ); + } + if (isset($parsed['uri'])) { + return $parsed['uri']; + } + if ($brief) { + if ($channel = $this->channelAlias($parsed['channel'])) { + return $channel . '/' . $parsed['package']; + } + } + $upass = ''; + if (isset($parsed['user'])) { + $upass = $parsed['user']; + if (isset($parsed['pass'])) { + $upass .= ':' . $parsed['pass']; + } + $upass = "$upass@"; + } + $ret = 'channel://' . $upass . $parsed['channel'] . '/' . $parsed['package']; + if (isset($parsed['version']) || isset($parsed['state'])) { + $ver = isset($parsed['version']) ? $parsed['version'] : ''; + $ver .= isset($parsed['state']) ? $parsed['state'] : ''; + $ret .= '-' . $ver; + } + if (isset($parsed['extension'])) { + $ret .= '.' . $parsed['extension']; + } + if (isset($parsed['opts'])) { + $ret .= '?'; + foreach ($parsed['opts'] as $name => $value) { + $parsed['opts'][$name] = "$name=$value"; + } + $ret .= implode('&', $parsed['opts']); + } + if (isset($parsed['group'])) { + $ret .= '#' . $parsed['group']; + } + return $ret; + } +} + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: REST.php 286489 2009-07-29 05:59:08Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * For downloading xml files + */ +require_once 'phar://go-pear.phar/' . 'PEAR.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/XMLParser.php'; + +/** + * Intelligently retrieve data, following hyperlinks if necessary, and re-directing + * as well + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_REST +{ + var $config; + var $_options; + + function PEAR_REST(&$config, $options = array()) + { + $this->config = &$config; + $this->_options = $options; + } + + /** + * Retrieve REST data, but always retrieve the local cache if it is available. + * + * This is useful for elements that should never change, such as information on a particular + * release + * @param string full URL to this resource + * @param array|false contents of the accept-encoding header + * @param boolean if true, xml will be returned as a string, otherwise, xml will be + * parsed using PEAR_XMLParser + * @return string|array + */ + function retrieveCacheFirst($url, $accept = false, $forcestring = false, $channel = false) + { + $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cachefile'; + + if (file_exists($cachefile)) { + return unserialize(implode('', file($cachefile))); + } + + return $this->retrieveData($url, $accept, $forcestring, $channel); + } + + /** + * Retrieve a remote REST resource + * @param string full URL to this resource + * @param array|false contents of the accept-encoding header + * @param boolean if true, xml will be returned as a string, otherwise, xml will be + * parsed using PEAR_XMLParser + * @return string|array + */ + function retrieveData($url, $accept = false, $forcestring = false, $channel = false) + { + $cacheId = $this->getCacheId($url); + if ($ret = $this->useLocalCache($url, $cacheId)) { + return $ret; + } + + $file = $trieddownload = false; + if (!isset($this->_options['offline'])) { + $trieddownload = true; + $file = $this->downloadHttp($url, $cacheId ? $cacheId['lastChange'] : false, $accept, $channel); + } + + if (PEAR::isError($file)) { + if ($file->getCode() !== -9276) { + return $file; + } + + $trieddownload = false; + $file = false; // use local copy if available on socket connect error + } + + if (!$file) { + $ret = $this->getCache($url); + if (!PEAR::isError($ret) && $trieddownload) { + // reset the age of the cache if the server says it was unmodified + $this->saveCache($url, $ret, null, true, $cacheId); + } + + return $ret; + } + + if (is_array($file)) { + $headers = $file[2]; + $lastmodified = $file[1]; + $content = $file[0]; + } else { + $headers = array(); + $lastmodified = false; + $content = $file; + } + + if ($forcestring) { + $this->saveCache($url, $content, $lastmodified, false, $cacheId); + return $content; + } + + if (isset($headers['content-type'])) { + switch ($headers['content-type']) { + case 'text/xml' : + case 'application/xml' : + case 'text/plain' : + if ($headers['content-type'] === 'text/plain') { + $check = substr($content, 0, 5); + if ($check !== 'parse($content); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + return PEAR::raiseError('Invalid xml downloaded from "' . $url . '": ' . + $err->getMessage()); + } + $content = $parser->getData(); + case 'text/html' : + default : + // use it as a string + } + } else { + // assume XML + $parser = new PEAR_XMLParser; + $parser->parse($content); + $content = $parser->getData(); + } + + $this->saveCache($url, $content, $lastmodified, false, $cacheId); + return $content; + } + + function useLocalCache($url, $cacheid = null) + { + if ($cacheid === null) { + $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cacheid'; + if (!file_exists($cacheidfile)) { + return false; + } + + $cacheid = unserialize(implode('', file($cacheidfile))); + } + + $cachettl = $this->config->get('cache_ttl'); + // If cache is newer than $cachettl seconds, we use the cache! + if (time() - $cacheid['age'] < $cachettl) { + return $this->getCache($url); + } + + return false; + } + + function getCacheId($url) + { + $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cacheid'; + + if (!file_exists($cacheidfile)) { + return false; + } + + $ret = unserialize(implode('', file($cacheidfile))); + return $ret; + } + + function getCache($url) + { + $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cachefile'; + + if (!file_exists($cachefile)) { + return PEAR::raiseError('No cached content available for "' . $url . '"'); + } + + return unserialize(implode('', file($cachefile))); + } + + /** + * @param string full URL to REST resource + * @param string original contents of the REST resource + * @param array HTTP Last-Modified and ETag headers + * @param bool if true, then the cache id file should be regenerated to + * trigger a new time-to-live value + */ + function saveCache($url, $contents, $lastmodified, $nochange = false, $cacheid = null) + { + $cachedir = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . md5($url); + $cacheidfile = $cachedir . 'rest.cacheid'; + $cachefile = $cachedir . 'rest.cachefile'; + + if ($cacheid === null && $nochange) { + $cacheid = unserialize(implode('', file($cacheidfile))); + } + + $fp = @fopen($cacheidfile, 'wb'); + if (!$fp) { + $cache_dir = $this->config->get('cache_dir'); + if (is_dir($cache_dir)) { + return false; + } + + System::mkdir(array('-p', $cache_dir)); + $fp = @fopen($cacheidfile, 'wb'); + if (!$fp) { + return false; + } + } + + if ($nochange) { + fwrite($fp, serialize(array( + 'age' => time(), + 'lastChange' => $cacheid['lastChange'], + )) + ); + + fclose($fp); + return true; + } + + fwrite($fp, serialize(array( + 'age' => time(), + 'lastChange' => $lastmodified, + )) + ); + + fclose($fp); + $fp = @fopen($cachefile, 'wb'); + if (!$fp) { + if (file_exists($cacheidfile)) { + @unlink($cacheidfile); + } + + return false; + } + + fwrite($fp, serialize($contents)); + fclose($fp); + return true; + } + + /** + * Efficiently Download a file through HTTP. Returns downloaded file as a string in-memory + * This is best used for small files + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param string $save_dir directory to save file in + * @param false|string|array $lastmodified header values to check against for caching + * use false to return the header values from this download + * @param false|array $accept Accept headers to send + * @return string|array Returns the contents of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). If caching is requested, then return the header + * values. + * + * @access public + */ + function downloadHttp($url, $lastmodified = null, $accept = false, $channel = false) + { + static $redirect = 0; + // always reset , so we are clean case of error + $wasredirect = $redirect; + $redirect = 0; + + $info = parse_url($url); + if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) { + return PEAR::raiseError('Cannot download non-http URL "' . $url . '"'); + } + + if (!isset($info['host'])) { + return PEAR::raiseError('Cannot download from non-URL "' . $url . '"'); + } + + $host = isset($info['host']) ? $info['host'] : null; + $port = isset($info['port']) ? $info['port'] : null; + $path = isset($info['path']) ? $info['path'] : null; + $schema = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http'; + + $proxy_host = $proxy_port = $proxy_user = $proxy_pass = ''; + if ($this->config->get('http_proxy')&& + $proxy = parse_url($this->config->get('http_proxy')) + ) { + $proxy_host = isset($proxy['host']) ? $proxy['host'] : null; + if ($schema === 'https') { + $proxy_host = 'ssl://' . $proxy_host; + } + + $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080; + $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null; + $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null; + $proxy_schema = (isset($proxy['scheme']) && $proxy['scheme'] == 'https') ? 'https' : 'http'; + } + + if (empty($port)) { + $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80; + } + + if (isset($proxy['host'])) { + $request = "GET $url HTTP/1.1\r\n"; + } else { + $request = "GET $path HTTP/1.1\r\n"; + } + + $request .= "Host: $host:$port\r\n"; + $ifmodifiedsince = ''; + if (is_array($lastmodified)) { + if (isset($lastmodified['Last-Modified'])) { + $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n"; + } + + if (isset($lastmodified['ETag'])) { + $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n"; + } + } else { + $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : ''); + } + + $request .= $ifmodifiedsince . + "User-Agent: PEAR/1.9.0/PHP/" . PHP_VERSION . "\r\n"; + + $username = $this->config->get('username', null, $channel); + $password = $this->config->get('password', null, $channel); + + if ($username && $password) { + $tmp = base64_encode("$username:$password"); + $request .= "Authorization: Basic $tmp\r\n"; + } + + if ($proxy_host != '' && $proxy_user != '') { + $request .= 'Proxy-Authorization: Basic ' . + base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n"; + } + + if ($accept) { + $request .= 'Accept: ' . implode(', ', $accept) . "\r\n"; + } + + $request .= "Accept-Encoding:\r\n"; + $request .= "Connection: close\r\n"; + $request .= "\r\n"; + + if ($proxy_host != '') { + $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr, 15); + if (!$fp) { + return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", -9276); + } + } else { + if ($schema === 'https') { + $host = 'ssl://' . $host; + } + + $fp = @fsockopen($host, $port, $errno, $errstr); + if (!$fp) { + return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno); + } + } + + fwrite($fp, $request); + + $headers = array(); + $reply = 0; + while ($line = trim(fgets($fp, 1024))) { + if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) { + $headers[strtolower($matches[1])] = trim($matches[2]); + } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) { + $reply = (int)$matches[1]; + if ($reply == 304 && ($lastmodified || ($lastmodified === false))) { + return false; + } + + if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (received: $line)"); + } + } + } + + if ($reply != 200) { + if (!isset($headers['location'])) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (redirected but no location)"); + } + + if ($wasredirect > 4) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (redirection looped more than 5 times)"); + } + + $redirect = $wasredirect + 1; + return $this->downloadHttp($headers['location'], $lastmodified, $accept, $channel); + } + + $length = isset($headers['content-length']) ? $headers['content-length'] : -1; + + $data = ''; + while ($chunk = @fread($fp, 8192)) { + $data .= $chunk; + } + fclose($fp); + + if ($lastmodified === false || $lastmodified) { + if (isset($headers['etag'])) { + $lastmodified = array('ETag' => $headers['etag']); + } + + if (isset($headers['last-modified'])) { + if (is_array($lastmodified)) { + $lastmodified['Last-Modified'] = $headers['last-modified']; + } else { + $lastmodified = $headers['last-modified']; + } + } + + return array($data, $lastmodified, $headers); + } + + return $data; + } +} + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: 10.php 287558 2009-08-21 22:21:28Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a12 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'phar://go-pear.phar/' . 'PEAR/REST.php'; + +/** + * Implement REST 1.0 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a12 + */ +class PEAR_REST_10 +{ + /** + * @var PEAR_REST + */ + var $_rest; + function PEAR_REST_10($config, $options = array()) + { + $this->_rest = &new PEAR_REST($config, $options); + } + + /** + * Retrieve information about a remote package to be downloaded from a REST server + * + * @param string $base The uri to prepend to all REST calls + * @param array $packageinfo an array of format: + *
    +     *  array(
    +     *   'package' => 'packagename',
    +     *   'channel' => 'channelname',
    +     *  ['state' => 'alpha' (or valid state),]
    +     *  -or-
    +     *  ['version' => '1.whatever']
    +     * 
    + * @param string $prefstate Current preferred_state config variable value + * @param bool $installed the installed version of this package to compare against + * @return array|false|PEAR_Error see {@link _returnDownloadURL()} + */ + function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $packageinfo['channel']; + $package = $packageinfo['package']; + $state = isset($packageinfo['state']) ? $packageinfo['state'] : null; + $version = isset($packageinfo['version']) ? $packageinfo['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('No releases available for package "' . + $channel . '/' . $package . '"'); + } + + if (!isset($info['r'])) { + return false; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + + if (isset($state)) { + // try our preferred state first + if ($release['s'] == $state) { + $found = true; + break; + } + // see if there is something newer and more stable + // bug #7221 + if (in_array($release['s'], $this->betterStates($state), true)) { + $found = true; + break; + } + } elseif (isset($version)) { + if ($release['v'] == $version) { + $found = true; + break; + } + } else { + if (in_array($release['s'], $states)) { + $found = true; + break; + } + } + } + + return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel); + } + + function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage, + $prefstate = 'stable', $installed = false, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $dependency['channel']; + $package = $dependency['name']; + $state = isset($dependency['state']) ? $dependency['state'] : null; + $version = isset($dependency['version']) ? $dependency['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package'] + . '" dependency "' . $channel . '/' . $package . '" has no releases'); + } + + if (!is_array($info) || !isset($info['r'])) { + return false; + } + + $exclude = array(); + $min = $max = $recommended = false; + if ($xsdversion == '1.0') { + switch ($dependency['rel']) { + case 'ge' : + $min = $dependency['version']; + break; + case 'gt' : + $min = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'eq' : + $recommended = $dependency['version']; + break; + case 'lt' : + $max = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'le' : + $max = $dependency['version']; + break; + case 'ne' : + $exclude = array($dependency['version']); + break; + } + } else { + $min = isset($dependency['min']) ? $dependency['min'] : false; + $max = isset($dependency['max']) ? $dependency['max'] : false; + $recommended = isset($dependency['recommended']) ? + $dependency['recommended'] : false; + if (isset($dependency['exclude'])) { + if (!isset($dependency['exclude'][0])) { + $exclude = array($dependency['exclude']); + } + } + } + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + if (in_array($release['v'], $exclude)) { // skip excluded versions + continue; + } + // allow newer releases to say "I'm OK with the dependent package" + if ($xsdversion == '2.0' && isset($release['co'])) { + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + foreach ($release['co'] as $entry) { + if (isset($entry['x']) && !is_array($entry['x'])) { + $entry['x'] = array($entry['x']); + } elseif (!isset($entry['x'])) { + $entry['x'] = array(); + } + if ($entry['c'] == $deppackage['channel'] && + strtolower($entry['p']) == strtolower($deppackage['package']) && + version_compare($deppackage['version'], $entry['min'], '>=') && + version_compare($deppackage['version'], $entry['max'], '<=') && + !in_array($release['v'], $entry['x'])) { + $recommended = $release['v']; + break; + } + } + } + if ($recommended) { + if ($release['v'] != $recommended) { // if we want a specific + // version, then skip all others + continue; + } else { + if (!in_array($release['s'], $states)) { + // the stability is too low, but we must return the + // recommended version if possible + return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel); + } + } + } + if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions + continue; + } + if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions + continue; + } + if ($installed && version_compare($release['v'], $installed, '<')) { + continue; + } + if (in_array($release['s'], $states)) { // if in the preferred state... + $found = true; // ... then use it + break; + } + } + return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel); + } + + /** + * Take raw data and return the array needed for processing a download URL + * + * @param string $base REST base uri + * @param string $package Package name + * @param array $release an array of format array('v' => version, 's' => state) + * describing the release to download + * @param array $info list of all releases as defined by allreleases.xml + * @param bool|null $found determines whether the release was found or this is the next + * best alternative. If null, then versions were skipped because + * of PHP dependency + * @return array|PEAR_Error + * @access private + */ + function _returnDownloadURL($base, $package, $release, $info, $found, $phpversion = false, $channel = false) + { + if (!$found) { + $release = $info['r'][0]; + } + + $packageLower = strtolower($package); + $pinfo = $this->_rest->retrieveCacheFirst($base . 'p/' . $packageLower . '/' . + 'info.xml', false, false, $channel); + if (PEAR::isError($pinfo)) { + return PEAR::raiseError('Package "' . $package . + '" does not have REST info xml available'); + } + + $releaseinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' . + $release['v'] . '.xml', false, false, $channel); + if (PEAR::isError($releaseinfo)) { + return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] . + '" does not have REST xml available'); + } + + $packagexml = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' . + 'deps.' . $release['v'] . '.txt', false, true, $channel); + if (PEAR::isError($packagexml)) { + return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] . + '" does not have REST dependency information available'); + } + + $packagexml = unserialize($packagexml); + if (!$packagexml) { + $packagexml = array(); + } + + $allinfo = $this->_rest->retrieveData($base . 'r/' . $packageLower . + '/allreleases.xml', false, false, $channel); + if (!is_array($allinfo['r']) || !isset($allinfo['r'][0])) { + $allinfo['r'] = array($allinfo['r']); + } + + $compatible = false; + foreach ($allinfo['r'] as $release) { + if ($release['v'] != $releaseinfo['v']) { + continue; + } + + if (!isset($release['co'])) { + break; + } + + $compatible = array(); + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + + foreach ($release['co'] as $entry) { + $comp = array(); + $comp['name'] = $entry['p']; + $comp['channel'] = $entry['c']; + $comp['min'] = $entry['min']; + $comp['max'] = $entry['max']; + if (isset($entry['x']) && !is_array($entry['x'])) { + $comp['exclude'] = $entry['x']; + } + + $compatible[] = $comp; + } + + if (count($compatible) == 1) { + $compatible = $compatible[0]; + } + + break; + } + + $deprecated = false; + if (isset($pinfo['dc']) && isset($pinfo['dp'])) { + if (is_array($pinfo['dp'])) { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp']['_content'])); + } else { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp'])); + } + } + + $return = array( + 'version' => $releaseinfo['v'], + 'info' => $packagexml, + 'package' => $releaseinfo['p']['_content'], + 'stability' => $releaseinfo['st'], + 'compatible' => $compatible, + 'deprecated' => $deprecated, + ); + + if ($found) { + $return['url'] = $releaseinfo['g']; + return $return; + } + + $return['php'] = $phpversion; + return $return; + } + + function listPackages($base, $channel = false) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return array(); + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + return $packagelist['p']; + } + + /** + * List all categories of a REST server + * + * @param string $base base URL of the server + * @return array of categorynames + */ + function listCategories($base, $channel = false) + { + $categories = array(); + + // c/categories.xml does not exist; + // check for every package its category manually + // This is SLOOOWWWW : /// + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + $ret = array(); + return $ret; + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($packagelist['p'] as $package) { + $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($inf)) { + PEAR::popErrorHandling(); + return $inf; + } + $cat = $inf['ca']['_content']; + if (!isset($categories[$cat])) { + $categories[$cat] = $inf['ca']; + } + } + + return array_values($categories); + } + + /** + * List a category of a REST server + * + * @param string $base base URL of the server + * @param string $category name of the category + * @param boolean $info also download full package info + * @return array of packagenames + */ + function listCategory($base, $category, $info = false, $channel = false) + { + // gives '404 Not Found' error when category doesn't exist + $packagelist = $this->_rest->retrieveData($base.'c/'.urlencode($category).'/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return array(); + } + + if (!is_array($packagelist['p']) || + !isset($packagelist['p'][0])) { // only 1 pkg + $packagelist = array($packagelist['p']); + } else { + $packagelist = $packagelist['p']; + } + + if ($info == true) { + // get individual package info + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($packagelist as $i => $packageitem) { + $url = sprintf('%s'.'r/%s/latest.txt', + $base, + strtolower($packageitem['_content'])); + $version = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($version)) { + break; // skipit + } + $url = sprintf('%s'.'r/%s/%s.xml', + $base, + strtolower($packageitem['_content']), + $version); + $info = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($info)) { + break; // skipit + } + $packagelist[$i]['info'] = $info; + } + PEAR::popErrorHandling(); + } + + return $packagelist; + } + + + function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + if ($this->_rest->config->get('verbose') > 0) { + $ui = &PEAR_Frontend::singleton(); + $ui->log('Retrieving data...0%', true); + } + $ret = array(); + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return $ret; + } + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + // only search-packagename = quicksearch ! + if ($searchpackage && (!$searchsummary || empty($searchpackage))) { + $newpackagelist = array(); + foreach ($packagelist['p'] as $package) { + if (!empty($searchpackage) && stristr($package, $searchpackage) !== false) { + $newpackagelist[] = $package; + } + } + $packagelist['p'] = $newpackagelist; + } + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $next = .1; + foreach ($packagelist['p'] as $progress => $package) { + if ($this->_rest->config->get('verbose') > 0) { + if ($progress / count($packagelist['p']) >= $next) { + if ($next == .5) { + $ui->log('50%', false); + } else { + $ui->log('.', false); + } + $next += .1; + } + } + + if ($basic) { // remote-list command + if ($dostable) { + $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/stable.txt', false, false, $channel); + } else { + $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/latest.txt', false, false, $channel); + } + if (PEAR::isError($latest)) { + $latest = false; + } + $info = array('stable' => $latest); + } else { // list-all command + $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($inf)) { + PEAR::popErrorHandling(); + return $inf; + } + if ($searchpackage) { + $found = (!empty($searchpackage) && stristr($package, $searchpackage) !== false); + if (!$found && !(isset($searchsummary) && !empty($searchsummary) + && (stristr($inf['s'], $searchsummary) !== false + || stristr($inf['d'], $searchsummary) !== false))) + { + continue; + }; + } + $releases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + if (PEAR::isError($releases)) { + continue; + } + if (!isset($releases['r'][0])) { + $releases['r'] = array($releases['r']); + } + unset($latest); + unset($unstable); + unset($stable); + unset($state); + foreach ($releases['r'] as $release) { + if (!isset($latest)) { + if ($dostable && $release['s'] == 'stable') { + $latest = $release['v']; + $state = 'stable'; + } + if (!$dostable) { + $latest = $release['v']; + $state = $release['s']; + } + } + if (!isset($stable) && $release['s'] == 'stable') { + $stable = $release['v']; + if (!isset($unstable)) { + $unstable = $stable; + } + } + if (!isset($unstable) && $release['s'] != 'stable') { + $latest = $unstable = $release['v']; + $state = $release['s']; + } + if (isset($latest) && !isset($state)) { + $state = $release['s']; + } + if (isset($latest) && isset($stable) && isset($unstable)) { + break; + } + } + $deps = array(); + if (!isset($unstable)) { + $unstable = false; + $state = 'stable'; + if (isset($stable)) { + $latest = $unstable = $stable; + } + } else { + $latest = $unstable; + } + if (!isset($latest)) { + $latest = false; + } + if ($latest) { + $d = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' . + $latest . '.txt', false, false, $channel); + if (!PEAR::isError($d)) { + $d = unserialize($d); + if ($d) { + if (isset($d['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v2.php'; + } + if (!isset($pf)) { + $pf = new PEAR_PackageFile_v2; + } + $pf->setDeps($d); + $tdeps = $pf->getDeps(); + } else { + $tdeps = $d; + } + foreach ($tdeps as $dep) { + if ($dep['type'] !== 'pkg') { + continue; + } + $deps[] = $dep; + } + } + } + } + if (!isset($stable)) { + $stable = '-n/a-'; + } + if (!$searchpackage) { + $info = array('stable' => $latest, 'summary' => $inf['s'], 'description' => + $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'], + 'unstable' => $unstable, 'state' => $state); + } else { + $info = array('stable' => $stable, 'summary' => $inf['s'], 'description' => + $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'], + 'unstable' => $unstable, 'state' => $state); + } + } + $ret[$package] = $info; + } + PEAR::popErrorHandling(); + return $ret; + } + + function listLatestUpgrades($base, $pref_state, $installed, $channel, &$reg) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + $ret = array(); + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return $ret; + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + foreach ($packagelist['p'] as $package) { + if (!isset($installed[strtolower($package)])) { + continue; + } + + $inst_version = $reg->packageInfo($package, 'version', $channel); + $inst_state = $reg->packageInfo($package, 'release_state', $channel); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + PEAR::popErrorHandling(); + if (PEAR::isError($info)) { + continue; // no remote releases + } + + if (!isset($info['r'])) { + continue; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + // $info['r'] is sorted by version number + usort($info['r'], array($this, '_sortReleasesByVersionNumber')); + foreach ($info['r'] as $release) { + if ($inst_version && version_compare($release['v'], $inst_version, '<=')) { + // not newer than the one installed + break; + } + + // new version > installed version + if (!$pref_state) { + // every state is a good state + $found = true; + break; + } else { + $new_state = $release['s']; + // if new state >= installed state: go + if (in_array($new_state, $this->betterStates($inst_state, true))) { + $found = true; + break; + } else { + // only allow to lower the state of package, + // if new state >= preferred state: go + if (in_array($new_state, $this->betterStates($pref_state, true))) { + $found = true; + break; + } + } + } + } + + if (!$found) { + continue; + } + + $relinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/' . + $release['v'] . '.xml', false, false, $channel); + if (PEAR::isError($relinfo)) { + return $relinfo; + } + + $ret[$package] = array( + 'version' => $release['v'], + 'state' => $release['s'], + 'filesize' => $relinfo['f'], + ); + } + + return $ret; + } + + function packageInfo($base, $package, $channel = false) + { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pinfo = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($pinfo)) { + PEAR::popErrorHandling(); + return PEAR::raiseError('Unknown package: "' . $package . '" in channel "' . $channel . '"' . "\n". 'Debug: ' . + $pinfo->getMessage()); + } + + $releases = array(); + $allreleases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + if (!PEAR::isError($allreleases)) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'phar://go-pear.phar/' . 'PEAR/PackageFile/v2.php'; + } + + if (!is_array($allreleases['r']) || !isset($allreleases['r'][0])) { + $allreleases['r'] = array($allreleases['r']); + } + + $pf = new PEAR_PackageFile_v2; + foreach ($allreleases['r'] as $release) { + $ds = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' . + $release['v'] . '.txt', false, false, $channel); + if (PEAR::isError($ds)) { + continue; + } + + if (!isset($latest)) { + $latest = $release['v']; + } + + $pf->setDeps(unserialize($ds)); + $ds = $pf->getDeps(); + $info = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) + . '/' . $release['v'] . '.xml', false, false, $channel); + + if (PEAR::isError($info)) { + continue; + } + + $releases[$release['v']] = array( + 'doneby' => $info['m'], + 'license' => $info['l'], + 'summary' => $info['s'], + 'description' => $info['d'], + 'releasedate' => $info['da'], + 'releasenotes' => $info['n'], + 'state' => $release['s'], + 'deps' => $ds ? $ds : array(), + ); + } + } else { + $latest = ''; + } + + PEAR::popErrorHandling(); + if (isset($pinfo['dc']) && isset($pinfo['dp'])) { + if (is_array($pinfo['dp'])) { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp']['_content'])); + } else { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp'])); + } + } else { + $deprecated = false; + } + + if (!isset($latest)) { + $latest = ''; + } + + return array( + 'name' => $pinfo['n'], + 'channel' => $pinfo['c'], + 'category' => $pinfo['ca']['_content'], + 'stable' => $latest, + 'license' => $pinfo['l'], + 'summary' => $pinfo['s'], + 'description' => $pinfo['d'], + 'releases' => $releases, + 'deprecated' => $deprecated, + ); + } + + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + + if ($include) { + $i--; + } + + return array_slice($states, $i + 1); + } + + /** + * Sort releases by version number + * + * @access private + */ + function _sortReleasesByVersionNumber($a, $b) + { + if (version_compare($a['v'], $b['v'], '=')) { + return 0; + } + + if (version_compare($a['v'], $b['v'], '>')) { + return -1; + } + + if (version_compare($a['v'], $b['v'], '<')) { + return 1; + } + } +} 'Installation base ($prefix)', + 'temp_dir' => 'Temporary directory for processing', + 'download_dir' => 'Temporary directory for downloads', + 'bin_dir' => 'Binaries directory', + 'php_dir' => 'PHP code directory ($php_dir)', + 'doc_dir' => 'Documentation directory', + 'data_dir' => 'Data directory', + 'cfg_dir' => 'User-modifiable configuration files directory', + 'www_dir' => 'Public Web Files directory', + 'test_dir' => 'Tests directory', + 'pear_conf' => 'Name of configuration file', + ); + var $localInstall; + var $PEARConfig; + var $tarball = array(); + function PEAR_Start() + { + parent::PEAR(); + if (OS_WINDOWS) { + $this->configPrompt['php_bin'] = 'Path to CLI php.exe'; + $this->config[] = 'php_bin'; + $this->prefix = getcwd(); + + if (!@is_dir($this->prefix)) { + if (@is_dir('c:\php5')) { + $this->prefix = 'c:\php5'; + } elseif (@is_dir('c:\php4')) { + $this->prefix = 'c:\php4'; + } elseif (@is_dir('c:\php')) { + $this->prefix = 'c:\php'; + } + } + + $this->localInstall = false; + $this->bin_dir = '$prefix'; + $this->temp_dir = '$prefix\tmp'; + $this->download_dir = '$prefix\tmp'; + $this->php_dir = '$prefix\pear'; + $this->doc_dir = '$prefix\docs'; + $this->data_dir = '$prefix\data'; + $this->test_dir = '$prefix\tests'; + $this->www_dir = '$prefix\www'; + $this->cfg_dir = '$prefix\cfg'; + $this->pear_conf = PEAR_CONFIG_SYSCONFDIR . '\\pear.ini'; + /* + * Detects php.exe + */ + $this->validPHPBin = true; + if ($t = $this->safeGetenv('PHP_PEAR_PHP_BIN')) { + $this->php_bin = dirname($t); + } elseif ($t = $this->safeGetenv('PHP_BIN')) { + $this->php_bin = dirname($t); + } elseif ($t = System::which('php')) { + $this->php_bin = dirname($t); + } elseif (is_file($this->prefix . '\cli\php.exe')) { + $this->php_bin = $this->prefix . '\cli'; + } elseif (is_file($this->prefix . '\php.exe')) { + $this->php_bin = $this->prefix; + } + $phpexe = OS_WINDOWS ? '\\php.exe' : '/php'; + if ($this->php_bin && !is_file($this->php_bin . $phpexe)) { + $this->php_bin = ''; + } else { + if (strpos($this->php_bin, ':') === 0) { + $this->php_bin = getcwd() . DIRECTORY_SEPARATOR . $this->php_bin; + } + } + if (!is_file($this->php_bin . $phpexe)) { + if (is_file('c:/php/cli/php.exe')) { + $this->php_bin = 'c"\\php\\cli'; + } elseif (is_file('c:/php5/php.exe')) { + $this->php_bin = 'c:\\php5'; + } elseif (is_file('c:/php4/cli/php.exe')) { + $this->php_bin = 'c:\\php4\\cli'; + } else { + $this->validPHPBin = false; + } + } + } else { + $this->prefix = dirname(PHP_BINDIR); + $this->pear_conf = PEAR_CONFIG_SYSCONFDIR . '/pear.conf'; + if (get_current_user() != 'root') { + $this->prefix = $this->safeGetenv('HOME') . '/pear'; + $this->pear_conf = $this->safeGetenv('HOME') . '.pearrc'; + } + $this->bin_dir = '$prefix/bin'; + $this->php_dir = '$prefix/share/pear'; + $this->temp_dir = '/tmp/pear/install'; + $this->download_dir = '/tmp/pear/install'; + $this->doc_dir = '$prefix/docs'; + $this->www_dir = '$prefix/www'; + $this->cfg_dir = '$prefix/cfg'; + $this->data_dir = '$prefix/data'; + $this->test_dir = '$prefix/tests'; + // check if the user has installed PHP with PHP or GNU layout + if (@is_dir("$this->prefix/lib/php/.registry")) { + $this->php_dir = '$prefix/lib/php'; + } elseif (@is_dir("$this->prefix/share/pear/lib/.registry")) { + $this->php_dir = '$prefix/share/pear/lib'; + $this->doc_dir = '$prefix/share/pear/docs'; + $this->data_dir = '$prefix/share/pear/data'; + $this->test_dir = '$prefix/share/pear/tests'; + } elseif (@is_dir("$this->prefix/share/php/.registry")) { + $this->php_dir = '$prefix/share/php'; + } + } + } + + function safeGetenv($var) + { + if (is_array($_ENV) && isset($_ENV[$var])) { + return $_ENV[$var]; + } + return getenv($var); + } + + function show($stuff) + { + print $stuff; + } + + function locatePackagesToInstall() + { + $dp = @opendir(dirname(__FILE__) . '/go-pear-tarballs'); + if (empty($dp)) { + return PEAR::raiseError("while locating packages to install: opendir('" . + dirname(__FILE__) . "/go-pear-tarballs') failed"); + } + $potentials = array(); + while (false !== ($entry = readdir($dp))) { + if ($entry{0} == '.' || !in_array(substr($entry, -4), array('.tar', '.tgz'))) { + continue; + } + $potentials[] = $entry; + } + closedir($dp); + $notfound = array(); + foreach ($this->corePackages as $package) { + foreach ($potentials as $i => $candidate) { + if (preg_match('/^' . $package . '-' . _PEAR_COMMON_PACKAGE_VERSION_PREG + . '\.(tar|tgz)\\z/', $candidate)) { + $this->tarball[$package] = dirname(__FILE__) . '/go-pear-tarballs/' . $candidate; + unset($potentials[$i]); + continue 2; + } + } + $notfound[] = $package; + } + if (count($notfound)) { + return PEAR::raiseError("No tarballs found for core packages: " . + implode(', ', $notfound)); + } + $this->tarball = array_merge($this->tarball, $potentials); + } + + function setupTempStuff() + { + if (!($this->ptmp = System::mktemp(array('-d')))) { + $this->show("System's Tempdir failed, trying to use \$prefix/tmp ..."); + $res = System::mkDir(array($this->prefix . '/tmp')); + if (!$res) { + return PEAR::raiseError('mkdir ' . $this->prefix . '/tmp ... failed'); + } + $_temp = tempnam($this->prefix . '/tmp', 'gope'); + + System::rm(array('-rf', $_temp)); + System::mkdir(array('-p','-m', '0700', $_temp)); + $this->ptmp = $this->prefix . '/tmp'; + $ok = @chdir($this->ptmp); + + if (!$ok) { // This should not happen, really ;) + $this->bail('chdir ' . $this->ptmp . ' ... failed'); + } + + print "ok\n"; + + // Adjust TEMPDIR envvars + if (!isset($_ENV)) { + $_ENV = array(); + }; + $_ENV['TMPDIR'] = $_ENV['TEMP'] = $this->prefix . '/tmp'; + } + return @chdir($this->ptmp); + } + + /** + * Try to detect the kind of SAPI used by the + * the given php.exe. + * @author Pierrre-Alain Joye + */ + function win32DetectPHPSAPI() + { + if ($this->php_bin != '') { + if (OS_WINDOWS) { + exec('"' . $this->php_bin . '\\php.exe" -v', $res); + } else { + exec('"' . $this->php_bin . '/php" -v', $res); + } + if (is_array($res)) { + if (isset($res[0]) && strpos($res[0],"(cli)")) { + return 'cli'; + } + if (isset($res[0]) && strpos($res[0],"cgi")) { + return 'cgi'; + } + if (isset($res[0]) && strpos($res[0],"cgi-fcgi")) { + return 'cgi'; + } else { + return 'unknown'; + } + } + } + return 'unknown'; + } + + function doInstall() + { + print "Beginning install...\n"; + // finish php_bin config + if (OS_WINDOWS) { + $this->php_bin .= '\\php.exe'; + } else { + $this->php_bin .= '/php'; + } + $this->PEARConfig = &PEAR_Config::singleton($this->pear_conf, $this->pear_conf); + $this->PEARConfig->set('preferred_state', 'stable'); + foreach ($this->config as $var) { + if ($var == 'pear_conf' || $var == 'prefix') { + continue; + } + $this->PEARConfig->set($var, $this->$var); + } + + $this->PEARConfig->store(); +// $this->PEARConfig->set('verbose', 6); + print "Configuration written to $this->pear_conf...\n"; + $this->registry = &$this->PEARConfig->getRegistry(); + print "Initialized registry...\n"; + $install = &PEAR_Command::factory('install', $this->PEARConfig); + print "Preparing to install...\n"; + $options = array( + 'nodeps' => true, + 'force' => true, + 'upgrade' => true, + ); + foreach ($this->tarball as $pkg => $src) { + print "installing $src...\n"; + } + $install->run('install', $options, array_values($this->tarball)); + } + + function postProcessConfigVars() + { + foreach ($this->config as $n => $var) { + for ($m = 1; $m <= count($this->config); $m++) { + $var2 = $this->config[$m]; + $this->$var = str_replace('$'.$var2, $this->$var2, $this->$var); + } + } + + foreach ($this->config as $var) { + $dir = $this->$var; + + if (!preg_match('/_dir\\z/', $var)) { + continue; + } + + if (!@is_dir($dir)) { + if (!System::mkDir(array('-p', $dir))) { + $root = OS_WINDOWS ? 'administrator' : 'root'; + return PEAR::raiseError("Unable to create {$this->configPrompt[$var]} $dir. +Run this script as $root or pick another location.\n"); + } + } + } + } + + /** + * Get the php.ini file used with the current + * process or with the given php.exe + * + * Horrible hack, but well ;) + * + * Not used yet, will add the support later + * @author Pierre-Alain Joye + */ + function getPhpiniPath() + { + $pathIni = get_cfg_var('cfg_file_path'); + if ($pathIni && is_file($pathIni)) { + return $pathIni; + } + + // Oh well, we can keep this too :) + // I dunno if get_cfg_var() is safe on every OS + if (OS_WINDOWS) { + // on Windows, we can be pretty sure that there is a php.ini + // file somewhere + do { + $php_ini = PHP_CONFIG_FILE_PATH . DIRECTORY_SEPARATOR . 'php.ini'; + if (@file_exists($php_ini)) { + break; + } + $php_ini = 'c:\winnt\php.ini'; + if (@file_exists($php_ini)) { + break; + } + $php_ini = 'c:\windows\php.ini'; + } while (false); + } else { + $php_ini = PHP_CONFIG_FILE_PATH . DIRECTORY_SEPARATOR . 'php.ini'; + } + + if (@is_file($php_ini)) { + return $php_ini; + } + + // We re running in hackz&troubles :) + ob_implicit_flush(false); + ob_start(); + phpinfo(INFO_GENERAL); + $strInfo = ob_get_contents(); + ob_end_clean(); + ob_implicit_flush(true); + + if (php_sapi_name() != 'cli') { + $strInfo = strip_tags($strInfo,''); + $arrayInfo = explode("", $strInfo ); + $cli = false; + } else { + $arrayInfo = explode("\n", $strInfo); + $cli = true; + } + + foreach ($arrayInfo as $val) { + if (strpos($val,"php.ini")) { + if ($cli) { + list(,$pathIni) = explode('=>', $val); + } else { + $pathIni = strip_tags(trim($val)); + } + $pathIni = trim($pathIni); + if (is_file($pathIni)) { + return $pathIni; + } + } + } + + return false; + } +} +?> +tty = OS_WINDOWS ? @fopen('\con', 'r') : @fopen('/dev/tty', 'r'); + + if (!$this->tty) { + $this->tty = fopen('php://stdin', 'r'); + } + $this->origpwd = getcwd(); + $this->config = array_keys($this->configPrompt); + + // make indices run from 1... + array_unshift($this->config, ""); + unset($this->config[0]); + reset($this->config); + $this->descLength = max(array_map('strlen', $this->configPrompt)); + $this->descFormat = "%-{$this->descLength}s"; + $this->first = key($this->config); + end($this->config); + $this->last = key($this->config); + PEAR_Command::setFrontendType('CLI'); + } + + function _PEAR_Start_CLI() + { + if ($this->tty) { + @fclose($this->tty); + } + } + + function run() + { + if (PEAR::isError($err = $this->locatePackagesToInstall())) { + return $err; + } + $this->startupQuestion(); + $this->setupTempStuff(); + $this->getInstallLocations(); + $this->displayPreamble(); + if (PEAR::isError($err = $this->postProcessConfigVars())) { + return $err; + } + $this->doInstall(); + $this->finishInstall(); + } + + function startupQuestion() + { + if (OS_WINDOWS) { + print " +Are you installing a system-wide PEAR or a local copy? +(system|local) [system] : "; + $tmp = trim(fgets($this->tty, 1024)); + if (!empty($tmp)) { + if (strtolower($tmp) !== 'system') { + print "Please confirm local copy by typing 'yes' : "; + $tmp = trim(fgets($this->tty, 1024)); + if (strtolower($tmp) == 'yes') { + $this->localInstall = true; + $this->pear_conf = '$prefix\\pear.ini'; + } + } + } + } else { + if (get_current_user() == 'root') { + return; + } + $this->pear_conf = $this->safeGetenv('HOME') . '/.pearrc'; + } + } + + function getInstallLocations() + { + while (true) { + print " +Below is a suggested file layout for your new PEAR installation. To +change individual locations, type the number in front of the +directory. Type 'all' to change all of them or simply press Enter to +accept these locations. + +"; + + foreach ($this->config as $n => $var) { + $fullvar = $this->$var; + foreach ($this->config as $blah => $unused) { + foreach ($this->config as $m => $var2) { + $fullvar = str_replace('$'.$var2, $this->$var2, $fullvar); + } + } + printf("%2d. $this->descFormat : %s\n", $n, $this->configPrompt[$var], $fullvar); + } + + print "\n$this->first-$this->last, 'all' or Enter to continue: "; + $tmp = trim(fgets($this->tty, 1024)); + if (empty($tmp)) { + if (OS_WINDOWS && !$this->validPHPBin) { + echo "**ERROR** +Please, enter the php.exe path. + +"; + } else { + break; + } + } + if (isset($this->config[(int)$tmp])) { + $var = $this->config[(int)$tmp]; + $desc = $this->configPrompt[$var]; + $current = $this->$var; + if (WIN32GUI && $var != 'pear_conf'){ + $tmp = $this->win32BrowseForFolder("Choose a Folder for $desc [$current] :"); + } else { + print "(Use \$prefix as a shortcut for '$this->prefix', etc.) +$desc [$current] : "; + $tmp = trim(fgets($this->tty, 1024)); + } + $old = $this->$var; + $this->$var = $$var = $tmp; + if (OS_WINDOWS && $var=='php_bin') { + if ($this->validatePhpExecutable($tmp)) { + $this->php_bin = $tmp; + } else { + $this->php_bin = $old; + } + } + } elseif ($tmp == 'all') { + foreach ($this->config as $n => $var) { + $desc = $this->configPrompt[$var]; + $current = $this->$var; + print "$desc [$current] : "; + $tmp = trim(fgets($this->tty, 1024)); + if (!empty($tmp)) { + $this->$var = $tmp; + } + } + } + } + } + + function validatePhpExecutable($tmp) + { + if (OS_WINDOWS) { + if (strpos($tmp, 'php.exe')) { + $tmp = str_replace('php.exe', '', $tmp); + } + if (file_exists($tmp . DIRECTORY_SEPARATOR . 'php.exe')) { + $tmp = $tmp . DIRECTORY_SEPARATOR . 'php.exe'; + $this->php_bin_sapi = $this->win32DetectPHPSAPI(); + if ($this->php_bin_sapi=='cgi'){ + print " +****************************************************************************** +NOTICE! We found php.exe under $this->php_bin, it uses a $this->php_bin_sapi SAPI. +PEAR commandline tool works well with it. +If you have a CLI php.exe available, we recommend using it. + +Press Enter to continue..."; + $tmp = trim(fgets($this->tty, 1024)); + } elseif ($this->php_bin_sapi=='unknown') { + print " +****************************************************************************** +WARNING! We found php.exe under $this->php_bin, it uses an $this->php_bin_sapi SAPI. +PEAR commandline tool has NOT been tested with it. +If you have a CLI (or CGI) php.exe available, we strongly recommend using it. + +Press Enter to continue..."; + $tmp = trim(fgets($this->tty, 1024)); + } + echo "php.exe (sapi: $this->php_bin_sapi) found.\n\n"; + return $this->validPHPBin = true; + } else { + echo "**ERROR**: not a folder, or no php.exe found in this folder. +Press Enter to continue..."; + $tmp = trim(fgets($this->tty, 1024)); + return $this->validPHPBin = false; + } + } + } + + /** + * Create a vbs script to browse the getfolder dialog, called + * by cscript, if it's available. + * $label is the label text in the header of the dialog box + * + * TODO: + * - Do not show Control panel + * - Replace WSH with calls to w32 as soon as callbacks work + * @author Pierrre-Alain Joye + */ + function win32BrowseForFolder($label) + { + static $wshSaved=false; + static $cscript=''; + $wsh_browserfolder = 'Option Explicit +Dim ArgObj, var1, var2, sa, sFld +Set ArgObj = WScript.Arguments +Const BIF_EDITBOX = &H10 +Const BIF_NEWDIALOGSTYLE = &H40 +Const BIF_RETURNONLYFSDIRS = &H0001 +Const BIF_DONTGOBELOWDOMAIN = &H0002 +Const BIF_STATUSTEXT = &H0004 +Const BIF_RETURNFSANCESTORS = &H0008 +Const BIF_VALIDATE = &H0020 +Const BIF_BROWSEFORCOMPUTER = &H1000 +Const BIF_BROWSEFORPRINTER = &H2000 +Const BIF_BROWSEINCLUDEFILES = &H4000 +Const OFN_LONGNAMES = &H200000 +Const OFN_NOLONGNAMES = &H40000 +Const ssfDRIVES = &H11 +Const ssfNETWORK = &H12 +Set sa = CreateObject("Shell.Application") +var1=ArgObj(0) +Set sFld = sa.BrowseForFolder(0, var1, BIF_EDITBOX + BIF_VALIDATE + BIF_BROWSEINCLUDEFILES + BIF_RETURNFSANCESTORS+BIF_NEWDIALOGSTYLE , ssfDRIVES ) +if not sFld is nothing Then + if not left(sFld.items.item.path,1)=":" Then + WScript.Echo sFld.items.item.path + Else + WScript.Echo "invalid" + End If +Else + WScript.Echo "cancel" +End If +'; + if( !$wshSaved){ + $cscript = $this->ptmp . DIRECTORY_SEPARATOR . "bf.vbs"; + $fh = fopen($cscript, "wb+"); + fwrite($fh, $wsh_browserfolder, strlen($wsh_browserfolder)); + fclose($fh); + $wshSaved = true; + } + exec('cscript ' . $cscript . ' "' . $label . '" //noLogo', $arPath); + if (!count($arPath) || $arPath[0]=='' || $arPath[0]=='cancel') { + return ''; + } elseif ($arPath[0]=='invalid') { + echo "Invalid Path.\n"; + return ''; + } + return $arPath[0]; + } + + function displayPreamble() + { + if (OS_WINDOWS) { + /* + * Checks PHP SAPI version under windows/CLI + */ + if ($this->php_bin == '') { + print " +We do not find any php.exe, please select the php.exe folder (CLI is +recommended, usually in c:\php\cli\php.exe) +"; + $this->validPHPBin = false; + } elseif (strlen($this->php_bin)) { + $this->php_bin_sapi = $this->win32DetectPHPSAPI(); + $this->validPHPBin = true; + switch ($this->php_bin_sapi) { + case 'cli': + break; + case 'cgi': + case 'cgi-fcgi': + print " +*NOTICE* +We found php.exe under $this->php_bin, it uses a $this->php_bin_sapi SAPI. PEAR commandline +tool works well with it, if you have a CLI php.exe available, we +recommend using it. +"; + break; + default: + print " +*WARNING* +We found php.exe under $this->php_bin, it uses an unknown SAPI. PEAR commandline +tool has not been tested with it, if you have a CLI (or CGI) php.exe available, +we strongly recommend using it. + +"; + break; + } + } + } + } + + function finishInstall() + { + $sep = OS_WINDOWS ? ';' : ':'; + $include_path = explode($sep, ini_get('include_path')); + if (OS_WINDOWS) { + $found = false; + $t = strtolower($this->php_dir); + foreach ($include_path as $path) { + if ($t == strtolower($path)) { + $found = true; + break; + } + } + } else { + $found = in_array($this->php_dir, $include_path); + } + if (!$found) { + print " +****************************************************************************** +WARNING! The include_path defined in the currently used php.ini does not +contain the PEAR PHP directory you just specified: +<$this->php_dir> +If the specified directory is also not in the include_path used by +your scripts, you will have problems getting any PEAR packages working. +"; + + if ($php_ini = $this->getPhpiniPath()) { + print "\n\nWould you like to alter php.ini <$php_ini>? [Y/n] : "; + $alter_phpini = !stristr(fgets($this->tty, 1024), "n"); + if ($alter_phpini) { + $this->alterPhpIni($php_ini); + } else { + if (OS_WINDOWS) { + print " +Please look over your php.ini file to make sure +$this->php_dir is in your include_path."; + } else { + print " +I will add a workaround for this in the 'pear' command to make sure +the installer works, but please look over your php.ini or Apache +configuration to make sure $this->php_dir is in your include_path. +"; + } + } + } + + print " +Current include path : ".ini_get('include_path')." +Configured directory : $this->php_dir +Currently used php.ini (guess) : $php_ini +"; + + print "Press Enter to continue: "; + fgets($this->tty, 1024); + } + + $pear_cmd = $this->bin_dir . DIRECTORY_SEPARATOR . 'pear'; + $pear_cmd = OS_WINDOWS ? strtolower($pear_cmd).'.bat' : $pear_cmd; + + // check that the installed pear and the one in the path are the same (if any) + $pear_old = System::which(OS_WINDOWS ? 'pear.bat' : 'pear', $this->bin_dir); + if ($pear_old && ($pear_old != $pear_cmd)) { + // check if it is a link or symlink + $islink = OS_WINDOWS ? false : is_link($pear_old) ; + if ($islink && readlink($pear_old) != $pear_cmd) { + print "\n** WARNING! The link $pear_old does not point to the " . + "installed $pear_cmd\n"; + } elseif (!$this->localInstall && is_writable($pear_old) && !is_dir($pear_old)) { + rename($pear_old, "{$pear_old}_old"); + print "\n** WARNING! Backed up old pear to {$pear_old}_old\n"; + } else { + print "\n** WARNING! Old version found at $pear_old, please remove it or ". + "be sure to use the new $pear_cmd command\n"; + } + } + + print "\nThe 'pear' command is now at your service at $pear_cmd\n"; + + // Alert the user if the pear cmd is not in PATH + $old_dir = $pear_old ? dirname($pear_old) : false; + if (!$this->which('pear', $old_dir)) { + print " +** The 'pear' command is not currently in your PATH, so you need to +** use '$pear_cmd' until you have added +** '$this->bin_dir' to your PATH environment variable. + +"; + + print "Run it without parameters to see the available actions, try 'pear list' +to see what packages are installed, or 'pear help' for help. + +For more information about PEAR, see: + + http://pear.php.net/faq.php + http://pear.php.net/manual/ + +Thanks for using go-pear! + +"; + } + + if (OS_WINDOWS && !$this->localInstall) { + $this->win32CreateRegEnv(); + } + } + + /** + * System::which() does not allow path exclusion + */ + function which($program, $dont_search_in = false) + { + if (OS_WINDOWS) { + if ($_path = $this->safeGetEnv('Path')) { + $dirs = explode(';', $_path); + } else { + $dirs = explode(';', $this->safeGetEnv('PATH')); + } + foreach ($dirs as $i => $dir) { + $dirs[$i] = strtolower(realpath($dir)); + } + if ($dont_search_in) { + $dont_search_in = strtolower(realpath($dont_search_in)); + } + if ($dont_search_in && + ($key = array_search($dont_search_in, $dirs)) !== false) + { + unset($dirs[$key]); + } + + foreach ($dirs as $dir) { + $dir = str_replace('\\\\', '\\', $dir); + if (!strlen($dir)) { + continue; + } + if ($dir{strlen($dir) - 1} != '\\') { + $dir .= '\\'; + } + $tmp = $dir . $program; + $info = pathinfo($tmp); + if (isset($info['extension']) && in_array(strtolower($info['extension']), + array('exe', 'com', 'bat', 'cmd'))) { + if (file_exists($tmp)) { + return strtolower($tmp); + } + } elseif (file_exists($ret = $tmp . '.exe') || + file_exists($ret = $tmp . '.com') || + file_exists($ret = $tmp . '.bat') || + file_exists($ret = $tmp . '.cmd')) { + return strtolower($ret); + } + } + } else { + $dirs = explode(':', $this->safeGetEnv('PATH')); + if ($dont_search_in && + ($key = array_search($dont_search_in, $dirs)) !== false) + { + unset($dirs[$key]); + } + foreach ($dirs as $dir) { + if (is_executable("$dir/$program")) { + return "$dir/$program"; + } + } + } + return false; + } + + /** + * Not optimized, but seems to work, if some nice + * peardev will test it? :) + * + * @author Pierre-Alain Joye + */ + function alterPhpIni($pathIni='') + { + $foundAt = array(); + $iniSep = OS_WINDOWS ? ';' : ':'; + + if ($pathIni=='') { + $pathIni = $this->getPhpiniPath(); + } + + $arrayIni = file($pathIni); + $i=0; + $found=0; + + // Looks for each active include_path directives + foreach ($arrayIni as $iniLine) { + $iniLine = trim($iniLine); + $iniLine = str_replace(array("\n", "\r"), array('', ''), $iniLine); + if (preg_match("/^\s*include_path/", $iniLine)) { + $foundAt[] = $i; + $found++; + } + $i++; + } + + if ($found) { + $includeLine = $arrayIni[$foundAt[0]]; + list(, $currentPath) = explode('=', $includeLine); + + $currentPath = trim($currentPath); + if (substr($currentPath,0,1) == '"') { + $currentPath = substr($currentPath, 1, strlen($currentPath) - 2); + } + + $arrayPath = explode($iniSep, $currentPath); + $newPath = array(); + if ($arrayPath[0]=='.') { + $newPath[0] = '.'; + $newPath[1] = $this->php_dir; + array_shift($arrayPath); + } else { + $newPath[0] = $this->php_dir; + } + + foreach ($arrayPath as $path) { + $newPath[]= $path; + } + } else { + $newPath = array(); + $newPath[0] = '.'; + $newPath[1] = $this->php_dir; + $foundAt[] = count($arrayIni); // add a new line if none is present + } + $nl = OS_WINDOWS ? "\r\n" : "\n"; + $includepath = 'include_path="' . implode($iniSep,$newPath) . '"'; + $newInclude = "$nl$nl;***** Added by go-pear$nl" . + $includepath . + $nl . ";*****" . + $nl . $nl; + + $arrayIni[$foundAt[0]] = $newInclude; + + for ($i=1; $i<$found; $i++) { + $arrayIni[$foundAt[$i]]=';' . trim($arrayIni[$foundAt[$i]]); + } + + $newIni = implode("", $arrayIni); + if (!($fh = @fopen($pathIni, "wb+"))) { + $prefixIni = $this->prefix . DIRECTORY_SEPARATOR . "php.ini-gopear"; + $fh = fopen($prefixIni, "wb+"); + if (!$fh) { + echo " +****************************************************************************** +WARNING: Cannot write to $pathIni nor in $this->prefix/php.ini-gopear. Please +modify manually your php.ini by adding: + +$includepath + +"; + return false; + } else { + fwrite($fh, $newIni, strlen($newIni)); + fclose($fh); + echo " +****************************************************************************** +WARNING: Cannot write to $pathIni, but php.ini was successfully created +at <$this->prefix/php.ini-gopear>. Please replace the file <$pathIni> with +<$prefixIni> or modify your php.ini by adding: + +$includepath + +"; + + } + } else { + fwrite($fh, $newIni, strlen($newIni)); + fclose($fh); + echo " +php.ini <$pathIni> include_path updated. +"; + } + return true; + } + + /** + * Generates a registry addOn for Win32 platform + * This addon set PEAR environment variables + * @author Pierrre-Alain Joye + */ + function win32CreateRegEnv() + { + $nl = "\r\n"; + $reg ='REGEDIT4'.$nl. + '[HKEY_CURRENT_USER\Environment]'. $nl . + '"PHP_PEAR_SYSCONF_DIR"="' . addslashes($this->prefix) . '"' . $nl . + '"PHP_PEAR_INSTALL_DIR"="' . addslashes($this->php_dir) . '"' . $nl . + '"PHP_PEAR_DOC_DIR"="' . addslashes($this->doc_dir) . '"' . $nl . + '"PHP_PEAR_BIN_DIR"="' . addslashes($this->bin_dir) . '"' . $nl . + '"PHP_PEAR_DATA_DIR"="' . addslashes($this->data_dir) . '"' . $nl . + '"PHP_PEAR_PHP_BIN"="' . addslashes($this->php_bin) . '"' . $nl . + '"PHP_PEAR_TEST_DIR"="' . addslashes($this->test_dir) . '"' . $nl; + + $fh = fopen($this->prefix . DIRECTORY_SEPARATOR . 'PEAR_ENV.reg', 'wb'); + if($fh){ + fwrite($fh, $reg, strlen($reg)); + fclose($fh); + echo " + +* WINDOWS ENVIRONMENT VARIABLES * +For convenience, a REG file is available under $this->prefix\\PEAR_ENV.reg . +This file creates ENV variables for the current user. + +Double-click this file to add it to the current user registry. + +"; + } + } + + function displayHTMLProgress() + { + } +} +?> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 276394 2009-02-25 00:15:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/**#@+ + * Error codes for task validation routines + */ +define('PEAR_TASK_ERROR_NOATTRIBS', 1); +define('PEAR_TASK_ERROR_MISSING_ATTRIB', 2); +define('PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE', 3); +define('PEAR_TASK_ERROR_INVALID', 4); +/**#@-*/ +define('PEAR_TASK_PACKAGE', 1); +define('PEAR_TASK_INSTALL', 2); +define('PEAR_TASK_PACKAGEANDINSTALL', 3); +/** + * A task is an operation that manipulates the contents of a file. + * + * Simple tasks operate on 1 file. Multiple tasks are executed after all files have been + * processed and installed, and are designed to operate on all files containing the task. + * The Post-install script task simply takes advantage of the fact that it will be run + * after installation, replace is a simple task. + * + * Combining tasks is possible, but ordering is significant. + * + * + * + * + * + * + * This will first replace any instance of @data-dir@ in the test.php file + * with the path to the current data directory. Then, it will include the + * test.php file and run the script it contains to configure the package post-installation. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + * @abstract + */ +class PEAR_Task_Common +{ + /** + * Valid types for this version are 'simple' and 'multiple' + * + * - simple tasks operate on the contents of a file and write out changes to disk + * - multiple tasks operate on the contents of many files and write out the + * changes directly to disk + * + * Child task classes must override this property. + * @access protected + */ + var $type = 'simple'; + /** + * Determines which install phase this task is executed under + */ + var $phase = PEAR_TASK_INSTALL; + /** + * @access protected + */ + var $config; + /** + * @access protected + */ + var $registry; + /** + * @access protected + */ + var $logger; + /** + * @access protected + */ + var $installphase; + /** + * @param PEAR_Config + * @param PEAR_Common + */ + function PEAR_Task_Common(&$config, &$logger, $phase) + { + $this->config = &$config; + $this->registry = &$config->getRegistry(); + $this->logger = &$logger; + $this->installphase = $phase; + if ($this->type == 'multiple') { + $GLOBALS['_PEAR_TASK_POSTINSTANCES'][get_class($this)][] = &$this; + } + } + + /** + * Validate the basic contents of a task tag. + * @param PEAR_PackageFile_v2 + * @param array + * @param PEAR_Config + * @param array the entire parsed tag + * @return true|array On error, return an array in format: + * array(PEAR_TASK_ERROR_???[, param1][, param2][, ...]) + * + * For PEAR_TASK_ERROR_MISSING_ATTRIB, pass the attribute name in + * For PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, pass the attribute name and an array + * of legal values in + * @static + * @abstract + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param array attributes from the tag containing this task + * @param string|null last installed version of this package + * @abstract + */ + function init($xml, $fileAttributes, $lastVersion) + { + } + + /** + * Begin a task processing session. All multiple tasks will be processed after each file + * has been successfully installed, all simple tasks should perform their task here and + * return any errors using the custom throwError() method to allow forward compatibility + * + * This method MUST NOT write out any changes to disk + * @param PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + * @abstract + */ + function startSession($pkg, $contents, $dest) + { + } + + /** + * This method is used to process each of the tasks for a particular multiple class + * type. Simple tasks need not implement this method. + * @param array an array of tasks + * @access protected + * @static + * @abstract + */ + function run($tasks) + { + } + + /** + * @static + * @final + */ + function hasPostinstallTasks() + { + return isset($GLOBALS['_PEAR_TASK_POSTINSTANCES']); + } + + /** + * @static + * @final + */ + function runPostinstallTasks() + { + foreach ($GLOBALS['_PEAR_TASK_POSTINSTANCES'] as $class => $tasks) { + $err = call_user_func(array($class, 'run'), + $GLOBALS['_PEAR_TASK_POSTINSTANCES'][$class]); + if ($err) { + return PEAR_Task_Common::throwError($err); + } + } + unset($GLOBALS['_PEAR_TASK_POSTINSTANCES']); + } + + /** + * Determines whether a role is a script + * @return bool + */ + function isScript() + { + return $this->type == 'script'; + } + + function throwError($msg, $code = -1) + { + include_once 'phar://go-pear.phar/' . 'PEAR.php'; + return PEAR::raiseError($msg, $code); + } +} +?> + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Postinstallscript.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Task/Common.php'; +/** + * Implements the postinstallscript file task. + * + * Note that post-install scripts are handled separately from installation, by the + * "pear run-scripts" command + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Postinstallscript extends PEAR_Task_Common +{ + var $type = 'script'; + var $_class; + var $_params; + var $_obj; + /** + * + * @var PEAR_PackageFile_v2 + */ + var $_pkg; + var $_contents; + var $phase = PEAR_TASK_INSTALL; + + /** + * Validate the raw xml at parsing-time. + * + * This also attempts to validate the script to make sure it meets the criteria + * for a post-install script + * @param PEAR_PackageFile_v2 + * @param array The XML contents of the tag + * @param PEAR_Config + * @param array the entire parsed tag + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($fileXml['role'] != 'php') { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must be role="php"'); + } + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $file = $pkg->getFileContents($fileXml['name']); + if (PEAR::isError($file)) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" is not valid: ' . + $file->getMessage()); + } elseif ($file === null) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" could not be retrieved for processing!'); + } else { + $analysis = $pkg->analyzeSourceCode($file, true); + if (!$analysis) { + PEAR::popErrorHandling(); + $warnings = ''; + foreach ($pkg->getValidationWarnings() as $warn) { + $warnings .= $warn['message'] . "\n"; + } + return array(PEAR_TASK_ERROR_INVALID, 'Analysis of post-install script "' . + $fileXml['name'] . '" failed: ' . $warnings); + } + if (count($analysis['declared_classes']) != 1) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare exactly 1 class'); + } + $class = $analysis['declared_classes'][0]; + if ($class != str_replace(array('/', '.php'), array('_', ''), + $fileXml['name']) . '_postinstall') { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" class "' . $class . '" must be named "' . + str_replace(array('/', '.php'), array('_', ''), + $fileXml['name']) . '_postinstall"'); + } + if (!isset($analysis['declared_methods'][$class])) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare methods init() and run()'); + } + $methods = array('init' => 0, 'run' => 1); + foreach ($analysis['declared_methods'][$class] as $method) { + if (isset($methods[$method])) { + unset($methods[$method]); + } + } + if (count($methods)) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare methods init() and run()'); + } + } + PEAR::popErrorHandling(); + $definedparams = array(); + $tasksNamespace = $pkg->getTasksNs() . ':'; + if (!isset($xml[$tasksNamespace . 'paramgroup']) && isset($xml['paramgroup'])) { + // in order to support the older betas, which did not expect internal tags + // to also use the namespace + $tasksNamespace = ''; + } + if (isset($xml[$tasksNamespace . 'paramgroup'])) { + $params = $xml[$tasksNamespace . 'paramgroup']; + if (!is_array($params) || !isset($params[0])) { + $params = array($params); + } + foreach ($params as $param) { + if (!isset($param[$tasksNamespace . 'id'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must have ' . + 'an ' . $tasksNamespace . 'id> tag'); + } + if (isset($param[$tasksNamespace . 'name'])) { + if (!in_array($param[$tasksNamespace . 'name'], $definedparams)) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" parameter "' . $param[$tasksNamespace . 'name'] . + '" has not been previously defined'); + } + if (!isset($param[$tasksNamespace . 'conditiontype'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'conditiontype> tag containing either "=", ' . + '"!=", or "preg_match"'); + } + if (!in_array($param[$tasksNamespace . 'conditiontype'], + array('=', '!=', 'preg_match'))) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'conditiontype> tag containing either "=", ' . + '"!=", or "preg_match"'); + } + if (!isset($param[$tasksNamespace . 'value'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'value> tag containing expected parameter value'); + } + } + if (isset($param[$tasksNamespace . 'instructions'])) { + if (!is_string($param[$tasksNamespace . 'instructions'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" ' . $tasksNamespace . 'instructions> must be simple text'); + } + } + if (!isset($param[$tasksNamespace . 'param'])) { + continue; // is no longer required + } + $subparams = $param[$tasksNamespace . 'param']; + if (!is_array($subparams) || !isset($subparams[0])) { + $subparams = array($subparams); + } + foreach ($subparams as $subparam) { + if (!isset($subparam[$tasksNamespace . 'name'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter for ' . + $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . '" must have ' . + 'a ' . $tasksNamespace . 'name> tag'); + } + if (!preg_match('/[a-zA-Z0-9]+/', + $subparam[$tasksNamespace . 'name'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" is not a valid name. Must contain only alphanumeric characters'); + } + if (!isset($subparam[$tasksNamespace . 'prompt'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . 'prompt> tag'); + } + if (!isset($subparam[$tasksNamespace . 'type'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . 'type> tag'); + } + $definedparams[] = $param[$tasksNamespace . 'id'] . '::' . + $subparam[$tasksNamespace . 'name']; + } + } + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param array attributes from the tag containing this task + * @param string|null last installed version of this package, if any (useful for upgrades) + */ + function init($xml, $fileattribs, $lastversion) + { + $this->_class = str_replace('/', '_', $fileattribs['name']); + $this->_filename = $fileattribs['name']; + $this->_class = str_replace ('.php', '', $this->_class) . '_postinstall'; + $this->_params = $xml; + $this->_lastversion = $lastversion; + } + + /** + * Strip the tasks: namespace from internal params + * + * @access private + */ + function _stripNamespace($params = null) + { + if ($params === null) { + $params = array(); + if (!is_array($this->_params)) { + return; + } + foreach ($this->_params as $i => $param) { + if (is_array($param)) { + $param = $this->_stripNamespace($param); + } + $params[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param; + } + $this->_params = $params; + } else { + $newparams = array(); + foreach ($params as $i => $param) { + if (is_array($param)) { + $param = $this->_stripNamespace($param); + } + $newparams[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param; + } + return $newparams; + } + } + + /** + * Unlike other tasks, the installed file name is passed in instead of the file contents, + * because this task is handled post-installation + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file name + * @return bool|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError) + */ + function startSession($pkg, $contents) + { + if ($this->installphase != PEAR_TASK_INSTALL) { + return false; + } + // remove the tasks: namespace if present + $this->_pkg = $pkg; + $this->_stripNamespace(); + $this->logger->log(0, 'Including external post-installation script "' . + $contents . '" - any errors are in this script'); + include_once 'phar://go-pear.phar/' . $contents; + if (class_exists($this->_class)) { + $this->logger->log(0, 'Inclusion succeeded'); + } else { + return $this->throwError('init of post-install script class "' . $this->_class + . '" failed'); + } + $this->_obj = new $this->_class; + $this->logger->log(1, 'running post-install script "' . $this->_class . '->init()"'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $res = $this->_obj->init($this->config, $pkg, $this->_lastversion); + PEAR::popErrorHandling(); + if ($res) { + $this->logger->log(0, 'init succeeded'); + } else { + return $this->throwError('init of post-install script "' . $this->_class . + '->init()" failed'); + } + $this->_contents = $contents; + return true; + } + + /** + * No longer used + * @see PEAR_PackageFile_v2::runPostinstallScripts() + * @param array an array of tasks + * @param string install or upgrade + * @access protected + * @static + */ + function run() + { + } +} +?> - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Task/Postinstallscript.php'; +/** + * Abstracts the postinstallscript file task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Postinstallscript_rw extends PEAR_Task_Postinstallscript +{ + /** + * parent package file object + * + * @var PEAR_PackageFile_v2_rw + */ + var $_pkg; + /** + * Enter description here... + * + * @param PEAR_PackageFile_v2_rw $pkg + * @param PEAR_Config $config + * @param PEAR_Frontend $logger + * @param array $fileXml + * @return PEAR_Task_Postinstallscript_rw + */ + function PEAR_Task_Postinstallscript_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents); + } + + function getName() + { + return 'postinstallscript'; + } + + /** + * add a simple to the post-install script + * + * Order is significant, so call this method in the same + * sequence the users should see the paramgroups. The $params + * parameter should either be the result of a call to {@link getParam()} + * or an array of calls to getParam(). + * + * Use {@link addConditionTypeGroup()} to add a containing + * a tag + * @param string $id id as seen by the script + * @param array|false $params array of getParam() calls, or false for no params + * @param string|false $instructions + */ + function addParamGroup($id, $params = false, $instructions = false) + { + if ($params && isset($params[0]) && !isset($params[1])) { + $params = $params[0]; + } + $stuff = + array( + $this->_pkg->getTasksNs() . ':id' => $id, + ); + if ($instructions) { + $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions; + } + if ($params) { + $stuff[$this->_pkg->getTasksNs() . ':param'] = $params; + } + $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff; + } + + /** + * add a complex to the post-install script with conditions + * + * This inserts a with + * + * Order is significant, so call this method in the same + * sequence the users should see the paramgroups. The $params + * parameter should either be the result of a call to {@link getParam()} + * or an array of calls to getParam(). + * + * Use {@link addParamGroup()} to add a simple + * + * @param string $id id as seen by the script + * @param string $oldgroup id of the section referenced by + * + * @param string $param name of the from the older section referenced + * by + * @param string $value value to match of the parameter + * @param string $conditiontype one of '=', '!=', 'preg_match' + * @param array|false $params array of getParam() calls, or false for no params + * @param string|false $instructions + */ + function addConditionTypeGroup($id, $oldgroup, $param, $value, $conditiontype = '=', + $params = false, $instructions = false) + { + if ($params && isset($params[0]) && !isset($params[1])) { + $params = $params[0]; + } + $stuff = array( + $this->_pkg->getTasksNs() . ':id' => $id, + ); + if ($instructions) { + $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions; + } + $stuff[$this->_pkg->getTasksNs() . ':name'] = $oldgroup . '::' . $param; + $stuff[$this->_pkg->getTasksNs() . ':conditiontype'] = $conditiontype; + $stuff[$this->_pkg->getTasksNs() . ':value'] = $value; + if ($params) { + $stuff[$this->_pkg->getTasksNs() . ':param'] = $params; + } + $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff; + } + + function getXml() + { + return $this->_params; + } + + /** + * Use to set up a param tag for use in creating a paramgroup + * @static + */ + function getParam($name, $prompt, $type = 'string', $default = null) + { + if ($default !== null) { + return + array( + $this->_pkg->getTasksNs() . ':name' => $name, + $this->_pkg->getTasksNs() . ':prompt' => $prompt, + $this->_pkg->getTasksNs() . ':type' => $type, + $this->_pkg->getTasksNs() . ':default' => $default + ); + } + return + array( + $this->_pkg->getTasksNs() . ':name' => $name, + $this->_pkg->getTasksNs() . ':prompt' => $prompt, + $this->_pkg->getTasksNs() . ':type' => $type, + ); + } +} +?> + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Replace.php 276394 2009-02-25 00:15:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Task/Common.php'; +/** + * Implements the replace file task. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Replace extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGEANDINSTALL; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if (!isset($xml['attribs'])) { + return array(PEAR_TASK_ERROR_NOATTRIBS); + } + if (!isset($xml['attribs']['type'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'type'); + } + if (!isset($xml['attribs']['to'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'to'); + } + if (!isset($xml['attribs']['from'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'from'); + } + if ($xml['attribs']['type'] == 'pear-config') { + if (!in_array($xml['attribs']['to'], $config->getKeys())) { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + $config->getKeys()); + } + } elseif ($xml['attribs']['type'] == 'php-const') { + if (defined($xml['attribs']['to'])) { + return true; + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + array('valid PHP constant')); + } + } elseif ($xml['attribs']['type'] == 'package-info') { + if (in_array($xml['attribs']['to'], + array('name', 'summary', 'channel', 'notes', 'extends', 'description', + 'release_notes', 'license', 'release-license', 'license-uri', + 'version', 'api-version', 'state', 'api-state', 'release_date', + 'date', 'time'))) { + return true; + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + array('name', 'summary', 'channel', 'notes', 'extends', 'description', + 'release_notes', 'license', 'release-license', 'license-uri', + 'version', 'api-version', 'state', 'api-state', 'release_date', + 'date', 'time')); + } + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'type', $xml['attribs']['type'], + array('pear-config', 'package-info', 'php-const')); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + $this->_replacements = isset($xml['attribs']) ? array($xml) : $xml; + } + + /** + * Do a package.xml 1.0 replacement, with additional package-info fields available + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $subst_from = $subst_to = array(); + foreach ($this->_replacements as $a) { + $a = $a['attribs']; + $to = ''; + if ($a['type'] == 'pear-config') { + if ($this->installphase == PEAR_TASK_PACKAGE) { + return false; + } + if ($a['to'] == 'master_server') { + $chan = $this->registry->getChannel($pkg->getChannel()); + if (!PEAR::isError($chan)) { + $to = $chan->getServer(); + } else { + $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]"); + return false; + } + } else { + if ($this->config->isDefinedLayer('ftp')) { + // try the remote config file first + $to = $this->config->get($a['to'], 'ftp', $pkg->getChannel()); + if (is_null($to)) { + // then default to local + $to = $this->config->get($a['to'], null, $pkg->getChannel()); + } + } else { + $to = $this->config->get($a['to'], null, $pkg->getChannel()); + } + } + if (is_null($to)) { + $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]"); + return false; + } + } elseif ($a['type'] == 'php-const') { + if ($this->installphase == PEAR_TASK_PACKAGE) { + return false; + } + if (defined($a['to'])) { + $to = constant($a['to']); + } else { + $this->logger->log(0, "$dest: invalid php-const replacement: $a[to]"); + return false; + } + } else { + if ($t = $pkg->packageInfo($a['to'])) { + $to = $t; + } else { + $this->logger->log(0, "$dest: invalid package-info replacement: $a[to]"); + return false; + } + } + if (!is_null($to)) { + $subst_from[] = $a['from']; + $subst_to[] = $to; + } + } + $this->logger->log(3, "doing " . sizeof($subst_from) . + " substitution(s) for $dest"); + if (sizeof($subst_from)) { + $contents = str_replace($subst_from, $subst_to, $contents); + } + return $contents; + } +} +?> - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Task/Replace.php'; +/** + * Abstracts the replace task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Replace_rw extends PEAR_Task_Replace +{ + function PEAR_Task_Replace_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents); + } + + function setInfo($from, $to, $type) + { + $this->_params = array('attribs' => array('from' => $from, 'to' => $to, 'type' => $type)); + } + + function getName() + { + return 'replace'; + } + + function getXml() + { + return $this->_params; + } +} +?> + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Unixeol.php 276394 2009-02-25 00:15:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Task/Common.php'; +/** + * Implements the unix line endings file task. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Unixeol extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGE; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($xml != '') { + return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed'); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + } + + /** + * Replace all line endings with line endings customized for the current OS + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $this->logger->log(3, "replacing all line endings with \\n in $dest"); + return preg_replace("/\r\n|\n\r|\r|\n/", "\n", $contents); + } +} +?> - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Task/Unixeol.php'; +/** + * Abstracts the unixeol task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Unixeol_rw extends PEAR_Task_Unixeol +{ + function PEAR_Task_Unixeol_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return true; + } + + function getName() + { + return 'unixeol'; + } + + function getXml() + { + return ''; + } +} +?> + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Windowseol.php 276394 2009-02-25 00:15:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Task/Common.php'; +/** + * Implements the windows line endsings file task. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Windowseol extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGE; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($xml != '') { + return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed'); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + } + + /** + * Replace all line endings with windows line endings + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $this->logger->log(3, "replacing all line endings with \\r\\n in $dest"); + return preg_replace("/\r\n|\n\r|\r|\n/", "\r\n", $contents); + } +} +?> - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Task/Windowseol.php'; +/** + * Abstracts the windowseol task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Windowseol_rw extends PEAR_Task_Windowseol +{ + function PEAR_Task_Windowseol_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return true; + } + + function getName() + { + return 'windowseol'; + } + + function getXml() + { + return ''; + } +} +?> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Validate.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/**#@+ + * Constants for install stage + */ +define('PEAR_VALIDATE_INSTALLING', 1); +define('PEAR_VALIDATE_UNINSTALLING', 2); // this is not bit-mapped like the others +define('PEAR_VALIDATE_NORMAL', 3); +define('PEAR_VALIDATE_DOWNLOADING', 4); // this is not bit-mapped like the others +define('PEAR_VALIDATE_PACKAGING', 7); +/**#@-*/ +require_once 'phar://go-pear.phar/' . 'PEAR/Common.php'; +require_once 'phar://go-pear.phar/' . 'PEAR/Validator/PECL.php'; + +/** + * Validation class for package.xml - channel-level advanced validation + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Validate +{ + var $packageregex = _PEAR_COMMON_PACKAGE_NAME_PREG; + /** + * @var PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + var $_packagexml; + /** + * @var int one of the PEAR_VALIDATE_* constants + */ + var $_state = PEAR_VALIDATE_NORMAL; + /** + * Format: ('error' => array('field' => name, 'reason' => reason), 'warning' => same) + * @var array + * @access private + */ + var $_failures = array('error' => array(), 'warning' => array()); + + /** + * Override this method to handle validation of normal package names + * @param string + * @return bool + * @access protected + */ + function _validPackageName($name) + { + return (bool) preg_match('/^' . $this->packageregex . '\\z/', $name); + } + + /** + * @param string package name to validate + * @param string name of channel-specific validation package + * @final + */ + function validPackageName($name, $validatepackagename = false) + { + if ($validatepackagename) { + if (strtolower($name) == strtolower($validatepackagename)) { + return (bool) preg_match('/^[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*\\z/', $name); + } + } + return $this->_validPackageName($name); + } + + /** + * This validates a bundle name, and bundle names must conform + * to the PEAR naming convention, so the method is final and static. + * @param string + * @final + * @static + */ + function validGroupName($name) + { + return (bool) preg_match('/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/', $name); + } + + /** + * Determine whether $state represents a valid stability level + * @param string + * @return bool + * @static + * @final + */ + function validState($state) + { + return in_array($state, array('snapshot', 'devel', 'alpha', 'beta', 'stable')); + } + + /** + * Get a list of valid stability levels + * @return array + * @static + * @final + */ + function getValidStates() + { + return array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + } + + /** + * Determine whether a version is a properly formatted version number that can be used + * by version_compare + * @param string + * @return bool + * @static + * @final + */ + function validVersion($ver) + { + return (bool) preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver); + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function setPackageFile(&$pf) + { + $this->_packagexml = &$pf; + } + + /** + * @access private + */ + function _addFailure($field, $reason) + { + $this->_failures['errors'][] = array('field' => $field, 'reason' => $reason); + } + + /** + * @access private + */ + function _addWarning($field, $reason) + { + $this->_failures['warnings'][] = array('field' => $field, 'reason' => $reason); + } + + function getFailures() + { + $failures = $this->_failures; + $this->_failures = array('warnings' => array(), 'errors' => array()); + return $failures; + } + + /** + * @param int one of the PEAR_VALIDATE_* constants + */ + function validate($state = null) + { + if (!isset($this->_packagexml)) { + return false; + } + if ($state !== null) { + $this->_state = $state; + } + $this->_failures = array('warnings' => array(), 'errors' => array()); + $this->validatePackageName(); + $this->validateVersion(); + $this->validateMaintainers(); + $this->validateDate(); + $this->validateSummary(); + $this->validateDescription(); + $this->validateLicense(); + $this->validateNotes(); + if ($this->_packagexml->getPackagexmlVersion() == '1.0') { + $this->validateState(); + $this->validateFilelist(); + } elseif ($this->_packagexml->getPackagexmlVersion() == '2.0' || + $this->_packagexml->getPackagexmlVersion() == '2.1') { + $this->validateTime(); + $this->validateStability(); + $this->validateDeps(); + $this->validateMainFilelist(); + $this->validateReleaseFilelist(); + //$this->validateGlobalTasks(); + $this->validateChangelog(); + } + return !((bool) count($this->_failures['errors'])); + } + + /** + * @access protected + */ + function validatePackageName() + { + if ($this->_state == PEAR_VALIDATE_PACKAGING || + $this->_state == PEAR_VALIDATE_NORMAL) { + if (($this->_packagexml->getPackagexmlVersion() == '2.0' || + $this->_packagexml->getPackagexmlVersion() == '2.1') && + $this->_packagexml->getExtends()) { + $version = $this->_packagexml->getVersion() . ''; + $name = $this->_packagexml->getPackage(); + $test = array_shift($a = explode('.', $version)); + if ($test == '0') { + return true; + } + $vlen = strlen($test); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if ($majver != $test) { + $this->_addWarning('package', "package $name extends package " . + $this->_packagexml->getExtends() . ' and so the name should ' . + 'have a postfix equal to the major version like "' . + $this->_packagexml->getExtends() . $test . '"'); + return true; + } elseif (substr($name, 0, strlen($name) - $vlen) != + $this->_packagexml->getExtends()) { + $this->_addWarning('package', "package $name extends package " . + $this->_packagexml->getExtends() . ' and so the name must ' . + 'be an extension like "' . $this->_packagexml->getExtends() . + $test . '"'); + return true; + } + } + } + if (!$this->validPackageName($this->_packagexml->getPackage())) { + $this->_addFailure('name', 'package name "' . + $this->_packagexml->getPackage() . '" is invalid'); + return false; + } + } + + /** + * @access protected + */ + function validateVersion() + { + if ($this->_state != PEAR_VALIDATE_PACKAGING) { + if (!$this->validVersion($this->_packagexml->getVersion())) { + $this->_addFailure('version', + 'Invalid version number "' . $this->_packagexml->getVersion() . '"'); + } + return false; + } + $version = $this->_packagexml->getVersion(); + $versioncomponents = explode('.', $version); + if (count($versioncomponents) != 3) { + $this->_addWarning('version', + 'A version number should have 3 decimals (x.y.z)'); + return true; + } + $name = $this->_packagexml->getPackage(); + // version must be based upon state + switch ($this->_packagexml->getState()) { + case 'snapshot' : + return true; + case 'devel' : + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } else { + $this->_addWarning('version', + 'packages with devel stability must be < version 1.0.0'); + } + return true; + break; + case 'alpha' : + case 'beta' : + // check for a package that extends a package, + // like Foo and Foo2 + if ($this->_state == PEAR_VALIDATE_PACKAGING) { + if (substr($versioncomponents[2], 1, 2) == 'rc') { + $this->_addFailure('version', 'Release Candidate versions ' . + 'must have capital RC, not lower-case rc'); + return false; + } + } + if (!$this->_packagexml->getExtends()) { + if ($versioncomponents[0] == '1') { + if ($versioncomponents[2]{0} == '0') { + if ($versioncomponents[2] == '0') { + // version 1.*.0000 + $this->_addWarning('version', + 'version 1.' . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return true; + } elseif (strlen($versioncomponents[2]) > 1) { + // version 1.*.0RC1 or 1.*.0beta24 etc. + return true; + } else { + // version 1.*.0 + $this->_addWarning('version', + 'version 1.' . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return true; + } + } else { + $this->_addWarning('version', + 'bugfix versions (1.3.x where x > 0) probably should ' . + 'not be alpha or beta'); + return true; + } + } elseif ($versioncomponents[0] != '0') { + $this->_addWarning('version', + 'major versions greater than 1 are not allowed for packages ' . + 'without an tag or an identical postfix (foo2 v2.0.0)'); + return true; + } + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } + } else { + $vlen = strlen($versioncomponents[0] . ''); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) { + $this->_addWarning('version', 'first version number "' . + $versioncomponents[0] . '" must match the postfix of ' . + 'package name "' . $name . '" (' . + $majver . ')'); + return true; + } + if ($versioncomponents[0] == $majver) { + if ($versioncomponents[2]{0} == '0') { + if ($versioncomponents[2] == '0') { + // version 2.*.0000 + $this->_addWarning('version', + "version $majver." . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return false; + } elseif (strlen($versioncomponents[2]) > 1) { + // version 2.*.0RC1 or 2.*.0beta24 etc. + return true; + } else { + // version 2.*.0 + $this->_addWarning('version', + "version $majver." . $versioncomponents[1] . + '.0 cannot be alpha or beta'); + return true; + } + } else { + $this->_addWarning('version', + "bugfix versions ($majver.x.y where y > 0) should " . + 'not be alpha or beta'); + return true; + } + } elseif ($versioncomponents[0] != '0') { + $this->_addWarning('version', + "only versions 0.x.y and $majver.x.y are allowed for alpha/beta releases"); + return true; + } + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } + } + return true; + break; + case 'stable' : + if ($versioncomponents[0] == '0') { + $this->_addWarning('version', 'versions less than 1.0.0 cannot ' . + 'be stable'); + return true; + } + if (!is_numeric($versioncomponents[2])) { + if (preg_match('/\d+(rc|a|alpha|b|beta)\d*/i', + $versioncomponents[2])) { + $this->_addWarning('version', 'version "' . $version . '" or any ' . + 'RC/beta/alpha version cannot be stable'); + return true; + } + } + // check for a package that extends a package, + // like Foo and Foo2 + if ($this->_packagexml->getExtends()) { + $vlen = strlen($versioncomponents[0] . ''); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) { + $this->_addWarning('version', 'first version number "' . + $versioncomponents[0] . '" must match the postfix of ' . + 'package name "' . $name . '" (' . + $majver . ')'); + return true; + } + } elseif ($versioncomponents[0] > 1) { + $this->_addWarning('version', 'major version x in x.y.z may not be greater than ' . + '1 for any package that does not have an tag'); + } + return true; + break; + default : + return false; + break; + } + } + + /** + * @access protected + */ + function validateMaintainers() + { + // maintainers can only be truly validated server-side for most channels + // but allow this customization for those who wish it + return true; + } + + /** + * @access protected + */ + function validateDate() + { + if ($this->_state == PEAR_VALIDATE_NORMAL || + $this->_state == PEAR_VALIDATE_PACKAGING) { + + if (!preg_match('/(\d\d\d\d)\-(\d\d)\-(\d\d)/', + $this->_packagexml->getDate(), $res) || + count($res) < 4 + || !checkdate($res[2], $res[3], $res[1]) + ) { + $this->_addFailure('date', 'invalid release date "' . + $this->_packagexml->getDate() . '"'); + return false; + } + + if ($this->_state == PEAR_VALIDATE_PACKAGING && + $this->_packagexml->getDate() != date('Y-m-d')) { + $this->_addWarning('date', 'Release Date "' . + $this->_packagexml->getDate() . '" is not today'); + } + } + return true; + } + + /** + * @access protected + */ + function validateTime() + { + if (!$this->_packagexml->getTime()) { + // default of no time value set + return true; + } + + // packager automatically sets time, so only validate if pear validate is called + if ($this->_state = PEAR_VALIDATE_NORMAL) { + if (!preg_match('/\d\d:\d\d:\d\d/', + $this->_packagexml->getTime())) { + $this->_addFailure('time', 'invalid release time "' . + $this->_packagexml->getTime() . '"'); + return false; + } + + $result = preg_match('|\d{2}\:\d{2}\:\d{2}|', $this->_packagexml->getTime(), $matches); + if ($result === false || empty($matches)) { + $this->_addFailure('time', 'invalid release time "' . + $this->_packagexml->getTime() . '"'); + return false; + } + } + + return true; + } + + /** + * @access protected + */ + function validateState() + { + // this is the closest to "final" php4 can get + if (!PEAR_Validate::validState($this->_packagexml->getState())) { + if (strtolower($this->_packagexml->getState() == 'rc')) { + $this->_addFailure('state', 'RC is not a state, it is a version ' . + 'postfix, use ' . $this->_packagexml->getVersion() . 'RC1, state beta'); + } + $this->_addFailure('state', 'invalid release state "' . + $this->_packagexml->getState() . '", must be one of: ' . + implode(', ', PEAR_Validate::getValidStates())); + return false; + } + return true; + } + + /** + * @access protected + */ + function validateStability() + { + $ret = true; + $packagestability = $this->_packagexml->getState(); + $apistability = $this->_packagexml->getState('api'); + if (!PEAR_Validate::validState($packagestability)) { + $this->_addFailure('state', 'invalid release stability "' . + $this->_packagexml->getState() . '", must be one of: ' . + implode(', ', PEAR_Validate::getValidStates())); + $ret = false; + } + $apistates = PEAR_Validate::getValidStates(); + array_shift($apistates); // snapshot is not allowed + if (!in_array($apistability, $apistates)) { + $this->_addFailure('state', 'invalid API stability "' . + $this->_packagexml->getState('api') . '", must be one of: ' . + implode(', ', $apistates)); + $ret = false; + } + return $ret; + } + + /** + * @access protected + */ + function validateSummary() + { + return true; + } + + /** + * @access protected + */ + function validateDescription() + { + return true; + } + + /** + * @access protected + */ + function validateLicense() + { + return true; + } + + /** + * @access protected + */ + function validateNotes() + { + return true; + } + + /** + * for package.xml 2.0 only - channels can't use package.xml 1.0 + * @access protected + */ + function validateDependencies() + { + return true; + } + + /** + * for package.xml 1.0 only + * @access private + */ + function _validateFilelist() + { + return true; // placeholder for now + } + + /** + * for package.xml 2.0 only + * @access protected + */ + function validateMainFilelist() + { + return true; // placeholder for now + } + + /** + * for package.xml 2.0 only + * @access protected + */ + function validateReleaseFilelist() + { + return true; // placeholder for now + } + + /** + * @access protected + */ + function validateChangelog() + { + return true; + } + + /** + * @access protected + */ + function validateFilelist() + { + return true; + } + + /** + * @access protected + */ + function validateDeps() + { + return true; + } +} + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: PECL.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a5 + */ +/** + * This is the parent class for all validators + */ +require_once 'phar://go-pear.phar/' . 'PEAR/Validate.php'; +/** + * Channel Validator for the pecl.php.net channel + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a5 + */ +class PEAR_Validator_PECL extends PEAR_Validate +{ + function validateVersion() + { + if ($this->_state == PEAR_VALIDATE_PACKAGING) { + $version = $this->_packagexml->getVersion(); + $versioncomponents = explode('.', $version); + $last = array_pop($versioncomponents); + if (substr($last, 1, 2) == 'rc') { + $this->_addFailure('version', 'Release Candidate versions must have ' . + 'upper-case RC, not lower-case rc'); + return false; + } + } + return true; + } + + function validatePackageName() + { + $ret = parent::validatePackageName(); + if ($this->_packagexml->getPackageType() == 'extsrc' || + $this->_packagexml->getPackageType() == 'zendextsrc') { + if (strtolower($this->_packagexml->getPackage()) != + strtolower($this->_packagexml->getProvidesExtension())) { + $this->_addWarning('providesextension', 'package name "' . + $this->_packagexml->getPackage() . '" is different from extension name "' . + $this->_packagexml->getProvidesExtension() . '"'); + } + } + return $ret; + } +} +?> + * @author Stephan Schmidt (original XML_Unserializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: XMLParser.php 282970 2009-06-28 23:10:07Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Parser for any xml file + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Stephan Schmidt (original XML_Unserializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 1.9.0 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_XMLParser +{ + /** + * unserilialized data + * @var string $_serializedData + */ + var $_unserializedData = null; + + /** + * name of the root tag + * @var string $_root + */ + var $_root = null; + + /** + * stack for all data that is found + * @var array $_dataStack + */ + var $_dataStack = array(); + + /** + * stack for all values that are generated + * @var array $_valStack + */ + var $_valStack = array(); + + /** + * current tag depth + * @var int $_depth + */ + var $_depth = 0; + + /** + * The XML encoding to use + * @var string $encoding + */ + var $encoding = 'ISO-8859-1'; + + /** + * @return array + */ + function getData() + { + return $this->_unserializedData; + } + + /** + * @param string xml content + * @return true|PEAR_Error + */ + function parse($data) + { + if (!extension_loaded('xml')) { + include_once 'phar://go-pear.phar/' . 'PEAR.php'; + return PEAR::raiseError("XML Extension not found", 1); + } + $this->_dataStack = $this->_valStack = array(); + $this->_depth = 0; + + if ( + strpos($data, 'encoding="UTF-8"') + || strpos($data, 'encoding="utf-8"') + || strpos($data, "encoding='UTF-8'") + || strpos($data, "encoding='utf-8'") + ) { + $this->encoding = 'UTF-8'; + } + + if (version_compare(phpversion(), '5.0.0', 'lt') && $this->encoding == 'UTF-8') { + $data = utf8_decode($data); + $this->encoding = 'ISO-8859-1'; + } + + $xp = xml_parser_create($this->encoding); + xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, 0); + xml_set_object($xp, $this); + xml_set_element_handler($xp, 'startHandler', 'endHandler'); + xml_set_character_data_handler($xp, 'cdataHandler'); + if (!xml_parse($xp, $data)) { + $msg = xml_error_string(xml_get_error_code($xp)); + $line = xml_get_current_line_number($xp); + xml_parser_free($xp); + include_once 'phar://go-pear.phar/' . 'PEAR.php'; + return PEAR::raiseError("XML Error: '$msg' on line '$line'", 2); + } + xml_parser_free($xp); + return true; + } + + /** + * Start element handler for XML parser + * + * @access private + * @param object $parser XML parser object + * @param string $element XML element + * @param array $attribs attributes of XML tag + * @return void + */ + function startHandler($parser, $element, $attribs) + { + $this->_depth++; + $this->_dataStack[$this->_depth] = null; + + $val = array( + 'name' => $element, + 'value' => null, + 'type' => 'string', + 'childrenKeys' => array(), + 'aggregKeys' => array() + ); + + if (count($attribs) > 0) { + $val['children'] = array(); + $val['type'] = 'array'; + $val['children']['attribs'] = $attribs; + } + + array_push($this->_valStack, $val); + } + + /** + * post-process data + * + * @param string $data + * @param string $element element name + */ + function postProcess($data, $element) + { + return trim($data); + } + + /** + * End element handler for XML parser + * + * @access private + * @param object XML parser object + * @param string + * @return void + */ + function endHandler($parser, $element) + { + $value = array_pop($this->_valStack); + $data = $this->postProcess($this->_dataStack[$this->_depth], $element); + + // adjust type of the value + switch (strtolower($value['type'])) { + // unserialize an array + case 'array': + if ($data !== '') { + $value['children']['_content'] = $data; + } + + $value['value'] = isset($value['children']) ? $value['children'] : array(); + break; + + /* + * unserialize a null value + */ + case 'null': + $data = null; + break; + + /* + * unserialize any scalar value + */ + default: + settype($data, $value['type']); + $value['value'] = $data; + break; + } + + $parent = array_pop($this->_valStack); + if ($parent === null) { + $this->_unserializedData = &$value['value']; + $this->_root = &$value['name']; + return true; + } + + // parent has to be an array + if (!isset($parent['children']) || !is_array($parent['children'])) { + $parent['children'] = array(); + if ($parent['type'] != 'array') { + $parent['type'] = 'array'; + } + } + + if (!empty($value['name'])) { + // there already has been a tag with this name + if (in_array($value['name'], $parent['childrenKeys'])) { + // no aggregate has been created for this tag + if (!in_array($value['name'], $parent['aggregKeys'])) { + if (isset($parent['children'][$value['name']])) { + $parent['children'][$value['name']] = array($parent['children'][$value['name']]); + } else { + $parent['children'][$value['name']] = array(); + } + array_push($parent['aggregKeys'], $value['name']); + } + array_push($parent['children'][$value['name']], $value['value']); + } else { + $parent['children'][$value['name']] = &$value['value']; + array_push($parent['childrenKeys'], $value['name']); + } + } else { + array_push($parent['children'],$value['value']); + } + array_push($this->_valStack, $parent); + + $this->_depth--; + } + + /** + * Handler for character data + * + * @access private + * @param object XML parser object + * @param string CDATA + * @return void + */ + function cdataHandler($parser, $cdata) + { + $this->_dataStack[$this->_depth] .= $cdata; + } +} | +// +-----------------------------------------------------------------------------+ +// +/** + * The Graph.php file contains the definition of the Structures_Graph class + * + * @see Structures_Graph + * @package Structures_Graph + */ + +/* dependencies {{{ */ +/** PEAR base classes */ +require_once 'phar://go-pear.phar/' . 'PEAR.php'; +/** Graph Node */ +require_once 'phar://go-pear.phar/' . 'Structures/Graph/Node.php'; +/* }}} */ + +define('STRUCTURES_GRAPH_ERROR_GENERIC', 100); + +/* class Structures_Graph {{{ */ +/** + * The Structures_Graph class represents a graph data structure. + * + * A Graph is a data structure composed by a set of nodes, connected by arcs. + * Graphs may either be directed or undirected. In a directed graph, arcs are + * directional, and can be traveled only one way. In an undirected graph, arcs + * are bidirectional, and can be traveled both ways. + * + * @author Sérgio Carvalho + * @copyright (c) 2004 by Sérgio Carvalho + * @package Structures_Graph + */ +/* }}} */ +class Structures_Graph { + /* fields {{{ */ + /** + * @access private + */ + var $_nodes = array(); + /** + * @access private + */ + var $_directed = false; + /* }}} */ + + /* Constructor {{{ */ + /** + * + * Constructor + * + * @param boolean Set to true if the graph is directed. Set to false if it is not directed. (Optional, defaults to true) + * @access public + */ + function Structures_Graph($directed = true) { + $this->_directed = $directed; + } + /* }}} */ + + /* isDirected {{{ */ + /** + * + * Return true if a graph is directed + * + * @return boolean true if the graph is directed + * @access public + */ + function isDirected() { + return (boolean) $this->_directed; + } + /* }}} */ + + /* addNode {{{ */ + /** + * + * Add a Node to the Graph + * + * @param Structures_Graph_Node The node to be added. + * @access public + */ + function addNode(&$newNode) { + // We only add nodes + if (!is_a($newNode, 'Structures_Graph_Node')) return Pear::raiseError('Structures_Graph::addNode received an object that is not a Structures_Graph_Node', STRUCTURES_GRAPH_ERROR_GENERIC); + // Graphs are node *sets*, so duplicates are forbidden. We allow nodes that are exactly equal, but disallow equal references. + foreach($this->_nodes as $key => $node) { + /* + ZE1 equality operators choke on the recursive cycle introduced by the _graph field in the Node object. + So, we'll check references the hard way (change $this->_nodes[$key] and check if the change reflects in + $node) + */ + $savedData = $this->_nodes[$key]; + $referenceIsEqualFlag = false; + $this->_nodes[$key] = true; + if ($node === true) { + $this->_nodes[$key] = false; + if ($node === false) $referenceIsEqualFlag = true; + } + $this->_nodes[$key] = $savedData; + if ($referenceIsEqualFlag) return Pear::raiseError('Structures_Graph::addNode received an object that is a duplicate for this dataset', STRUCTURES_GRAPH_ERROR_GENERIC); + } + $this->_nodes[] =& $newNode; + $newNode->setGraph($this); + } + /* }}} */ + + /* removeNode (unimplemented) {{{ */ + /** + * + * Remove a Node from the Graph + * + * @todo This is unimplemented + * @param Structures_Graph_Node The node to be removed from the graph + * @access public + */ + function removeNode(&$node) { + } + /* }}} */ + + /* getNodes {{{ */ + /** + * + * Return the node set, in no particular order. For ordered node sets, use a Graph Manipulator insted. + * + * @access public + * @see Structures_Graph_Manipulator_TopologicalSorter + * @return array The set of nodes in this graph + */ + function &getNodes() { + return $this->_nodes; + } + /* }}} */ +} +?> + | +// +-----------------------------------------------------------------------------+ +// +/** + * This file contains the definition of the Structures_Graph_Manipulator_AcyclicTest graph manipulator. + * + * @see Structures_Graph_Manipulator_AcyclicTest + * @package Structures_Graph + */ + +/* dependencies {{{ */ +/** */ +require_once 'phar://go-pear.phar/' . 'PEAR.php'; +/** */ +require_once 'phar://go-pear.phar/' . 'Structures/Graph.php'; +/** */ +require_once 'phar://go-pear.phar/' . 'Structures/Graph/Node.php'; +/* }}} */ + +/* class Structures_Graph_Manipulator_AcyclicTest {{{ */ +/** + * The Structures_Graph_Manipulator_AcyclicTest is a graph manipulator + * which tests whether a graph contains a cycle. + * + * The definition of an acyclic graph used in this manipulator is that of a + * DAG. The graph must be directed, or else it is considered cyclic, even when + * there are no arcs. + * + * @author Sérgio Carvalho + * @copyright (c) 2004 by Sérgio Carvalho + * @package Structures_Graph + */ +class Structures_Graph_Manipulator_AcyclicTest { + /* _nonVisitedInDegree {{{ */ + /** + * + * This is a variant of Structures_Graph::inDegree which does + * not count nodes marked as visited. + * + * @access private + * @return integer Number of non-visited nodes that link to this one + */ + function _nonVisitedInDegree(&$node) { + $result = 0; + $graphNodes =& $node->_graph->getNodes(); + foreach (array_keys($graphNodes) as $key) { + if ((!$graphNodes[$key]->getMetadata('acyclic-test-visited')) && $graphNodes[$key]->connectsTo($node)) $result++; + } + return $result; + + } + /* }}} */ + + /* _isAcyclic {{{ */ + /** + * @access private + */ + function _isAcyclic(&$graph) { + // Mark every node as not visited + $nodes =& $graph->getNodes(); + $nodeKeys = array_keys($nodes); + $refGenerator = array(); + foreach($nodeKeys as $key) { + $refGenerator[] = false; + $nodes[$key]->setMetadata('acyclic-test-visited', $refGenerator[sizeof($refGenerator) - 1]); + } + + // Iteratively peel off leaf nodes + do { + // Find out which nodes are leafs (excluding visited nodes) + $leafNodes = array(); + foreach($nodeKeys as $key) { + if ((!$nodes[$key]->getMetadata('acyclic-test-visited')) && Structures_Graph_Manipulator_AcyclicTest::_nonVisitedInDegree($nodes[$key]) == 0) { + $leafNodes[] =& $nodes[$key]; + } + } + // Mark leafs as visited + for ($i=sizeof($leafNodes) - 1; $i>=0; $i--) { + $visited =& $leafNodes[$i]->getMetadata('acyclic-test-visited'); + $visited = true; + $leafNodes[$i]->setMetadata('acyclic-test-visited', $visited); + } + } while (sizeof($leafNodes) > 0); + + // If graph is a DAG, there should be no non-visited nodes. Let's try to prove otherwise + $result = true; + foreach($nodeKeys as $key) if (!$nodes[$key]->getMetadata('acyclic-test-visited')) $result = false; + + // Cleanup visited marks + foreach($nodeKeys as $key) $nodes[$key]->unsetMetadata('acyclic-test-visited'); + + return $result; + } + /* }}} */ + + /* isAcyclic {{{ */ + /** + * + * isAcyclic returns true if a graph contains no cycles, false otherwise. + * + * @return boolean true iff graph is acyclic + * @access public + */ + function isAcyclic(&$graph) { + // We only test graphs + if (!is_a($graph, 'Structures_Graph')) return Pear::raiseError('Structures_Graph_Manipulator_AcyclicTest::isAcyclic received an object that is not a Structures_Graph', STRUCTURES_GRAPH_ERROR_GENERIC); + if (!$graph->isDirected()) return false; // Only directed graphs may be acyclic + + return Structures_Graph_Manipulator_AcyclicTest::_isAcyclic($graph); + } + /* }}} */ +} +/* }}} */ +?> + | +// +-----------------------------------------------------------------------------+ +// +/** + * This file contains the definition of the Structures_Graph_Manipulator_TopologicalSorter class. + * + * @see Structures_Graph_Manipulator_TopologicalSorter + * @package Structures_Graph + */ + +/* dependencies {{{ */ +/** */ +require_once 'phar://go-pear.phar/' . 'PEAR.php'; +/** */ +require_once 'phar://go-pear.phar/' . 'Structures/Graph.php'; +/** */ +require_once 'phar://go-pear.phar/' . 'Structures/Graph/Node.php'; +/** */ +require_once 'phar://go-pear.phar/' . 'Structures/Graph/Manipulator/AcyclicTest.php'; +/* }}} */ + +/* class Structures_Graph_Manipulator_TopologicalSorter {{{ */ +/** + * The Structures_Graph_Manipulator_TopologicalSorter is a manipulator + * which is able to return the set of nodes in a graph, sorted by topological + * order. + * + * A graph may only be sorted topologically iff it's a DAG. You can test it + * with the Structures_Graph_Manipulator_AcyclicTest. + * + * @author Sérgio Carvalho + * @copyright (c) 2004 by Sérgio Carvalho + * @see Structures_Graph_Manipulator_AcyclicTest + * @package Structures_Graph + */ +class Structures_Graph_Manipulator_TopologicalSorter { + /* _nonVisitedInDegree {{{ */ + /** + * + * This is a variant of Structures_Graph::inDegree which does + * not count nodes marked as visited. + * + * @access private + * @return integer Number of non-visited nodes that link to this one + */ + function _nonVisitedInDegree(&$node) { + $result = 0; + $graphNodes =& $node->_graph->getNodes(); + foreach (array_keys($graphNodes) as $key) { + if ((!$graphNodes[$key]->getMetadata('topological-sort-visited')) && $graphNodes[$key]->connectsTo($node)) $result++; + } + return $result; + + } + /* }}} */ + + /* _sort {{{ */ + /** + * @access private + */ + function _sort(&$graph) { + // Mark every node as not visited + $nodes =& $graph->getNodes(); + $nodeKeys = array_keys($nodes); + $refGenerator = array(); + foreach($nodeKeys as $key) { + $refGenerator[] = false; + $nodes[$key]->setMetadata('topological-sort-visited', $refGenerator[sizeof($refGenerator) - 1]); + } + + // Iteratively peel off leaf nodes + $topologicalLevel = 0; + do { + // Find out which nodes are leafs (excluding visited nodes) + $leafNodes = array(); + foreach($nodeKeys as $key) { + if ((!$nodes[$key]->getMetadata('topological-sort-visited')) && Structures_Graph_Manipulator_TopologicalSorter::_nonVisitedInDegree($nodes[$key]) == 0) { + $leafNodes[] =& $nodes[$key]; + } + } + // Mark leafs as visited + $refGenerator[] = $topologicalLevel; + for ($i=sizeof($leafNodes) - 1; $i>=0; $i--) { + $visited =& $leafNodes[$i]->getMetadata('topological-sort-visited'); + $visited = true; + $leafNodes[$i]->setMetadata('topological-sort-visited', $visited); + $leafNodes[$i]->setMetadata('topological-sort-level', $refGenerator[sizeof($refGenerator) - 1]); + } + $topologicalLevel++; + } while (sizeof($leafNodes) > 0); + + // Cleanup visited marks + foreach($nodeKeys as $key) $nodes[$key]->unsetMetadata('topological-sort-visited'); + } + /* }}} */ + + /* sort {{{ */ + /** + * + * sort returns the graph's nodes, sorted by topological order. + * + * The result is an array with + * as many entries as topological levels. Each entry in this array is an array of nodes within + * the given topological level. + * + * @return array The graph's nodes, sorted by topological order. + * @access public + */ + function sort(&$graph) { + // We only sort graphs + if (!is_a($graph, 'Structures_Graph')) return Pear::raiseError('Structures_Graph_Manipulator_TopologicalSorter::sort received an object that is not a Structures_Graph', STRUCTURES_GRAPH_ERROR_GENERIC); + if (!Structures_Graph_Manipulator_AcyclicTest::isAcyclic($graph)) return Pear::raiseError('Structures_Graph_Manipulator_TopologicalSorter::sort received an graph that has cycles', STRUCTURES_GRAPH_ERROR_GENERIC); + + Structures_Graph_Manipulator_TopologicalSorter::_sort($graph); + $result = array(); + + // Fill out result array + $nodes =& $graph->getNodes(); + $nodeKeys = array_keys($nodes); + foreach($nodeKeys as $key) { + if (!array_key_exists($nodes[$key]->getMetadata('topological-sort-level'), $result)) $result[$nodes[$key]->getMetadata('topological-sort-level')] = array(); + $result[$nodes[$key]->getMetadata('topological-sort-level')][] =& $nodes[$key]; + $nodes[$key]->unsetMetadata('topological-sort-level'); + } + + return $result; + } + /* }}} */ +} +/* }}} */ +?> + | +// +-----------------------------------------------------------------------------+ +// +/** + * This file contains the definition of the Structures_Graph_Node class + * + * @see Structures_Graph_Node + * @package Structures_Graph + */ + +/* dependencies {{{ */ +/** */ +require_once 'phar://go-pear.phar/' . 'PEAR.php'; +/** */ +require_once 'phar://go-pear.phar/' . 'Structures/Graph.php'; +/* }}} */ + +/* class Structures_Graph_Node {{{ */ +/** + * The Structures_Graph_Node class represents a Node that can be member of a + * graph node set. + * + * A graph node can contain data. Under this API, the node contains default data, + * and key index data. It behaves, thus, both as a regular data node, and as a + * dictionary (or associative array) node. + * + * Regular data is accessed via getData and setData. Key indexed data is accessed + * via getMetadata and setMetadata. + * + * @author Sérgio Carvalho + * @copyright (c) 2004 by Sérgio Carvalho + * @package Structures_Graph + */ +/* }}} */ +class Structures_Graph_Node { + /* fields {{{ */ + /** + * @access private + */ + var $_data = null; + /** @access private */ + var $_metadata = array(); + /** @access private */ + var $_arcs = array(); + /** @access private */ + var $_graph = null; + /* }}} */ + + /* Constructor {{{ */ + /** + * + * Constructor + * + * @access public + */ + function Structures_Graph_Node() { + } + /* }}} */ + + /* getGraph {{{ */ + /** + * + * Node graph getter + * + * @return Structures_Graph Graph where node is stored + * @access public + */ + function &getGraph() { + return $this->_graph; + } + /* }}} */ + + /* setGraph {{{ */ + /** + * + * Node graph setter. This method should not be called directly. Use Graph::addNode instead. + * + * @param Structures_Graph Set the graph for this node. + * @see Structures_Graph::addNode() + * @access public + */ + function setGraph(&$graph) { + $this->_graph =& $graph; + } + /* }}} */ + + /* getData {{{ */ + /** + * + * Node data getter. + * + * Each graph node can contain a reference to one variable. This is the getter for that reference. + * + * @return mixed Data stored in node + * @access public + */ + function &getData() { + return $this->_data; + } + /* }}} */ + + /* setData {{{ */ + /** + * + * Node data setter + * + * Each graph node can contain a reference to one variable. This is the setter for that reference. + * + * @return mixed Data to store in node + * @access public + */ + function setData($data) { + $this->_data =& $data; + } + /* }}} */ + + /* metadataKeyExists {{{ */ + /** + * + * Test for existence of metadata under a given key. + * + * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an + * associative array or in a dictionary. This method tests whether a given metadata key exists for this node. + * + * @param string Key to test + * @return boolean + * @access public + */ + function metadataKeyExists($key) { + return array_key_exists($key, $this->_metadata); + } + /* }}} */ + + /* getMetadata {{{ */ + /** + * + * Node metadata getter + * + * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an + * associative array or in a dictionary. This method gets the data under the given key. If the key does + * not exist, an error will be thrown, so testing using metadataKeyExists might be needed. + * + * @param string Key + * @param boolean nullIfNonexistent (defaults to false). + * @return mixed Metadata Data stored in node under given key + * @see metadataKeyExists + * @access public + */ + function &getMetadata($key, $nullIfNonexistent = false) { + if (array_key_exists($key, $this->_metadata)) { + return $this->_metadata[$key]; + } else { + if ($nullIfNonexistent) { + $a = null; + return $a; + } else { + $a = Pear::raiseError('Structures_Graph_Node::getMetadata: Requested key does not exist', STRUCTURES_GRAPH_ERROR_GENERIC); + return $a; + } + } + } + /* }}} */ + + /* unsetMetadata {{{ */ + /** + * + * Delete metadata by key + * + * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an + * associative array or in a dictionary. This method removes any data that might be stored under the provided key. + * If the key does not exist, no error is thrown, so it is safe using this method without testing for key existence. + * + * @param string Key + * @access public + */ + function unsetMetadata($key) { + if (array_key_exists($key, $this->_metadata)) unset($this->_metadata[$key]); + } + /* }}} */ + + /* setMetadata {{{ */ + /** + * + * Node metadata setter + * + * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an + * associative array or in a dictionary. This method stores data under the given key. If the key already exists, + * previously stored data is discarded. + * + * @param string Key + * @param mixed Data + * @access public + */ + function setMetadata($key, $data) { + $this->_metadata[$key] =& $data; + } + /* }}} */ + + /* _connectTo {{{ */ + /** @access private */ + function _connectTo(&$destinationNode) { + $this->_arcs[] =& $destinationNode; + } + /* }}} */ + + /* connectTo {{{ */ + /** + * + * Connect this node to another one. + * + * If the graph is not directed, the reverse arc, connecting $destinationNode to $this is also created. + * + * @param Structures_Graph_Node Node to connect to + * @access public + */ + function connectTo(&$destinationNode) { + // We only connect to nodes + if (!is_a($destinationNode, 'Structures_Graph_Node')) return Pear::raiseError('Structures_Graph_Node::connectTo received an object that is not a Structures_Graph_Node', STRUCTURES_GRAPH_ERROR_GENERIC); + // Nodes must already be in graphs to be connected + if ($this->_graph == null) return Pear::raiseError('Structures_Graph_Node::connectTo Tried to connect a node that is not in a graph', STRUCTURES_GRAPH_ERROR_GENERIC); + if ($destinationNode->getGraph() == null) return Pear::raiseError('Structures_Graph_Node::connectTo Tried to connect to a node that is not in a graph', STRUCTURES_GRAPH_ERROR_GENERIC); + // Connect here + $this->_connectTo($destinationNode); + // If graph is undirected, connect back + if (!$this->_graph->isDirected()) { + $destinationNode->_connectTo($this); + } + } + /* }}} */ + + /* getNeighbours {{{ */ + /** + * + * Return nodes connected to this one. + * + * @return array Array of nodes + * @access public + */ + function getNeighbours() { + return $this->_arcs; + } + /* }}} */ + + /* connectsTo {{{ */ + /** + * + * Test wether this node has an arc to the target node + * + * @return boolean True if the two nodes are connected + * @access public + */ + function connectsTo(&$target) { + $copy = $target; + $arcKeys = array_keys($this->_arcs); + foreach($arcKeys as $key) { + /* ZE1 chokes on this expression: + if ($target === $arc) return true; + so, we'll use more convoluted stuff + */ + $arc =& $this->_arcs[$key]; + $target = true; + if ($arc === true) { + $target = false; + if ($arc === false) { + $target = $copy; + return true; + } + } + } + $target = $copy; + return false; + } + /* }}} */ + + /* inDegree {{{ */ + /** + * + * Calculate the in degree of the node. + * + * The indegree for a node is the number of arcs entering the node. For non directed graphs, + * the indegree is equal to the outdegree. + * + * @return integer In degree of the node + * @access public + */ + function inDegree() { + if ($this->_graph == null) return 0; + if (!$this->_graph->isDirected()) return $this->outDegree(); + $result = 0; + $graphNodes =& $this->_graph->getNodes(); + foreach (array_keys($graphNodes) as $key) { + if ($graphNodes[$key]->connectsTo($this)) $result++; + } + return $result; + + } + /* }}} */ + + /* outDegree {{{ */ + /** + * + * Calculate the out degree of the node. + * + * The outdegree for a node is the number of arcs exiting the node. For non directed graphs, + * the outdegree is always equal to the indegree. + * + * @return integer Out degree of the node + * @access public + */ + function outDegree() { + if ($this->_graph == null) return 0; + return sizeof($this->_arcs); + } + /* }}} */ +} +?> + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: System.php 276386 2009-02-24 23:52:56Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'phar://go-pear.phar/' . 'PEAR.php'; +require_once 'phar://go-pear.phar/' . 'Console/Getopt.php'; + +$GLOBALS['_System_temp_files'] = array(); + +/** +* System offers cross plattform compatible system functions +* +* Static functions for different operations. Should work under +* Unix and Windows. The names and usage has been taken from its respectively +* GNU commands. The functions will return (bool) false on error and will +* trigger the error with the PHP trigger_error() function (you can silence +* the error by prefixing a '@' sign after the function call, but this +* is not recommended practice. Instead use an error handler with +* {@link set_error_handler()}). +* +* Documentation on this class you can find in: +* http://pear.php.net/manual/ +* +* Example usage: +* if (!@System::rm('-r file1 dir1')) { +* print "could not delete file1 or dir1"; +* } +* +* In case you need to to pass file names with spaces, +* pass the params as an array: +* +* System::rm(array('-r', $file1, $dir1)); +* +* @category pear +* @package System +* @author Tomas V.V. Cox +* @copyright 1997-2006 The PHP Group +* @license http://opensource.org/licenses/bsd-license.php New BSD License +* @version Release: 1.9.0 +* @link http://pear.php.net/package/PEAR +* @since Class available since Release 0.1 +* @static +*/ +class System +{ + /** + * returns the commandline arguments of a function + * + * @param string $argv the commandline + * @param string $short_options the allowed option short-tags + * @param string $long_options the allowed option long-tags + * @return array the given options and there values + * @static + * @access private + */ + function _parseArgs($argv, $short_options, $long_options = null) + { + if (!is_array($argv) && $argv !== null) { + $argv = preg_split('/\s+/', $argv, -1, PREG_SPLIT_NO_EMPTY); + } + return Console_Getopt::getopt2($argv, $short_options); + } + + /** + * Output errors with PHP trigger_error(). You can silence the errors + * with prefixing a "@" sign to the function call: @System::mkdir(..); + * + * @param mixed $error a PEAR error or a string with the error message + * @return bool false + * @static + * @access private + */ + function raiseError($error) + { + if (PEAR::isError($error)) { + $error = $error->getMessage(); + } + trigger_error($error, E_USER_WARNING); + return false; + } + + /** + * Creates a nested array representing the structure of a directory + * + * System::_dirToStruct('dir1', 0) => + * Array + * ( + * [dirs] => Array + * ( + * [0] => dir1 + * ) + * + * [files] => Array + * ( + * [0] => dir1/file2 + * [1] => dir1/file3 + * ) + * ) + * @param string $sPath Name of the directory + * @param integer $maxinst max. deep of the lookup + * @param integer $aktinst starting deep of the lookup + * @param bool $silent if true, do not emit errors. + * @return array the structure of the dir + * @static + * @access private + */ + function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false) + { + $struct = array('dirs' => array(), 'files' => array()); + if (($dir = @opendir($sPath)) === false) { + if (!$silent) { + System::raiseError("Could not open dir $sPath"); + } + return $struct; // XXX could not open error + } + + $struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ? + $list = array(); + while (false !== ($file = readdir($dir))) { + if ($file != '.' && $file != '..') { + $list[] = $file; + } + } + + closedir($dir); + natsort($list); + if ($aktinst < $maxinst || $maxinst == 0) { + foreach ($list as $val) { + $path = $sPath . DIRECTORY_SEPARATOR . $val; + if (is_dir($path) && !is_link($path)) { + $tmp = System::_dirToStruct($path, $maxinst, $aktinst+1, $silent); + $struct = array_merge_recursive($struct, $tmp); + } else { + $struct['files'][] = $path; + } + } + } + + return $struct; + } + + /** + * Creates a nested array representing the structure of a directory and files + * + * @param array $files Array listing files and dirs + * @return array + * @static + * @see System::_dirToStruct() + */ + function _multipleToStruct($files) + { + $struct = array('dirs' => array(), 'files' => array()); + settype($files, 'array'); + foreach ($files as $file) { + if (is_dir($file) && !is_link($file)) { + $tmp = System::_dirToStruct($file, 0); + $struct = array_merge_recursive($tmp, $struct); + } else { + if (!in_array($file, $struct['files'])) { + $struct['files'][] = $file; + } + } + } + return $struct; + } + + /** + * The rm command for removing files. + * Supports multiple files and dirs and also recursive deletes + * + * @param string $args the arguments for rm + * @return mixed PEAR_Error or true for success + * @static + * @access public + */ + function rm($args) + { + $opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-) + if (PEAR::isError($opts)) { + return System::raiseError($opts); + } + foreach ($opts[0] as $opt) { + if ($opt[0] == 'r') { + $do_recursive = true; + } + } + $ret = true; + if (isset($do_recursive)) { + $struct = System::_multipleToStruct($opts[1]); + foreach ($struct['files'] as $file) { + if (!@unlink($file)) { + $ret = false; + } + } + + rsort($struct['dirs']); + foreach ($struct['dirs'] as $dir) { + if (!@rmdir($dir)) { + $ret = false; + } + } + } else { + foreach ($opts[1] as $file) { + $delete = (is_dir($file)) ? 'rmdir' : 'unlink'; + if (!@$delete($file)) { + $ret = false; + } + } + } + return $ret; + } + + /** + * Make directories. + * + * The -p option will create parent directories + * @param string $args the name of the director(y|ies) to create + * @return bool True for success + * @static + * @access public + */ + function mkDir($args) + { + $opts = System::_parseArgs($args, 'pm:'); + if (PEAR::isError($opts)) { + return System::raiseError($opts); + } + + $mode = 0777; // default mode + foreach ($opts[0] as $opt) { + if ($opt[0] == 'p') { + $create_parents = true; + } elseif ($opt[0] == 'm') { + // if the mode is clearly an octal number (starts with 0) + // convert it to decimal + if (strlen($opt[1]) && $opt[1]{0} == '0') { + $opt[1] = octdec($opt[1]); + } else { + // convert to int + $opt[1] += 0; + } + $mode = $opt[1]; + } + } + + $ret = true; + if (isset($create_parents)) { + foreach ($opts[1] as $dir) { + $dirstack = array(); + while ((!file_exists($dir) || !is_dir($dir)) && + $dir != DIRECTORY_SEPARATOR) { + array_unshift($dirstack, $dir); + $dir = dirname($dir); + } + + while ($newdir = array_shift($dirstack)) { + if (!is_writeable(dirname($newdir))) { + $ret = false; + break; + } + + if (!mkdir($newdir, $mode)) { + $ret = false; + } + } + } + } else { + foreach($opts[1] as $dir) { + if ((@file_exists($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) { + $ret = false; + } + } + } + + return $ret; + } + + /** + * Concatenate files + * + * Usage: + * 1) $var = System::cat('sample.txt test.txt'); + * 2) System::cat('sample.txt test.txt > final.txt'); + * 3) System::cat('sample.txt test.txt >> final.txt'); + * + * Note: as the class use fopen, urls should work also (test that) + * + * @param string $args the arguments + * @return boolean true on success + * @static + * @access public + */ + function &cat($args) + { + $ret = null; + $files = array(); + if (!is_array($args)) { + $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY); + } + + $count_args = count($args); + for ($i = 0; $i < $count_args; $i++) { + if ($args[$i] == '>') { + $mode = 'wb'; + $outputfile = $args[$i+1]; + break; + } elseif ($args[$i] == '>>') { + $mode = 'ab+'; + $outputfile = $args[$i+1]; + break; + } else { + $files[] = $args[$i]; + } + } + $outputfd = false; + if (isset($mode)) { + if (!$outputfd = fopen($outputfile, $mode)) { + $err = System::raiseError("Could not open $outputfile"); + return $err; + } + $ret = true; + } + foreach ($files as $file) { + if (!$fd = fopen($file, 'r')) { + System::raiseError("Could not open $file"); + continue; + } + while ($cont = fread($fd, 2048)) { + if (is_resource($outputfd)) { + fwrite($outputfd, $cont); + } else { + $ret .= $cont; + } + } + fclose($fd); + } + if (is_resource($outputfd)) { + fclose($outputfd); + } + return $ret; + } + + /** + * Creates temporary files or directories. This function will remove + * the created files when the scripts finish its execution. + * + * Usage: + * 1) $tempfile = System::mktemp("prefix"); + * 2) $tempdir = System::mktemp("-d prefix"); + * 3) $tempfile = System::mktemp(); + * 4) $tempfile = System::mktemp("-t /var/tmp prefix"); + * + * prefix -> The string that will be prepended to the temp name + * (defaults to "tmp"). + * -d -> A temporary dir will be created instead of a file. + * -t -> The target dir where the temporary (file|dir) will be created. If + * this param is missing by default the env vars TMP on Windows or + * TMPDIR in Unix will be used. If these vars are also missing + * c:\windows\temp or /tmp will be used. + * + * @param string $args The arguments + * @return mixed the full path of the created (file|dir) or false + * @see System::tmpdir() + * @static + * @access public + */ + function mktemp($args = null) + { + static $first_time = true; + $opts = System::_parseArgs($args, 't:d'); + if (PEAR::isError($opts)) { + return System::raiseError($opts); + } + + foreach ($opts[0] as $opt) { + if ($opt[0] == 'd') { + $tmp_is_dir = true; + } elseif ($opt[0] == 't') { + $tmpdir = $opt[1]; + } + } + + $prefix = (isset($opts[1][0])) ? $opts[1][0] : 'tmp'; + if (!isset($tmpdir)) { + $tmpdir = System::tmpdir(); + } + + if (!System::mkDir(array('-p', $tmpdir))) { + return false; + } + + $tmp = tempnam($tmpdir, $prefix); + if (isset($tmp_is_dir)) { + unlink($tmp); // be careful possible race condition here + if (!mkdir($tmp, 0700)) { + return System::raiseError("Unable to create temporary directory $tmpdir"); + } + } + + $GLOBALS['_System_temp_files'][] = $tmp; + if (isset($tmp_is_dir)) { + //$GLOBALS['_System_temp_files'][] = dirname($tmp); + } + + if ($first_time) { + PEAR::registerShutdownFunc(array('System', '_removeTmpFiles')); + $first_time = false; + } + + return $tmp; + } + + /** + * Remove temporary files created my mkTemp. This function is executed + * at script shutdown time + * + * @static + * @access private + */ + function _removeTmpFiles() + { + if (count($GLOBALS['_System_temp_files'])) { + $delete = $GLOBALS['_System_temp_files']; + array_unshift($delete, '-r'); + System::rm($delete); + $GLOBALS['_System_temp_files'] = array(); + } + } + + /** + * Get the path of the temporal directory set in the system + * by looking in its environments variables. + * Note: php.ini-recommended removes the "E" from the variables_order setting, + * making unavaible the $_ENV array, that s why we do tests with _ENV + * + * @static + * @return string The temporary directory on the system + */ + function tmpdir() + { + if (OS_WINDOWS) { + if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) { + return $var; + } + if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) { + return $var; + } + if ($var = isset($_ENV['USERPROFILE']) ? $_ENV['USERPROFILE'] : getenv('USERPROFILE')) { + return $var; + } + if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) { + return $var; + } + return getenv('SystemRoot') . '\temp'; + } + if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) { + return $var; + } + return realpath('/tmp'); + } + + /** + * The "which" command (show the full path of a command) + * + * @param string $program The command to search for + * @param mixed $fallback Value to return if $program is not found + * + * @return mixed A string with the full path or false if not found + * @static + * @author Stig Bakken + */ + function which($program, $fallback = false) + { + // enforce API + if (!is_string($program) || '' == $program) { + return $fallback; + } + + // full path given + if (basename($program) != $program) { + $path_elements[] = dirname($program); + $program = basename($program); + } else { + // Honor safe mode + if (!ini_get('safe_mode') || !$path = ini_get('safe_mode_exec_dir')) { + $path = getenv('PATH'); + if (!$path) { + $path = getenv('Path'); // some OSes are just stupid enough to do this + } + } + $path_elements = explode(PATH_SEPARATOR, $path); + } + + if (OS_WINDOWS) { + $exe_suffixes = getenv('PATHEXT') + ? explode(PATH_SEPARATOR, getenv('PATHEXT')) + : array('.exe','.bat','.cmd','.com'); + // allow passing a command.exe param + if (strpos($program, '.') !== false) { + array_unshift($exe_suffixes, ''); + } + // is_executable() is not available on windows for PHP4 + $pear_is_executable = (function_exists('is_executable')) ? 'is_executable' : 'is_file'; + } else { + $exe_suffixes = array(''); + $pear_is_executable = 'is_executable'; + } + + foreach ($exe_suffixes as $suff) { + foreach ($path_elements as $dir) { + $file = $dir . DIRECTORY_SEPARATOR . $program . $suff; + if (@$pear_is_executable($file)) { + return $file; + } + } + } + return $fallback; + } + + /** + * The "find" command + * + * Usage: + * + * System::find($dir); + * System::find("$dir -type d"); + * System::find("$dir -type f"); + * System::find("$dir -name *.php"); + * System::find("$dir -name *.php -name *.htm*"); + * System::find("$dir -maxdepth 1"); + * + * Params implmented: + * $dir -> Start the search at this directory + * -type d -> return only directories + * -type f -> return only files + * -maxdepth -> max depth of recursion + * -name -> search pattern (bash style). Multiple -name param allowed + * + * @param mixed Either array or string with the command line + * @return array Array of found files + * @static + * + */ + function find($args) + { + if (!is_array($args)) { + $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY); + } + $dir = realpath(array_shift($args)); + if (!$dir) { + return array(); + } + $patterns = array(); + $depth = 0; + $do_files = $do_dirs = true; + $args_count = count($args); + for ($i = 0; $i < $args_count; $i++) { + switch ($args[$i]) { + case '-type': + if (in_array($args[$i+1], array('d', 'f'))) { + if ($args[$i+1] == 'd') { + $do_files = false; + } else { + $do_dirs = false; + } + } + $i++; + break; + case '-name': + $name = preg_quote($args[$i+1], '#'); + // our magic characters ? and * have just been escaped, + // so now we change the escaped versions to PCRE operators + $name = strtr($name, array('\?' => '.', '\*' => '.*')); + $patterns[] = '('.$name.')'; + $i++; + break; + case '-maxdepth': + $depth = $args[$i+1]; + break; + } + } + $path = System::_dirToStruct($dir, $depth, 0, true); + if ($do_files && $do_dirs) { + $files = array_merge($path['files'], $path['dirs']); + } elseif ($do_dirs) { + $files = $path['dirs']; + } else { + $files = $path['files']; + } + if (count($patterns)) { + $dsq = preg_quote(DIRECTORY_SEPARATOR, '#'); + $pattern = '#(^|'.$dsq.')'.implode('|', $patterns).'($|'.$dsq.')#'; + $ret = array(); + $files_count = count($files); + for ($i = 0; $i < $files_count; $i++) { + // only search in the part of the file below the current directory + $filepart = basename($files[$i]); + if (preg_match($pattern, $filepart)) { + $ret[] = $files[$i]; + } + } + return $ret; + } + return $files; + } +} + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Util + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Util.php 275418 2009-02-09 14:23:42Z ashnazg $ + * @link http://pear.php.net/package/XML_Util + */ + +/** + * error code for invalid chars in XML name + */ +define('XML_UTIL_ERROR_INVALID_CHARS', 51); + +/** + * error code for invalid chars in XML name + */ +define('XML_UTIL_ERROR_INVALID_START', 52); + +/** + * error code for non-scalar tag content + */ +define('XML_UTIL_ERROR_NON_SCALAR_CONTENT', 60); + +/** + * error code for missing tag name + */ +define('XML_UTIL_ERROR_NO_TAG_NAME', 61); + +/** + * replace XML entities + */ +define('XML_UTIL_REPLACE_ENTITIES', 1); + +/** + * embedd content in a CData Section + */ +define('XML_UTIL_CDATA_SECTION', 5); + +/** + * do not replace entitites + */ +define('XML_UTIL_ENTITIES_NONE', 0); + +/** + * replace all XML entitites + * This setting will replace <, >, ", ' and & + */ +define('XML_UTIL_ENTITIES_XML', 1); + +/** + * replace only required XML entitites + * This setting will replace <, " and & + */ +define('XML_UTIL_ENTITIES_XML_REQUIRED', 2); + +/** + * replace HTML entitites + * @link http://www.php.net/htmlentities + */ +define('XML_UTIL_ENTITIES_HTML', 3); + +/** + * Collapse all empty tags. + */ +define('XML_UTIL_COLLAPSE_ALL', 1); + +/** + * Collapse only empty XHTML tags that have no end tag. + */ +define('XML_UTIL_COLLAPSE_XHTML_ONLY', 2); + +/** + * utility class for working with XML documents + * + + * @category XML + * @package XML_Util + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: @version@ + * @link http://pear.php.net/package/XML_Util + */ +class XML_Util +{ + /** + * return API version + * + * @return string $version API version + * @access public + * @static + */ + function apiVersion() + { + return '1.1'; + } + + /** + * replace XML entities + * + * With the optional second parameter, you may select, which + * entities should be replaced. + * + * + * require_once 'XML/Util.php'; + * + * // replace XML entites: + * $string = XML_Util::replaceEntities('This string contains < & >.'); + * + * + * With the optional third parameter, you may pass the character encoding + * + * require_once 'XML/Util.php'; + * + * // replace XML entites in UTF-8: + * $string = XML_Util::replaceEntities( + * 'This string contains < & > as well as ä, ö, ß, à and ê', + * XML_UTIL_ENTITIES_HTML, + * 'UTF-8' + * ); + * + * + * @param string $string string where XML special chars + * should be replaced + * @param int $replaceEntities setting for entities in attribute values + * (one of XML_UTIL_ENTITIES_XML, + * XML_UTIL_ENTITIES_XML_REQUIRED, + * XML_UTIL_ENTITIES_HTML) + * @param string $encoding encoding value (if any)... + * must be a valid encoding as determined + * by the htmlentities() function + * + * @return string string with replaced chars + * @access public + * @static + * @see reverseEntities() + */ + function replaceEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML, + $encoding = 'ISO-8859-1') + { + switch ($replaceEntities) { + case XML_UTIL_ENTITIES_XML: + return strtr($string, array( + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + '\'' => ''' )); + break; + case XML_UTIL_ENTITIES_XML_REQUIRED: + return strtr($string, array( + '&' => '&', + '<' => '<', + '"' => '"' )); + break; + case XML_UTIL_ENTITIES_HTML: + return htmlentities($string, ENT_COMPAT, $encoding); + break; + } + return $string; + } + + /** + * reverse XML entities + * + * With the optional second parameter, you may select, which + * entities should be reversed. + * + * + * require_once 'XML/Util.php'; + * + * // reverse XML entites: + * $string = XML_Util::reverseEntities('This string contains < & >.'); + * + * + * With the optional third parameter, you may pass the character encoding + * + * require_once 'XML/Util.php'; + * + * // reverse XML entites in UTF-8: + * $string = XML_Util::reverseEntities( + * 'This string contains < & > as well as' + * . ' ä, ö, ß, à and ê', + * XML_UTIL_ENTITIES_HTML, + * 'UTF-8' + * ); + * + * + * @param string $string string where XML special chars + * should be replaced + * @param int $replaceEntities setting for entities in attribute values + * (one of XML_UTIL_ENTITIES_XML, + * XML_UTIL_ENTITIES_XML_REQUIRED, + * XML_UTIL_ENTITIES_HTML) + * @param string $encoding encoding value (if any)... + * must be a valid encoding as determined + * by the html_entity_decode() function + * + * @return string string with replaced chars + * @access public + * @static + * @see replaceEntities() + */ + function reverseEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML, + $encoding = 'ISO-8859-1') + { + switch ($replaceEntities) { + case XML_UTIL_ENTITIES_XML: + return strtr($string, array( + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + ''' => '\'' )); + break; + case XML_UTIL_ENTITIES_XML_REQUIRED: + return strtr($string, array( + '&' => '&', + '<' => '<', + '"' => '"' )); + break; + case XML_UTIL_ENTITIES_HTML: + return html_entity_decode($string, ENT_COMPAT, $encoding); + break; + } + return $string; + } + + /** + * build an xml declaration + * + * + * require_once 'XML/Util.php'; + * + * // get an XML declaration: + * $xmlDecl = XML_Util::getXMLDeclaration('1.0', 'UTF-8', true); + * + * + * @param string $version xml version + * @param string $encoding character encoding + * @param bool $standalone document is standalone (or not) + * + * @return string xml declaration + * @access public + * @static + * @uses attributesToString() to serialize the attributes of the XML declaration + */ + function getXMLDeclaration($version = '1.0', $encoding = null, + $standalone = null) + { + $attributes = array( + 'version' => $version, + ); + // add encoding + if ($encoding !== null) { + $attributes['encoding'] = $encoding; + } + // add standalone, if specified + if ($standalone !== null) { + $attributes['standalone'] = $standalone ? 'yes' : 'no'; + } + + return sprintf('', + XML_Util::attributesToString($attributes, false)); + } + + /** + * build a document type declaration + * + * + * require_once 'XML/Util.php'; + * + * // get a doctype declaration: + * $xmlDecl = XML_Util::getDocTypeDeclaration('rootTag','myDocType.dtd'); + * + * + * @param string $root name of the root tag + * @param string $uri uri of the doctype definition + * (or array with uri and public id) + * @param string $internalDtd internal dtd entries + * + * @return string doctype declaration + * @access public + * @static + * @since 0.2 + */ + function getDocTypeDeclaration($root, $uri = null, $internalDtd = null) + { + if (is_array($uri)) { + $ref = sprintf(' PUBLIC "%s" "%s"', $uri['id'], $uri['uri']); + } elseif (!empty($uri)) { + $ref = sprintf(' SYSTEM "%s"', $uri); + } else { + $ref = ''; + } + + if (empty($internalDtd)) { + return sprintf('', $root, $ref); + } else { + return sprintf("", $root, $ref, $internalDtd); + } + } + + /** + * create string representation of an attribute list + * + * + * require_once 'XML/Util.php'; + * + * // build an attribute string + * $att = array( + * 'foo' => 'bar', + * 'argh' => 'tomato' + * ); + * + * $attList = XML_Util::attributesToString($att); + * + * + * @param array $attributes attribute array + * @param bool|array $sort sort attribute list alphabetically, + * may also be an assoc array containing + * the keys 'sort', 'multiline', 'indent', + * 'linebreak' and 'entities' + * @param bool $multiline use linebreaks, if more than + * one attribute is given + * @param string $indent string used for indentation of + * multiline attributes + * @param string $linebreak string used for linebreaks of + * multiline attributes + * @param int $entities setting for entities in attribute values + * (one of XML_UTIL_ENTITIES_NONE, + * XML_UTIL_ENTITIES_XML, + * XML_UTIL_ENTITIES_XML_REQUIRED, + * XML_UTIL_ENTITIES_HTML) + * + * @return string string representation of the attributes + * @access public + * @static + * @uses replaceEntities() to replace XML entities in attribute values + * @todo allow sort also to be an options array + */ + function attributesToString($attributes, $sort = true, $multiline = false, + $indent = ' ', $linebreak = "\n", $entities = XML_UTIL_ENTITIES_XML) + { + /* + * second parameter may be an array + */ + if (is_array($sort)) { + if (isset($sort['multiline'])) { + $multiline = $sort['multiline']; + } + if (isset($sort['indent'])) { + $indent = $sort['indent']; + } + if (isset($sort['linebreak'])) { + $multiline = $sort['linebreak']; + } + if (isset($sort['entities'])) { + $entities = $sort['entities']; + } + if (isset($sort['sort'])) { + $sort = $sort['sort']; + } else { + $sort = true; + } + } + $string = ''; + if (is_array($attributes) && !empty($attributes)) { + if ($sort) { + ksort($attributes); + } + if ( !$multiline || count($attributes) == 1) { + foreach ($attributes as $key => $value) { + if ($entities != XML_UTIL_ENTITIES_NONE) { + if ($entities === XML_UTIL_CDATA_SECTION) { + $entities = XML_UTIL_ENTITIES_XML; + } + $value = XML_Util::replaceEntities($value, $entities); + } + $string .= ' ' . $key . '="' . $value . '"'; + } + } else { + $first = true; + foreach ($attributes as $key => $value) { + if ($entities != XML_UTIL_ENTITIES_NONE) { + $value = XML_Util::replaceEntities($value, $entities); + } + if ($first) { + $string .= ' ' . $key . '="' . $value . '"'; + $first = false; + } else { + $string .= $linebreak . $indent . $key . '="' . $value . '"'; + } + } + } + } + return $string; + } + + /** + * Collapses empty tags. + * + * @param string $xml XML + * @param int $mode Whether to collapse all empty tags (XML_UTIL_COLLAPSE_ALL) + * or only XHTML (XML_UTIL_COLLAPSE_XHTML_ONLY) ones. + * + * @return string XML + * @access public + * @static + * @todo PEAR CS - unable to avoid "space after open parens" error + * in the IF branch + */ + function collapseEmptyTags($xml, $mode = XML_UTIL_COLLAPSE_ALL) + { + if ($mode == XML_UTIL_COLLAPSE_XHTML_ONLY) { + return preg_replace( + '/<(area|base(?:font)?|br|col|frame|hr|img|input|isindex|link|meta|' + . 'param)([^>]*)><\/\\1>/s', + '<\\1\\2 />', + $xml); + } else { + return preg_replace('/<(\w+)([^>]*)><\/\\1>/s', '<\\1\\2 />', $xml); + } + } + + /** + * create a tag + * + * This method will call XML_Util::createTagFromArray(), which + * is more flexible. + * + * + * require_once 'XML/Util.php'; + * + * // create an XML tag: + * $tag = XML_Util::createTag('myNs:myTag', + * array('foo' => 'bar'), + * 'This is inside the tag', + * 'http://www.w3c.org/myNs#'); + * + * + * @param string $qname qualified tagname (including namespace) + * @param array $attributes array containg attributes + * @param mixed $content the content + * @param string $namespaceUri URI of the namespace + * @param int $replaceEntities whether to replace XML special chars in + * content, embedd it in a CData section + * or none of both + * @param bool $multiline whether to create a multiline tag where + * each attribute gets written to a single line + * @param string $indent string used to indent attributes + * (_auto indents attributes so they start + * at the same column) + * @param string $linebreak string used for linebreaks + * @param bool $sortAttributes Whether to sort the attributes or not + * + * @return string XML tag + * @access public + * @static + * @see createTagFromArray() + * @uses createTagFromArray() to create the tag + */ + function createTag($qname, $attributes = array(), $content = null, + $namespaceUri = null, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, + $multiline = false, $indent = '_auto', $linebreak = "\n", + $sortAttributes = true) + { + $tag = array( + 'qname' => $qname, + 'attributes' => $attributes + ); + + // add tag content + if ($content !== null) { + $tag['content'] = $content; + } + + // add namespace Uri + if ($namespaceUri !== null) { + $tag['namespaceUri'] = $namespaceUri; + } + + return XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, + $indent, $linebreak, $sortAttributes); + } + + /** + * create a tag from an array + * this method awaits an array in the following format + *
    +     * array(
    +     *     // qualified name of the tag
    +     *     'qname' => $qname
    +     *
    +     *     // namespace prefix (optional, if qname is specified or no namespace)
    +     *     'namespace' => $namespace
    +     *
    +     *     // local part of the tagname (optional, if qname is specified)
    +     *     'localpart' => $localpart,
    +     *
    +     *     // array containing all attributes (optional)
    +     *     'attributes' => array(),
    +     *
    +     *     // tag content (optional)
    +     *     'content' => $content,
    +     *
    +     *     // namespaceUri for the given namespace (optional)
    +     *     'namespaceUri' => $namespaceUri
    +     * )
    +     * 
    + * + * + * require_once 'XML/Util.php'; + * + * $tag = array( + * 'qname' => 'foo:bar', + * 'namespaceUri' => 'http://foo.com', + * 'attributes' => array('key' => 'value', 'argh' => 'fruit&vegetable'), + * 'content' => 'I\'m inside the tag', + * ); + * // creating a tag with qualified name and namespaceUri + * $string = XML_Util::createTagFromArray($tag); + * + * + * @param array $tag tag definition + * @param int $replaceEntities whether to replace XML special chars in + * content, embedd it in a CData section + * or none of both + * @param bool $multiline whether to create a multiline tag where each + * attribute gets written to a single line + * @param string $indent string used to indent attributes + * (_auto indents attributes so they start + * at the same column) + * @param string $linebreak string used for linebreaks + * @param bool $sortAttributes Whether to sort the attributes or not + * + * @return string XML tag + * @access public + * @static + * @see createTag() + * @uses attributesToString() to serialize the attributes of the tag + * @uses splitQualifiedName() to get local part and namespace of a qualified name + * @uses createCDataSection() + * @uses raiseError() + */ + function createTagFromArray($tag, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, + $multiline = false, $indent = '_auto', $linebreak = "\n", + $sortAttributes = true) + { + if (isset($tag['content']) && !is_scalar($tag['content'])) { + return XML_Util::raiseError('Supplied non-scalar value as tag content', + XML_UTIL_ERROR_NON_SCALAR_CONTENT); + } + + if (!isset($tag['qname']) && !isset($tag['localPart'])) { + return XML_Util::raiseError('You must either supply a qualified name ' + . '(qname) or local tag name (localPart).', + XML_UTIL_ERROR_NO_TAG_NAME); + } + + // if no attributes hav been set, use empty attributes + if (!isset($tag['attributes']) || !is_array($tag['attributes'])) { + $tag['attributes'] = array(); + } + + if (isset($tag['namespaces'])) { + foreach ($tag['namespaces'] as $ns => $uri) { + $tag['attributes']['xmlns:' . $ns] = $uri; + } + } + + if (!isset($tag['qname'])) { + // qualified name is not given + + // check for namespace + if (isset($tag['namespace']) && !empty($tag['namespace'])) { + $tag['qname'] = $tag['namespace'] . ':' . $tag['localPart']; + } else { + $tag['qname'] = $tag['localPart']; + } + } elseif (isset($tag['namespaceUri']) && !isset($tag['namespace'])) { + // namespace URI is set, but no namespace + + $parts = XML_Util::splitQualifiedName($tag['qname']); + + $tag['localPart'] = $parts['localPart']; + if (isset($parts['namespace'])) { + $tag['namespace'] = $parts['namespace']; + } + } + + if (isset($tag['namespaceUri']) && !empty($tag['namespaceUri'])) { + // is a namespace given + if (isset($tag['namespace']) && !empty($tag['namespace'])) { + $tag['attributes']['xmlns:' . $tag['namespace']] = + $tag['namespaceUri']; + } else { + // define this Uri as the default namespace + $tag['attributes']['xmlns'] = $tag['namespaceUri']; + } + } + + // check for multiline attributes + if ($multiline === true) { + if ($indent === '_auto') { + $indent = str_repeat(' ', (strlen($tag['qname'])+2)); + } + } + + // create attribute list + $attList = XML_Util::attributesToString($tag['attributes'], + $sortAttributes, $multiline, $indent, $linebreak, $replaceEntities); + if (!isset($tag['content']) || (string)$tag['content'] == '') { + $tag = sprintf('<%s%s />', $tag['qname'], $attList); + } else { + switch ($replaceEntities) { + case XML_UTIL_ENTITIES_NONE: + break; + case XML_UTIL_CDATA_SECTION: + $tag['content'] = XML_Util::createCDataSection($tag['content']); + break; + default: + $tag['content'] = XML_Util::replaceEntities($tag['content'], + $replaceEntities); + break; + } + $tag = sprintf('<%s%s>%s', $tag['qname'], $attList, $tag['content'], + $tag['qname']); + } + return $tag; + } + + /** + * create a start element + * + * + * require_once 'XML/Util.php'; + * + * // create an XML start element: + * $tag = XML_Util::createStartElement('myNs:myTag', + * array('foo' => 'bar') ,'http://www.w3c.org/myNs#'); + * + * + * @param string $qname qualified tagname (including namespace) + * @param array $attributes array containg attributes + * @param string $namespaceUri URI of the namespace + * @param bool $multiline whether to create a multiline tag where each + * attribute gets written to a single line + * @param string $indent string used to indent attributes (_auto indents + * attributes so they start at the same column) + * @param string $linebreak string used for linebreaks + * @param bool $sortAttributes Whether to sort the attributes or not + * + * @return string XML start element + * @access public + * @static + * @see createEndElement(), createTag() + */ + function createStartElement($qname, $attributes = array(), $namespaceUri = null, + $multiline = false, $indent = '_auto', $linebreak = "\n", + $sortAttributes = true) + { + // if no attributes hav been set, use empty attributes + if (!isset($attributes) || !is_array($attributes)) { + $attributes = array(); + } + + if ($namespaceUri != null) { + $parts = XML_Util::splitQualifiedName($qname); + } + + // check for multiline attributes + if ($multiline === true) { + if ($indent === '_auto') { + $indent = str_repeat(' ', (strlen($qname)+2)); + } + } + + if ($namespaceUri != null) { + // is a namespace given + if (isset($parts['namespace']) && !empty($parts['namespace'])) { + $attributes['xmlns:' . $parts['namespace']] = $namespaceUri; + } else { + // define this Uri as the default namespace + $attributes['xmlns'] = $namespaceUri; + } + } + + // create attribute list + $attList = XML_Util::attributesToString($attributes, $sortAttributes, + $multiline, $indent, $linebreak); + $element = sprintf('<%s%s>', $qname, $attList); + return $element; + } + + /** + * create an end element + * + * + * require_once 'XML/Util.php'; + * + * // create an XML start element: + * $tag = XML_Util::createEndElement('myNs:myTag'); + * + * + * @param string $qname qualified tagname (including namespace) + * + * @return string XML end element + * @access public + * @static + * @see createStartElement(), createTag() + */ + function createEndElement($qname) + { + $element = sprintf('', $qname); + return $element; + } + + /** + * create an XML comment + * + * + * require_once 'XML/Util.php'; + * + * // create an XML start element: + * $tag = XML_Util::createComment('I am a comment'); + * + * + * @param string $content content of the comment + * + * @return string XML comment + * @access public + * @static + */ + function createComment($content) + { + $comment = sprintf('', $content); + return $comment; + } + + /** + * create a CData section + * + * + * require_once 'XML/Util.php'; + * + * // create a CData section + * $tag = XML_Util::createCDataSection('I am content.'); + * + * + * @param string $data data of the CData section + * + * @return string CData section with content + * @access public + * @static + */ + function createCDataSection($data) + { + return sprintf('', + preg_replace('/\]\]>/', ']]]]>', strval($data))); + + } + + /** + * split qualified name and return namespace and local part + * + * + * require_once 'XML/Util.php'; + * + * // split qualified tag + * $parts = XML_Util::splitQualifiedName('xslt:stylesheet'); + * + * the returned array will contain two elements: + *
    +     * array(
    +     *     'namespace' => 'xslt',
    +     *     'localPart' => 'stylesheet'
    +     * );
    +     * 
    + * + * @param string $qname qualified tag name + * @param string $defaultNs default namespace (optional) + * + * @return array array containing namespace and local part + * @access public + * @static + */ + function splitQualifiedName($qname, $defaultNs = null) + { + if (strstr($qname, ':')) { + $tmp = explode(':', $qname); + return array( + 'namespace' => $tmp[0], + 'localPart' => $tmp[1] + ); + } + return array( + 'namespace' => $defaultNs, + 'localPart' => $qname + ); + } + + /** + * check, whether string is valid XML name + * + *

    XML names are used for tagname, attribute names and various + * other, lesser known entities.

    + *

    An XML name may only consist of alphanumeric characters, + * dashes, undescores and periods, and has to start with a letter + * or an underscore.

    + * + * + * require_once 'XML/Util.php'; + * + * // verify tag name + * $result = XML_Util::isValidName('invalidTag?'); + * if (is_a($result, 'PEAR_Error')) { + * print 'Invalid XML name: ' . $result->getMessage(); + * } + * + * + * @param string $string string that should be checked + * + * @return mixed true, if string is a valid XML name, PEAR error otherwise + * @access public + * @static + * @todo support for other charsets + * @todo PEAR CS - unable to avoid 85-char limit on second preg_match + */ + function isValidName($string) + { + // check for invalid chars + if (!preg_match('/^[[:alpha:]_]\\z/', $string{0})) { + return XML_Util::raiseError('XML names may only start with letter ' + . 'or underscore', XML_UTIL_ERROR_INVALID_START); + } + + // check for invalid chars + if (!preg_match('/^([[:alpha:]_]([[:alnum:]\-\.]*)?:)?[[:alpha:]_]([[:alnum:]\_\-\.]+)?\\z/', + $string) + ) { + return XML_Util::raiseError('XML names may only contain alphanumeric ' + . 'chars, period, hyphen, colon and underscores', + XML_UTIL_ERROR_INVALID_CHARS); + } + // XML name is valid + return true; + } + + /** + * replacement for XML_Util::raiseError + * + * Avoids the necessity to always require + * PEAR.php + * + * @param string $msg error message + * @param int $code error code + * + * @return PEAR_Error + * @access public + * @static + * @todo PEAR CS - should this use include_once instead? + */ + function raiseError($msg, $code) + { + require_once 'phar://go-pear.phar/' . 'PEAR.php'; + return PEAR::raiseError($msg, $code); + } +} +?> +:”“Slc(%ÝÍ¥+ÖbC°°GBMB diff --git a/cookbooks/php/libraries/helpers.rb b/cookbooks/php/libraries/helpers.rb new file mode 100644 index 0000000..f58873e --- /dev/null +++ b/cookbooks/php/libraries/helpers.rb @@ -0,0 +1,23 @@ +# +# Author:: Joshua Timberman () +# Cookbook Name:: php +# Libraries:: helpers +# +# Copyright 2013, Opscode, Inc. +# +# 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. +# + +def el5_range + (0..99).to_a.map { |i| "5.#{i}" } +end diff --git a/cookbooks/php/metadata.json b/cookbooks/php/metadata.json new file mode 100644 index 0000000..63fbf2f --- /dev/null +++ b/cookbooks/php/metadata.json @@ -0,0 +1,57 @@ +{ + "name": "php", + "version": "1.5.0", + "description": "Installs and maintains php and php modules", + "long_description": "", + "maintainer": "Opscode, Inc.", + "maintainer_email": "cookbooks@opscode.com", + "license": "Apache 2.0", + "platforms": { + "debian": ">= 0.0.0", + "ubuntu": ">= 0.0.0", + "centos": ">= 0.0.0", + "redhat": ">= 0.0.0", + "fedora": ">= 0.0.0", + "scientific": ">= 0.0.0", + "amazon": ">= 0.0.0", + "windows": ">= 0.0.0", + "oracle": ">= 0.0.0" + }, + "dependencies": { + "build-essential": ">= 0.0.0", + "xml": ">= 0.0.0", + "mysql": ">= 0.0.0", + "yum-epel": ">= 0.0.0", + "windows": ">= 0.0.0", + "iis": ">= 0.0.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + "php": "Installs php", + "php::package": "Installs php using packages.", + "php::source": "Installs php from source.", + "php::module_apc": "Install the php5-apc package", + "php::module_curl": "Install the php5-curl package", + "php::module_fileinfo": "Install the php5-fileinfo package", + "php::module_fpdf": "Install the php-fpdf package", + "php::module_gd": "Install the php5-gd package", + "php::module_ldap": "Install the php5-ldap package", + "php::module_memcache": "Install the php5-memcache package", + "php::module_mysql": "Install the php5-mysql package", + "php::module_pgsql": "Install the php5-pgsql packag", + "php::module_sqlite3": "Install the php5-sqlite3 package" + } +} \ No newline at end of file diff --git a/cookbooks/php/metadata.rb b/cookbooks/php/metadata.rb new file mode 100644 index 0000000..18259d0 --- /dev/null +++ b/cookbooks/php/metadata.rb @@ -0,0 +1,31 @@ +name 'php' +maintainer 'Opscode, Inc.' +maintainer_email 'cookbooks@opscode.com' +license 'Apache 2.0' +description 'Installs and maintains php and php modules' +version '1.5.0' + +depends 'build-essential' +depends 'xml' +depends 'mysql' +depends 'yum-epel' +depends 'windows' +depends 'iis' + +%w{ debian ubuntu centos redhat fedora scientific amazon windows oracle }.each do |os| + supports os +end + +recipe 'php', 'Installs php' +recipe 'php::package', 'Installs php using packages.' +recipe 'php::source', 'Installs php from source.' +recipe 'php::module_apc', 'Install the php5-apc package' +recipe 'php::module_curl', 'Install the php5-curl package' +recipe 'php::module_fileinfo', 'Install the php5-fileinfo package' +recipe 'php::module_fpdf', 'Install the php-fpdf package' +recipe 'php::module_gd', 'Install the php5-gd package' +recipe 'php::module_ldap', 'Install the php5-ldap package' +recipe 'php::module_memcache', 'Install the php5-memcache package' +recipe 'php::module_mysql', 'Install the php5-mysql package' +recipe 'php::module_pgsql', 'Install the php5-pgsql packag' +recipe 'php::module_sqlite3', 'Install the php5-sqlite3 package' diff --git a/cookbooks/php/providers/pear.rb b/cookbooks/php/providers/pear.rb new file mode 100644 index 0000000..ae5a63d --- /dev/null +++ b/cookbooks/php/providers/pear.rb @@ -0,0 +1,280 @@ +# +# Author:: Seth Chisamore +# Cookbook Name:: php +# Provider:: pear_package +# +# Copyright:: 2011, Opscode, Inc +# +# 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. +# + +require 'chef/mixin/shell_out' +require 'chef/mixin/language' +include Chef::Mixin::ShellOut + +# the logic in all action methods mirror that of +# the Chef::Provider::Package which will make +# refactoring into core chef easy + +def whyrun_supported? + true +end + +action :install do + # If we specified a version, and it's not the current version, move to the specified version + install_version = @new_resource.version unless @new_resource.version.nil? || @new_resource.version == @current_resource.version + + # If it's not installed at all or an upgrade, install it + if install_version || @current_resource.version.nil? + description = "install package #{@new_resource} #{install_version}" + converge_by(description) do + info_output = "Installing #{@new_resource}" + info_output << " version #{install_version}" if install_version && !install_version.empty? + Chef::Log.info(info_output) + status = install_package(@new_resource.package_name, install_version) + end + end +end + +action :upgrade do + if @current_resource.version != candidate_version + orig_version = @current_resource.version || 'uninstalled' + description = "upgrade package #{@new_resource} version from #{orig_version} to #{candidate_version}" + converge_by(description) do + Chef::Log.info("Upgrading #{@new_resource} version from #{orig_version} to #{candidate_version}") + status = upgrade_package(@new_resource.package_name, candidate_version) + end + end +end + +action :remove do + if removing_package? + description = "remove package #{@new_resource}" + converge_by(description) do + Chef::Log.info("Removing #{@new_resource}") + remove_package(@current_resource.package_name, @new_resource.version) + end + else + end +end + +action :purge do + if removing_package? + description = "purge package #{@new_resource}" + converge_by(description) do + Chef::Log.info("Purging #{@new_resource}") + purge_package(@current_resource.package_name, @new_resource.version) + end + end +end + +def removing_package? + if @current_resource.version.nil? + false # nothing to remove + elsif @new_resource.version.nil? + true # remove any version of a package + elsif @new_resource.version == @current_resource.version + true # remove the version we have + else + false # we don't have the version we want to remove + end +end + +def expand_options(options) + options ? " #{options}" : '' +end + +# these methods are the required overrides of +# a provider that extends from Chef::Provider::Package +# so refactoring into core Chef should be easy + +def load_current_resource + @current_resource = Chef::Resource::PhpPear.new(@new_resource.name) + @current_resource.package_name(@new_resource.package_name) + @bin = node['php']['pear'] + if pecl? + Chef::Log.debug("#{@new_resource} smells like a pecl...installing package in Pecl mode.") + @bin = node['php']['pecl'] + end + Chef::Log.debug("#{@current_resource}: Installed version: #{current_installed_version} Candidate version: #{candidate_version}") + + unless current_installed_version.nil? + @current_resource.version(current_installed_version) + Chef::Log.debug("Current version is #{@current_resource.version}") if @current_resource.version + end + @current_resource +end + +def current_installed_version + @current_installed_version ||= begin + v = nil + version_check_cmd = "#{@bin} -d " + version_check_cmd << " preferred_state=#{can_haz(@new_resource, "preferred_state")}" + version_check_cmd << " list#{expand_channel(can_haz(@new_resource, "channel"))}" + p = shell_out(version_check_cmd) + response = nil + response = grep_for_version(p.stdout, @new_resource.package_name) if p.stdout =~ /\.?Installed packages/i + response + end +end + +def candidate_version + @candidate_version ||= begin + candidate_version_cmd = "#{@bin} -d " + candidate_version_cmd << "preferred_state=#{can_haz(@new_resource, "preferred_state")}" + candidate_version_cmd << " search#{expand_channel(can_haz(@new_resource, "channel"))}" + candidate_version_cmd << "#{@new_resource.package_name}" + p = shell_out(candidate_version_cmd) + response = nil + response = grep_for_version(p.stdout, @new_resource.package_name) if p.stdout =~ /\.?Matched packages/i + response + end +end + +def install_package(name, version) + command = "echo \"\r\" | #{@bin} -d" + command << " preferred_state=#{can_haz(@new_resource, "preferred_state")}" + command << " install -a#{expand_options(@new_resource.options)}" + command << " #{prefix_channel(can_haz(@new_resource, "channel"))}#{name}" + command << "-#{version}" if version && !version.empty? + pear_shell_out(command) + manage_pecl_ini(name, :create, can_haz(@new_resource, 'directives'), can_haz(@new_resource, 'zend_extensions')) if pecl? +end + +def upgrade_package(name, version) + command = "echo \"\r\" | #{@bin} -d" + command << " preferred_state=#{can_haz(@new_resource, "preferred_state")}" + command << " upgrade -a#{expand_options(@new_resource.options)}" + command << " #{prefix_channel(can_haz(@new_resource, "channel"))}#{name}" + command << "-#{version}" if version && !version.empty? + pear_shell_out(command) + manage_pecl_ini(name, :create, can_haz(@new_resource, 'directives'), can_haz(@new_resource, 'zend_extensions')) if pecl? +end + +def remove_package(name, version) + command = "#{@bin} uninstall" + command << " #{expand_options(@new_resource.options)}" + command << " #{prefix_channel(can_haz(@new_resource, "channel"))}#{name}" + command << "-#{version}" if version && !version.empty? + pear_shell_out(command) + manage_pecl_ini(name, :delete) if pecl? +end + +def pear_shell_out(command) + p = shell_out!(command) + # pear/pecl commands return a 0 on failures...we'll grep for it + p.invalid! if p.stdout.split('\n').last =~ /^ERROR:.+/i + p +end + +def purge_package(name, version) + remove_package(name, version) +end + +def expand_channel(channel) + channel ? " -c #{channel}" : '' +end + +def prefix_channel(channel) + channel ? "#{channel}/" : '' +end + +def get_extension_dir + @extension_dir ||= begin + # Consider using "pecl config-get ext_dir". It is more cross-platform. + # p = shell_out("php-config --extension-dir") + p = shell_out("#{node['php']['pecl']} config-get ext_dir") + p.stdout.strip + end +end + +def get_extension_files(name) + files = [] + + p = shell_out("#{@bin} list-files #{name}") + p.stdout.each_line.grep(/^src\s+.*\.so$/i).each do |line| + files << line.split[1] + end + + files +end + +def manage_pecl_ini(name, action, directives, zend_extensions) + ext_prefix = get_extension_dir + ext_prefix << ::File::SEPARATOR if ext_prefix[-1].chr != ::File::SEPARATOR + + files = get_extension_files(name) + + extensions = Hash[ + files.map do |filepath| + rel_file = filepath.clone + rel_file.slice! ext_prefix if rel_file.start_with? ext_prefix + zend = zend_extensions.include?(rel_file) + [(zend ? filepath : rel_file) , zend] + end + ] + + template "#{node['php']['ext_conf_dir']}/#{name}.ini" do + source 'extension.ini.erb' + cookbook 'php' + owner 'root' + group 'root' + mode '0644' + variables(:name => name, :extensions => extensions, :directives => directives) + action action + end +end + +def grep_for_version(stdout, package) + v = nil + + stdout.split(/\n/).grep(/^#{package}\s/i).each do |m| + # XML_RPC 1.5.4 stable + # mongo 1.1.4/(1.1.4 stable) 1.1.4 MongoDB database driver + # Horde_Url -n/a-/(1.0.0beta1 beta) Horde Url class + # Horde_Url 1.0.0beta1 (beta) 1.0.0beta1 Horde Url class + v = m.split(/\s+/)[1].strip + if v.split(/\//)[0] =~ /.\./ + # 1.1.4/(1.1.4 stable) + v = v.split(/\//)[0] + else + # -n/a-/(1.0.0beta1 beta) + v = v.split(/(.*)\/\((.*)/).last.split(/\s/)[0] + end + end + v +end + +def pecl? + @pecl ||= + begin + # search as a pear first since most 3rd party channels will report pears as pecls! + search_args = String.new + search_args << " -d preferred_state=#{can_haz(@new_resource, "preferred_state")}" + search_args << " search#{expand_channel(can_haz(@new_resource, "channel"))} #{@new_resource.package_name}" + + if grep_for_version(shell_out(node['php']['pear'] + search_args).stdout, @new_resource.package_name) + false + elsif grep_for_version(shell_out(node['php']['pecl'] + search_args).stdout, @new_resource.package_name) + true + else + fail "Package #{@new_resource.package_name} not found in either PEAR or PECL." + end + end +end + +# TODO: remove when provider is moved into Chef core +# this allows PhpPear to work with Chef::Resource::Package +def can_haz(resource, attribute_name) + resource.respond_to?(attribute_name) ? resource.send(attribute_name) : nil +end diff --git a/cookbooks/php/providers/pear_channel.rb b/cookbooks/php/providers/pear_channel.rb new file mode 100644 index 0000000..34ab648 --- /dev/null +++ b/cookbooks/php/providers/pear_channel.rb @@ -0,0 +1,93 @@ +# +# Author:: Seth Chisamore +# Cookbook Name:: php +# Provider:: pear_channel +# +# Copyright:: 2011, Opscode, Inc +# +# 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. +# + +# http://pear.php.net/manual/en/guide.users.commandline.channels.php + +require 'chef/mixin/shell_out' +require 'chef/mixin/language' +include Chef::Mixin::ShellOut + +def whyrun_supported? + true +end + +action :discover do + unless exists? + Chef::Log.info("Discovering pear channel #{@new_resource}") + execute "#{node['php']['pear']} channel-discover #{@new_resource.channel_name}" do + action :run + end + end +end + +action :add do + unless exists? + Chef::Log.info("Adding pear channel #{@new_resource} from #{@new_resource.channel_xml}") + execute "#{node['php']['pear']} channel-add #{@new_resource.channel_xml}" do + action :run + end + end +end + +action :update do + if exists? + update_needed = false + begin + updated_needed = true if shell_out("#{node['php']['pear']} search -c #{@new_resource.channel_name} NNNNNN").stdout =~ /channel-update/ + rescue Chef::Exceptions::CommandTimeout + # CentOS can hang on 'pear search' if a channel needs updating + Chef::Log.info("Timed out checking if channel-update needed...forcing update of pear channel #{@new_resource}") + update_needed = true + end + if update_needed + description = "update pear channel #{@new_resource}" + converge_by(description) do + Chef::Log.info("Updating pear channel #{@new_resource}") + shell_out!("#{node['php']['pear']} channel-update #{@new_resource.channel_name}") + end + end + end +end + +action :remove do + if exists? + Chef::Log.info("Deleting pear channel #{@new_resource}") + execute "#{node['php']['pear']} channel-delete #{@new_resource.channel_name}" do + action :run + end + end +end + +def load_current_resource + @current_resource = Chef::Resource::PhpPearChannel.new(@new_resource.name) + @current_resource.channel_name(@new_resource.channel_name) + @current_resource +end + +private + +def exists? + begin + shell_out!("#{node['php']['pear']} channel-info #{@current_resource.channel_name}") + true + rescue Mixlib::ShellOut::ShellCommandFailed + false + end +end diff --git a/cookbooks/php/recipes/default.rb b/cookbooks/php/recipes/default.rb new file mode 100644 index 0000000..38ca7f4 --- /dev/null +++ b/cookbooks/php/recipes/default.rb @@ -0,0 +1,33 @@ +# +# Author:: Joshua Timberman () +# Author:: Seth Chisamore () +# Cookbook Name:: php +# Recipe:: default +# +# Copyright 2009-2011, Opscode, Inc. +# +# 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. +# + +include_recipe "php::#{node['php']['install_method']}" + +# update the main channels +php_pear_channel 'pear.php.net' do + action :update +end + +php_pear_channel 'pecl.php.net' do + action :update +end + +include_recipe "php::ini" diff --git a/cookbooks/php/recipes/ini.rb b/cookbooks/php/recipes/ini.rb new file mode 100644 index 0000000..b3d737a --- /dev/null +++ b/cookbooks/php/recipes/ini.rb @@ -0,0 +1,30 @@ +# +# Author:: Christo De Lange () +# Cookbook Name:: php +# Recipe:: ini +# +# Copyright 2011, Opscode, Inc. +# +# 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. +# + +template "#{node['php']['conf_dir']}/php.ini" do + source node['php']['ini']['template'] + cookbook node['php']['ini']['cookbook'] + unless platform?('windows') + owner 'root' + group 'root' + mode '0644' + end + variables(:directives => node['php']['directives']) +end diff --git a/cookbooks/php/recipes/module_apc.rb b/cookbooks/php/recipes/module_apc.rb new file mode 100644 index 0000000..e45d7eb --- /dev/null +++ b/cookbooks/php/recipes/module_apc.rb @@ -0,0 +1,37 @@ +# +# Author:: Joshua Timberman () +# Author:: Seth Chisamore () +# Cookbook Name:: php +# Recipe:: module_apc +# +# Copyright 2009-2011, Opscode, Inc. +# +# 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. +# + +case node['platform_family'] +when 'rhel', 'fedora' + %w{ httpd-devel pcre pcre-devel }.each do |pkg| + package pkg do + action :install + end + end + php_pear 'APC' do + action :install + directives(:shm_size => '128M', :enable_cli => 0) + end +when 'debian' + package 'php-apc' do + action :install + end +end diff --git a/cookbooks/php/recipes/module_curl.rb b/cookbooks/php/recipes/module_curl.rb new file mode 100644 index 0000000..3848672 --- /dev/null +++ b/cookbooks/php/recipes/module_curl.rb @@ -0,0 +1,29 @@ +# +# Author:: Joshua Timberman () +# Author:: Seth Chisamore () +# Cookbook Name:: php +# Recipe:: module_curl +# +# Copyright 2009-2011, Opscode, Inc. +# +# 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. +# + +case node['platform_family'] +when 'rhel', 'fedora' + # centos php compiled with curl +when 'debian' + package 'php5-curl' do + action :upgrade + end +end diff --git a/cookbooks/php/recipes/module_fpdf.rb b/cookbooks/php/recipes/module_fpdf.rb new file mode 100644 index 0000000..519fe93 --- /dev/null +++ b/cookbooks/php/recipes/module_fpdf.rb @@ -0,0 +1,35 @@ +# +# Author:: Joshua Timberman () +# Author:: Seth Chisamore () +# Cookbook Name:: php +# Recipe:: module_fpdf +# +# Copyright 2009-2011, Opscode, Inc. +# +# 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. +# + +case node['platform_family'] +when 'rhel', 'fedora' + pearhub_chan = php_pear_channel 'pearhub.org' do + action :discover + end + php_pear 'FPDF' do + channel pearhub_chan.channel_name + action :install + end +when 'debian' + package 'php-fpdf' do + action :install + end +end diff --git a/cookbooks/php/recipes/module_gd.rb b/cookbooks/php/recipes/module_gd.rb new file mode 100644 index 0000000..1482323 --- /dev/null +++ b/cookbooks/php/recipes/module_gd.rb @@ -0,0 +1,32 @@ +# +# Author:: Joshua Timberman () +# Author:: Seth Chisamore () +# Cookbook Name:: php +# Recipe:: module_gd +# +# Copyright 2009-2011, Opscode, Inc. +# +# 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. +# + +pkg = value_for_platform( + %w(centos redhat scientific fedora amazon oracle) => { + el5_range => 'php53-gd', + 'default' => 'php-gd' + }, + 'default' => 'php5-gd' +) + +package pkg do + action :install +end diff --git a/cookbooks/php/recipes/module_ldap.rb b/cookbooks/php/recipes/module_ldap.rb new file mode 100644 index 0000000..bb919ed --- /dev/null +++ b/cookbooks/php/recipes/module_ldap.rb @@ -0,0 +1,32 @@ +# +# Author:: Joshua Timberman () +# Author:: Seth Chisamore () +# Cookbook Name:: php +# Recipe:: module_ldap +# +# Copyright 2009-2011, Opscode, Inc. +# +# 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. +# + +pkg = value_for_platform( + %w(centos redhat scientific fedora amazon oracle) => { + el5_range => 'php53-ldap', + 'default' => 'php-ldap' + }, + 'default' => 'php5-ldap' +) + +package pkg do + action :install +end diff --git a/cookbooks/php/recipes/module_memcache.rb b/cookbooks/php/recipes/module_memcache.rb new file mode 100644 index 0000000..8f3669c --- /dev/null +++ b/cookbooks/php/recipes/module_memcache.rb @@ -0,0 +1,37 @@ +# +# Author:: Joshua Timberman () +# Author:: Seth Chisamore () +# Cookbook Name:: php +# Recipe:: module_memcache +# +# Copyright 2009-2011, Opscode, Inc. +# +# 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. +# + +case node['platform_family'] +when 'rhel', 'fedora' + %w{ zlib-devel }.each do |pkg| + package pkg do + action :install + end + end + php_pear 'memcache' do + action :install + # directives(:shm_size => "128M", :enable_cli => 0) + end +when 'debian' + package 'php5-memcache' do + action :install + end +end diff --git a/cookbooks/php/recipes/module_mysql.rb b/cookbooks/php/recipes/module_mysql.rb new file mode 100644 index 0000000..44a8b80 --- /dev/null +++ b/cookbooks/php/recipes/module_mysql.rb @@ -0,0 +1,32 @@ +# +# Author:: Joshua Timberman () +# Author:: Seth Chisamore () +# Cookbook Name:: php +# Recipe:: module_mysql +# +# Copyright 2009-2011, Opscode, Inc. +# +# 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. +# + +pkg = value_for_platform( + %w(centos redhat scientific fedora amazon oracle) => { + el5_range => 'php53-mysql', + 'default' => 'php-mysql' + }, + 'default' => 'php5-mysql' +) + +package pkg do + action :install +end diff --git a/cookbooks/php/recipes/module_pgsql.rb b/cookbooks/php/recipes/module_pgsql.rb new file mode 100644 index 0000000..fef279e --- /dev/null +++ b/cookbooks/php/recipes/module_pgsql.rb @@ -0,0 +1,32 @@ +# +# Author:: Joshua Timberman () +# Author:: Seth Chisamore () +# Cookbook Name:: php +# Recipe:: module_pgsql +# +# Copyright 2009-2011, Opscode, Inc. +# +# 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. +# + +pkg = value_for_platform( + %w(centos redhat scientific fedora amazon oracle) => { + el5_range => 'php53-pgsql', + 'default' => 'php-pgsql' + }, + 'default' => 'php5-pgsql' +) + +package pkg do + action :install +end diff --git a/cookbooks/php/recipes/module_sqlite3.rb b/cookbooks/php/recipes/module_sqlite3.rb new file mode 100644 index 0000000..2542d52 --- /dev/null +++ b/cookbooks/php/recipes/module_sqlite3.rb @@ -0,0 +1,29 @@ +# +# Author:: Joshua Timberman () +# Author:: Seth Chisamore () +# Cookbook Name:: php +# Recipe:: module_sqlite3 +# +# Copyright 2009-2011, Opscode, Inc. +# +# 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. +# + +case node['platform_family'] +when 'rhel', 'fedora' + # already there in centos, --with-pdo-sqlite=shared +when 'debian' + package 'php5-sqlite' do + action :install + end +end diff --git a/cookbooks/php/recipes/package.rb b/cookbooks/php/recipes/package.rb new file mode 100644 index 0000000..890c83b --- /dev/null +++ b/cookbooks/php/recipes/package.rb @@ -0,0 +1,66 @@ +# +# Author:: Seth Chisamore () +# Author:: Lucas Hansen () +# Cookbook Name:: php +# Recipe:: package +# +# Copyright 2013, Opscode, Inc. +# +# 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. +# + +if platform?('windows') + + include_recipe 'iis::mod_cgi' + + install_dir = File.expand_path(node['php']['conf_dir']).gsub('/', '\\') + windows_package node['php']['windows']['msi_name'] do + source node['php']['windows']['msi_source'] + installer_type :msi + + options %W[ + /quiet + INSTALLDIR="#{install_dir}" + ADDLOCAL=#{node['php']['packages'].join(',')} + ].join(' ') + end + + # WARNING: This is not the out-of-the-box go-pear.phar. It's been modified to patch this bug: + # http://pear.php.net/bugs/bug.php?id=16644 + cookbook_file "#{node['php']['conf_dir']}/PEAR/go-pear.phar" do + source 'go-pear.phar' + end + + template "#{node['php']['conf_dir']}/pear-options" do + source 'pear-options.erb' + end + + execute 'install-pear' do + cwd node['php']['conf_dir'] + command 'go-pear.bat < pear-options' + creates "#{node['php']['conf_dir']}/pear.bat" + end + + ENV['PATH'] += ";#{install_dir}" + windows_path install_dir + +else + node['php']['packages'].each do |pkg| + package pkg do + action :install + options node['php']['package_options'] + end + end +end + +include_recipe "php::ini" diff --git a/cookbooks/php/recipes/source.rb b/cookbooks/php/recipes/source.rb new file mode 100644 index 0000000..4805034 --- /dev/null +++ b/cookbooks/php/recipes/source.rb @@ -0,0 +1,85 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: php +# Recipe:: package +# +# Copyright 2011, Opscode, Inc. +# +# 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. +# + +configure_options = node['php']['configure_options'].join(' ') + +include_recipe 'build-essential' +include_recipe 'xml' +include_recipe 'mysql::client' if configure_options =~ /mysql/ +include_recipe 'yum-epel' if node['platform_family'] == 'rhel' + +pkgs = value_for_platform_family( + %w{ rhel fedora } => %w{ bzip2-devel libc-client-devel curl-devel freetype-devel gmp-devel libjpeg-devel krb5-devel libmcrypt-devel libpng-devel openssl-devel t1lib-devel mhash-devel }, + %w{ debian ubuntu } => %w{ libbz2-dev libc-client2007e-dev libcurl4-gnutls-dev libfreetype6-dev libgmp3-dev libjpeg62-dev libkrb5-dev libmcrypt-dev libpng12-dev libssl-dev libt1-dev }, + 'default' => %w{ libbz2-dev libc-client2007e-dev libcurl4-gnutls-dev libfreetype6-dev libgmp3-dev libjpeg62-dev libkrb5-dev libmcrypt-dev libpng12-dev libssl-dev libt1-dev } + ) + +pkgs.each do |pkg| + package pkg do + action :install + end +end + +version = node['php']['version'] + +remote_file "#{Chef::Config[:file_cache_path]}/php-#{version}.tar.gz" do + source "#{node['php']['url']}/php-#{version}.tar.gz/from/this/mirror" + checksum node['php']['checksum'] + mode '0644' + not_if "which #{node['php']['bin']}" +end + +if node['php']['ext_dir'] + directory node['php']['ext_dir'] do + owner 'root' + group 'root' + mode '0755' + recursive true + end + ext_dir_prefix = "EXTENSION_DIR=#{node['php']['ext_dir']}" +else + ext_dir_prefix = '' +end + +bash 'build php' do + cwd Chef::Config[:file_cache_path] + code <<-EOF + tar -zxf php-#{version}.tar.gz + (cd php-#{version} && #{ext_dir_prefix} ./configure #{configure_options}) + (cd php-#{version} && make && make install) + EOF + not_if "which #{node['php']['bin']}" +end + +directory node['php']['conf_dir'] do + owner 'root' + group 'root' + mode '0755' + recursive true +end + +directory node['php']['ext_conf_dir'] do + owner 'root' + group 'root' + mode '0755' + recursive true +end + +include_recipe "php::ini" diff --git a/cookbooks/php/resources/pear.rb b/cookbooks/php/resources/pear.rb new file mode 100644 index 0000000..d548531 --- /dev/null +++ b/cookbooks/php/resources/pear.rb @@ -0,0 +1,30 @@ +# +# Author:: Seth Chisamore +# Cookbook Name:: php +# Resource:: pear_package +# +# Copyright:: 2011, Opscode, Inc +# +# 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. +# + +default_action :install +actions :install, :upgrade, :remove, :purge + +attribute :package_name, :kind_of => String, :name_attribute => true +attribute :version, :default => nil +attribute :channel, :kind_of => String +attribute :options, :kind_of => String +attribute :directives, :kind_of => Hash, :default => {} +attribute :zend_extensions, :kind_of => Array, :default => [] +attribute :preferred_state, :default => 'stable' diff --git a/cookbooks/php/resources/pear_channel.rb b/cookbooks/php/resources/pear_channel.rb new file mode 100644 index 0000000..2a88f05 --- /dev/null +++ b/cookbooks/php/resources/pear_channel.rb @@ -0,0 +1,29 @@ +# +# Author:: Seth Chisamore +# Cookbook Name:: php +# Resource:: pear_channel +# +# Copyright:: 2011, Opscode, Inc +# +# 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. +# + +default_action :discover +actions :discover, :add, :update, :remove + +attribute :channel_name, :kind_of => String, :name_attribute => true +attribute :channel_xml, :kind_of => String + +# TODO: add authenticated channel support! +# attribute :username, :kind_of => String +# attribute :password, :kind_of => String diff --git a/cookbooks/php/templates/centos/php.ini.erb b/cookbooks/php/templates/centos/php.ini.erb new file mode 100644 index 0000000..c84d60d --- /dev/null +++ b/cookbooks/php/templates/centos/php.ini.erb @@ -0,0 +1,1225 @@ +[PHP] + +;;;;;;;;;;;;;;;;;;; +; About php.ini ; +;;;;;;;;;;;;;;;;;;; +; This file controls many aspects of PHP's behavior. In order for PHP to +; read it, it must be named 'php.ini'. PHP looks for it in the current +; working directory, in the path designated by the environment variable +; PHPRC, and in the path that was defined in compile time (in that order). +; Under Windows, the compile-time path is the Windows directory. The +; path in which the php.ini file is looked for can be overridden using +; the -c argument in command line mode. +; +; The syntax of the file is extremely simple. Whitespace and Lines +; beginning with a semicolon are silently ignored (as you probably guessed). +; Section headers (e.g. [Foo]) are also silently ignored, even though +; they might mean something in the future. +; +; Directives are specified using the following syntax: +; directive = value +; Directive names are *case sensitive* - foo=bar is different from FOO=bar. +; +; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one +; of the INI constants (On, Off, True, False, Yes, No and None) or an expression +; (e.g. E_ALL & ~E_NOTICE), or a quoted string ("foo"). +; +; Expressions in the INI file are limited to bitwise operators and parentheses: +; | bitwise OR +; & bitwise AND +; ~ bitwise NOT +; ! boolean NOT +; +; Boolean flags can be turned on using the values 1, On, True or Yes. +; They can be turned off using the values 0, Off, False or No. +; +; An empty string can be denoted by simply not writing anything after the equal +; sign, or by using the None keyword: +; +; foo = ; sets foo to an empty string +; foo = none ; sets foo to an empty string +; foo = "none" ; sets foo to the string 'none' +; +; If you use constants in your value, and these constants belong to a +; dynamically loaded extension (either a PHP extension or a Zend extension), +; you may only use these constants *after* the line that loads the extension. +; +; +;;;;;;;;;;;;;;;;;;; +; About this file ; +;;;;;;;;;;;;;;;;;;; +; This is the recommended, PHP 5-style version of the php.ini-dist file. It +; sets some non standard settings, that make PHP more efficient, more secure, +; and encourage cleaner coding. +; +; The price is that with these settings, PHP may be incompatible with some +; applications, and sometimes, more difficult to develop with. Using this +; file is warmly recommended for production sites. As all of the changes from +; the standard settings are thoroughly documented, you can go over each one, +; and decide whether you want to use it or not. +; +; For general information about the php.ini file, please consult the php.ini-dist +; file, included in your PHP distribution. +; +; This file is different from the php.ini-dist file in the fact that it features +; different values for several directives, in order to improve performance, while +; possibly breaking compatibility with the standard out-of-the-box behavior of +; PHP. Please make sure you read what's different, and modify your scripts +; accordingly, if you decide to use this file instead. +; +; - register_globals = Off [Security, Performance] +; Global variables are no longer registered for input data (POST, GET, cookies, +; environment and other server variables). Instead of using $foo, you must use +; you can use $_REQUEST["foo"] (includes any variable that arrives through the +; request, namely, POST, GET and cookie variables), or use one of the specific +; $_GET["foo"], $_POST["foo"], $_COOKIE["foo"] or $_FILES["foo"], depending +; on where the input originates. Also, you can look at the +; import_request_variables() function. +; Note that register_globals is going to be depracated (i.e., turned off by +; default) in the next version of PHP, because it often leads to security bugs. +; Read http://php.net/manual/en/security.registerglobals.php for further +; information. +; - register_long_arrays = Off [Performance] +; Disables registration of the older (and deprecated) long predefined array +; variables ($HTTP_*_VARS). Instead, use the superglobals that were +; introduced in PHP 4.1.0 +; - display_errors = Off [Security] +; With this directive set to off, errors that occur during the execution of +; scripts will no longer be displayed as a part of the script output, and thus, +; will no longer be exposed to remote users. With some errors, the error message +; content may expose information about your script, web server, or database +; server that may be exploitable for hacking. Production sites should have this +; directive set to off. +; - log_errors = On [Security] +; This directive complements the above one. Any errors that occur during the +; execution of your script will be logged (typically, to your server's error log, +; but can be configured in several ways). Along with setting display_errors to off, +; this setup gives you the ability to fully understand what may have gone wrong, +; without exposing any sensitive information to remote users. +; - output_buffering = 4096 [Performance] +; Set a 4KB output buffer. Enabling output buffering typically results in less +; writes, and sometimes less packets sent on the wire, which can often lead to +; better performance. The gain this directive actually yields greatly depends +; on which Web server you're working with, and what kind of scripts you're using. +; - register_argc_argv = Off [Performance] +; Disables registration of the somewhat redundant $argv and $argc global +; variables. +; - magic_quotes_gpc = Off [Performance] +; Input data is no longer escaped with slashes so that it can be sent into +; SQL databases without further manipulation. Instead, you should use the +; function addslashes() on each input element you wish to send to a database. +; - variables_order = "GPCS" [Performance] +; The environment variables are not hashed into the $_ENV. To access +; environment variables, you can use getenv() instead. +; - error_reporting = E_ALL [Code Cleanliness, Security(?)] +; By default, PHP surpresses errors of type E_NOTICE. These error messages +; are emitted for non-critical errors, but that could be a symptom of a bigger +; problem. Most notably, this will cause error messages about the use +; of uninitialized variables to be displayed. +; - allow_call_time_pass_reference = Off [Code cleanliness] +; It's not possible to decide to force a variable to be passed by reference +; when calling a function. The PHP 4 style to do this is by making the +; function require the relevant argument by reference. + + +;;;;;;;;;;;;;;;;;;;; +; Language Options ; +;;;;;;;;;;;;;;;;;;;; + +; Enable the PHP scripting language engine under Apache. +engine = On + +; Enable compatibility mode with Zend Engine 1 (PHP 4.x) +zend.ze1_compatibility_mode = Off + +; Allow the tags are recognized. +; NOTE: Using short tags should be avoided when developing applications or +; libraries that are meant for redistribution, or deployment on PHP +; servers which are not under your control, because short tags may not +; be supported on the target server. For portable, redistributable code, +; be sure not to use short tags. +short_open_tag = On + +; Allow ASP-style <% %> tags. +asp_tags = Off + +; The number of significant digits displayed in floating point numbers. +precision = 14 + +; Enforce year 2000 compliance (will cause problems with non-compliant browsers) +y2k_compliance = On + +; Output buffering allows you to send header lines (including cookies) even +; after you send body content, at the price of slowing PHP's output layer a +; bit. You can enable output buffering during runtime by calling the output +; buffering functions. You can also enable output buffering for all files by +; setting this directive to On. If you wish to limit the size of the buffer +; to a certain size - you can use a maximum number of bytes instead of 'On', as +; a value for this directive (e.g., output_buffering=4096). +output_buffering = 4096 + +; You can redirect all of the output of your scripts to a function. For +; example, if you set output_handler to "mb_output_handler", character +; encoding will be transparently converted to the specified encoding. +; Setting any output handler automatically turns on output buffering. +; Note: People who wrote portable scripts should not depend on this ini +; directive. Instead, explicitly set the output handler using ob_start(). +; Using this ini directive may cause problems unless you know what script +; is doing. +; Note: You cannot use both "mb_output_handler" with "ob_iconv_handler" +; and you cannot use both "ob_gzhandler" and "zlib.output_compression". +; Note: output_handler must be empty if this is set 'On' !!!! +; Instead you must use zlib.output_handler. +;output_handler = + +; Transparent output compression using the zlib library +; Valid values for this option are 'off', 'on', or a specific buffer size +; to be used for compression (default is 4KB) +; Note: Resulting chunk size may vary due to nature of compression. PHP +; outputs chunks that are few hundreds bytes each as a result of +; compression. If you prefer a larger chunk size for better +; performance, enable output_buffering in addition. +; Note: You need to use zlib.output_handler instead of the standard +; output_handler, or otherwise the output will be corrupted. +zlib.output_compression = Off + +; You cannot specify additional output handlers if zlib.output_compression +; is activated here. This setting does the same as output_handler but in +; a different order. +;zlib.output_handler = + +; Implicit flush tells PHP to tell the output layer to flush itself +; automatically after every output block. This is equivalent to calling the +; PHP function flush() after each and every call to print() or echo() and each +; and every HTML block. Turning this option on has serious performance +; implications and is generally recommended for debugging purposes only. +implicit_flush = Off + +; The unserialize callback function will be called (with the undefined class' +; name as parameter), if the unserializer finds an undefined class +; which should be instantiated. +; A warning appears if the specified function is not defined, or if the +; function doesn't include/implement the missing class. +; So only set this entry, if you really want to implement such a +; callback-function. +unserialize_callback_func= + +; When floats & doubles are serialized store serialize_precision significant +; digits after the floating point. The default value ensures that when floats +; are decoded with unserialize, the data will remain the same. +serialize_precision = 100 + +; Whether to enable the ability to force arguments to be passed by reference +; at function call time. This method is deprecated and is likely to be +; unsupported in future versions of PHP/Zend. The encouraged method of +; specifying which arguments should be passed by reference is in the function +; declaration. You're encouraged to try and turn this option Off and make +; sure your scripts work properly with it in order to ensure they will work +; with future versions of the language (you will receive a warning each time +; you use this feature, and the argument will be passed by value instead of by +; reference). +allow_call_time_pass_reference = Off + +; +; Safe Mode +; +safe_mode = Off + +; By default, Safe Mode does a UID compare check when +; opening files. If you want to relax this to a GID compare, +; then turn on safe_mode_gid. +safe_mode_gid = Off + +; When safe_mode is on, UID/GID checks are bypassed when +; including files from this directory and its subdirectories. +; (directory must also be in include_path or full path must +; be used when including) +safe_mode_include_dir = + +; When safe_mode is on, only executables located in the safe_mode_exec_dir +; will be allowed to be executed via the exec family of functions. +safe_mode_exec_dir = + +; Setting certain environment variables may be a potential security breach. +; This directive contains a comma-delimited list of prefixes. In Safe Mode, +; the user may only alter environment variables whose names begin with the +; prefixes supplied here. By default, users will only be able to set +; environment variables that begin with PHP_ (e.g. PHP_FOO=BAR). +; +; Note: If this directive is empty, PHP will let the user modify ANY +; environment variable! +safe_mode_allowed_env_vars = PHP_ + +; This directive contains a comma-delimited list of environment variables that +; the end user won't be able to change using putenv(). These variables will be +; protected even if safe_mode_allowed_env_vars is set to allow to change them. +safe_mode_protected_env_vars = LD_LIBRARY_PATH + +; open_basedir, if set, limits all file operations to the defined directory +; and below. This directive makes most sense if used in a per-directory +; or per-virtualhost web server configuration file. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +;open_basedir = + +; This directive allows you to disable certain functions for security reasons. +; It receives a comma-delimited list of function names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +disable_functions = + +; This directive allows you to disable certain classes for security reasons. +; It receives a comma-delimited list of class names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +disable_classes = + +; Colors for Syntax Highlighting mode. Anything that's acceptable in +; would work. +;highlight.string = #DD0000 +;highlight.comment = #FF9900 +;highlight.keyword = #007700 +;highlight.bg = #FFFFFF +;highlight.default = #0000BB +;highlight.html = #000000 + +; If enabled, the request will be allowed to complete even if the user aborts +; the request. Consider enabling it if executing long request, which may end up +; being interrupted by the user or a browser timing out. +; ignore_user_abort = On + +; Determines the size of the realpath cache to be used by PHP. This value should +; be increased on systems where PHP opens many files to reflect the quantity of +; the file operations performed. +; realpath_cache_size=16k + +; Duration of time, in seconds for which to cache realpath information for a given +; file or directory. For systems with rarely changing files, consider increasing this +; value. +; realpath_cache_ttl=120 + +; +; Misc +; +; Decides whether PHP may expose the fact that it is installed on the server +; (e.g. by adding its signature to the Web server header). It is no security +; threat in any way, but it makes it possible to determine whether you use PHP +; on your server or not. +expose_php = On + + +;;;;;;;;;;;;;;;;;;; +; Resource Limits ; +;;;;;;;;;;;;;;;;;;; + +max_execution_time = 30 ; Maximum execution time of each script, in seconds +max_input_time = 60 ; Maximum amount of time each script may spend parsing request data +memory_limit = 128M ; Maximum amount of memory a script may consume + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Error handling and logging ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; error_reporting is a bit-field. Or each number up to get desired error +; reporting level +; E_ALL - All errors and warnings (doesn't include E_STRICT) +; E_ERROR - fatal run-time errors +; E_WARNING - run-time warnings (non-fatal errors) +; E_PARSE - compile-time parse errors +; E_NOTICE - run-time notices (these are warnings which often result +; from a bug in your code, but it's possible that it was +; intentional (e.g., using an uninitialized variable and +; relying on the fact it's automatically initialized to an +; empty string) +; E_STRICT - run-time notices, enable to have PHP suggest changes +; to your code which will ensure the best interoperability +; and forward compatibility of your code +; E_CORE_ERROR - fatal errors that occur during PHP's initial startup +; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's +; initial startup +; E_COMPILE_ERROR - fatal compile-time errors +; E_COMPILE_WARNING - compile-time warnings (non-fatal errors) +; E_USER_ERROR - user-generated error message +; E_USER_WARNING - user-generated warning message +; E_USER_NOTICE - user-generated notice message +; +; Examples: +; +; - Show all errors, except for notices and coding standards warnings +; +;error_reporting = E_ALL & ~E_NOTICE +; +; - Show all errors, except for notices +; +;error_reporting = E_ALL & ~E_NOTICE | E_STRICT +; +; - Show only errors +; +;error_reporting = E_COMPILE_ERROR|E_ERROR|E_CORE_ERROR +; +; - Show all errors, except coding standards warnings +; +error_reporting = E_ALL + +; Print out errors (as a part of the output). For production web sites, +; you're strongly encouraged to turn this feature off, and use error logging +; instead (see below). Keeping display_errors enabled on a production web site +; may reveal security information to end users, such as file paths on your Web +; server, your database schema or other information. +display_errors = Off + +; Even when display_errors is on, errors that occur during PHP's startup +; sequence are not displayed. It's strongly recommended to keep +; display_startup_errors off, except for when debugging. +display_startup_errors = Off + +; Log errors into a log file (server-specific log, stderr, or error_log (below)) +; As stated above, you're strongly advised to use error logging in place of +; error displaying on production web sites. +log_errors = On + +; Set maximum length of log_errors. In error_log information about the source is +; added. The default is 1024 and 0 allows to not apply any maximum length at all. +log_errors_max_len = 1024 + +; Do not log repeated messages. Repeated errors must occur in same file on same +; line until ignore_repeated_source is set true. +ignore_repeated_errors = Off + +; Ignore source of message when ignoring repeated messages. When this setting +; is On you will not log errors with repeated messages from different files or +; sourcelines. +ignore_repeated_source = Off + +; If this parameter is set to Off, then memory leaks will not be shown (on +; stdout or in the log). This has only effect in a debug compile, and if +; error reporting includes E_WARNING in the allowed list +report_memleaks = On + +; Store the last error/warning message in $php_errormsg (boolean). +track_errors = Off + +; Disable the inclusion of HTML tags in error messages. +; Note: Never use this feature for production boxes. +;html_errors = Off + +; If html_errors is set On PHP produces clickable error messages that direct +; to a page describing the error or function causing the error in detail. +; You can download a copy of the PHP manual from http://www.php.net/docs.php +; and change docref_root to the base URL of your local copy including the +; leading '/'. You must also specify the file extension being used including +; the dot. +; Note: Never use this feature for production boxes. +;docref_root = "/phpmanual/" +;docref_ext = .html + +; String to output before an error message. +;error_prepend_string = "" + +; String to output after an error message. +;error_append_string = "" + +; Log errors to specified file. +;error_log = filename + +; Log errors to syslog (Event Log on NT, not valid in Windows 95). +;error_log = syslog + + +;;;;;;;;;;;;;;;;; +; Data Handling ; +;;;;;;;;;;;;;;;;; +; +; Note - track_vars is ALWAYS enabled as of PHP 4.0.3 + +; The separator used in PHP generated URLs to separate arguments. +; Default is "&". +;arg_separator.output = "&" + +; List of separator(s) used by PHP to parse input URLs into variables. +; Default is "&". +; NOTE: Every character in this directive is considered as separator! +;arg_separator.input = ";&" + +; This directive describes the order in which PHP registers GET, POST, Cookie, +; Environment and Built-in variables (G, P, C, E & S respectively, often +; referred to as EGPCS or GPC). Registration is done from left to right, newer +; values override older values. +variables_order = "EGPCS" + +; Whether or not to register the EGPCS variables as global variables. You may +; want to turn this off if you don't want to clutter your scripts' global scope +; with user data. This makes most sense when coupled with track_vars - in which +; case you can access all of the GPC variables through the $HTTP_*_VARS[], +; variables. +; +; You should do your best to write your scripts so that they do not require +; register_globals to be on; Using form variables as globals can easily lead +; to possible security problems, if the code is not very well thought of. +register_globals = Off + +; Whether or not to register the old-style input arrays, HTTP_GET_VARS +; and friends. If you're not using them, it's recommended to turn them off, +; for performance reasons. +register_long_arrays = Off + +; This directive tells PHP whether to declare the argv&argc variables (that +; would contain the GET information). If you don't use these variables, you +; should turn it off for increased performance. +register_argc_argv = Off + +; When enabled, the SERVER and ENV variables are created when they're first +; used (Just In Time) instead of when the script starts. If these variables +; are not used within a script, having this directive on will result in a +; performance gain. The PHP directives register_globals, register_long_arrays, +; and register_argc_argv must be disabled for this directive to have any affect. +auto_globals_jit = On + +; Maximum size of POST data that PHP will accept. +post_max_size = 8M + +; Magic quotes +; + +; Magic quotes for incoming GET/POST/Cookie data. +magic_quotes_gpc = Off + +; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc. +magic_quotes_runtime = Off + +; Use Sybase-style magic quotes (escape ' with '' instead of \'). +magic_quotes_sybase = Off + +; Automatically add files before or after any PHP document. +auto_prepend_file = +auto_append_file = + +; As of 4.0b4, PHP always outputs a character encoding by default in +; the Content-type: header. To disable sending of the charset, simply +; set it to be empty. +; +; PHP's built-in default is text/html +default_mimetype = "text/html" +;default_charset = "iso-8859-1" + +; Always populate the $HTTP_RAW_POST_DATA variable. +;always_populate_raw_post_data = On + + +;;;;;;;;;;;;;;;;;;;;;;;;; +; Paths and Directories ; +;;;;;;;;;;;;;;;;;;;;;;;;; + +; UNIX: "/path1:/path2" +;include_path = ".:/php/includes" +; +; Windows: "\path1;\path2" +;include_path = ".;c:\php\includes" + +; The root of the PHP pages, used only if nonempty. +; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root +; if you are running php as a CGI under any web server (other than IIS) +; see documentation for security issues. The alternate is to use the +; cgi.force_redirect configuration below +doc_root = + +; The directory under which PHP opens the script using /~username used only +; if nonempty. +user_dir = + +; Directory in which the loadable extensions (modules) reside. +extension_dir = "<%= node['php']['ext_dir'] %>" + +; Whether or not to enable the dl() function. The dl() function does NOT work +; properly in multithreaded servers, such as IIS or Zeus, and is automatically +; disabled on them. +enable_dl = On + +; cgi.force_redirect is necessary to provide security running PHP as a CGI under +; most web servers. Left undefined, PHP turns this on by default. You can +; turn it off here AT YOUR OWN RISK +; **You CAN safely turn this off for IIS, in fact, you MUST.** +; cgi.force_redirect = 1 + +; if cgi.nph is enabled it will force cgi to always sent Status: 200 with +; every request. +; cgi.nph = 1 + +; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape +; (iPlanet) web servers, you MAY need to set an environment variable name that PHP +; will look for to know it is OK to continue execution. Setting this variable MAY +; cause security issues, KNOW WHAT YOU ARE DOING FIRST. +; cgi.redirect_status_env = ; + +; FastCGI under IIS (on WINNT based OS) supports the ability to impersonate +; security tokens of the calling client. This allows IIS to define the +; security context that the request runs under. mod_fastcgi under Apache +; does not currently support this feature (03/17/2002) +; Set to 1 if running under IIS. Default is zero. +; fastcgi.impersonate = 1; + +; Disable logging through FastCGI connection +; fastcgi.log = 0 + +; cgi.rfc2616_headers configuration option tells PHP what type of headers to +; use when sending HTTP response code. If it's set 0 PHP sends Status: header that +; is supported by Apache. When this option is set to 1 PHP will send +; RFC2616 compliant header. +; Default is zero. +;cgi.rfc2616_headers = 0 + + +;;;;;;;;;;;;;;;; +; File Uploads ; +;;;;;;;;;;;;;;;; + +; Whether to allow HTTP file uploads. +file_uploads = On + +; Temporary directory for HTTP uploaded files (will use system default if not +; specified). +;upload_tmp_dir = + +; Maximum allowed size for uploaded files. +upload_max_filesize = 2M + + +;;;;;;;;;;;;;;;;;; +; Fopen wrappers ; +;;;;;;;;;;;;;;;;;; + +; Whether to allow the treatment of URLs (like http:// or ftp://) as files. +allow_url_fopen = On + +; Define the anonymous ftp password (your email address) +;from="john@doe.com" + +; Define the User-Agent string +; user_agent="PHP" + +; Default timeout for socket based streams (seconds) +default_socket_timeout = 60 + +; If your scripts have to deal with files from Macintosh systems, +; or you are running on a Mac and need to deal with files from +; unix or win32 systems, setting this flag will cause PHP to +; automatically detect the EOL character in those files so that +; fgets() and file() will work regardless of the source of the file. +; auto_detect_line_endings = Off + + +;;;;;;;;;;;;;;;;;;;;;; +; Dynamic Extensions ; +;;;;;;;;;;;;;;;;;;;;;; +; +; If you wish to have an extension loaded automatically, use the following +; syntax: +; +; extension=modulename.extension +; +; For example: +; +; extension=msql.so +; +; Note that it should be the name of the module only; no directory information +; needs to go here. Specify the location of the extension with the +; extension_dir directive above. + + +;;;; +; Note: packaged extension modules are now loaded via the .ini files +; found in the directory /etc/php.d; these are loaded by default. +;;;; + + +;;;;;;;;;;;;;;;;;;; +; Module Settings ; +;;;;;;;;;;;;;;;;;;; + +[Date] +; Defines the default timezone used by the date functions +;date.timezone = + +[Syslog] +; Whether or not to define the various syslog variables (e.g. $LOG_PID, +; $LOG_CRON, etc.). Turning it off is a good idea performance-wise. In +; runtime, you can define these variables by calling define_syslog_variables(). +define_syslog_variables = Off + +[mail function] +; For Win32 only. +SMTP = localhost +smtp_port = 25 + +; For Win32 only. +;sendmail_from = me@example.com + +; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). +sendmail_path = /usr/sbin/sendmail -t -i + +; Force the addition of the specified parameters to be passed as extra parameters +; to the sendmail binary. These parameters will always replace the value of +; the 5th parameter to mail(), even in safe mode. +;mail.force_extra_parameters = + +[SQL] +sql.safe_mode = Off + +[ODBC] +;odbc.default_db = Not yet implemented +;odbc.default_user = Not yet implemented +;odbc.default_pw = Not yet implemented + +; Allow or prevent persistent links. +odbc.allow_persistent = On + +; Check that a connection is still valid before reuse. +odbc.check_persistent = On + +; Maximum number of persistent links. -1 means no limit. +odbc.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +odbc.max_links = -1 + +; Handling of LONG fields. Returns number of bytes to variables. 0 means +; passthru. +odbc.defaultlrl = 4096 + +; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char. +; See the documentation on odbc_binmode and odbc_longreadlen for an explanation +; of uodbc.defaultlrl and uodbc.defaultbinmode +odbc.defaultbinmode = 1 + +[MySQL] +; Allow or prevent persistent links. +mysql.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +mysql.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +mysql.max_links = -1 + +; Default port number for mysql_connect(). If unset, mysql_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +mysql.default_port = + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +mysql.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +mysql.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +mysql.default_user = + +; Default password for mysql_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysql.default_password") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +mysql.default_password = + +; Maximum time (in secondes) for connect timeout. -1 means no limit +mysql.connect_timeout = 60 + +; Trace mode. When trace_mode is active (=On), warnings for table/index scans and +; SQL-Errors will be displayed. +mysql.trace_mode = Off + +[MySQLi] + +; Maximum number of links. -1 means no limit. +mysqli.max_links = -1 + +; Default port number for mysqli_connect(). If unset, mysqli_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +mysqli.default_port = 3306 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +mysqli.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +mysqli.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +mysqli.default_user = + +; Default password for mysqli_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +mysqli.default_pw = + +; Allow or prevent reconnect +mysqli.reconnect = Off + +[mSQL] +; Allow or prevent persistent links. +msql.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +msql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +msql.max_links = -1 + +[PostgresSQL] +; Allow or prevent persistent links. +pgsql.allow_persistent = On + +; Detect broken persistent links always with pg_pconnect(). +; Auto reset feature requires a little overheads. +pgsql.auto_reset_persistent = Off + +; Maximum number of persistent links. -1 means no limit. +pgsql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +pgsql.max_links = -1 + +; Ignore PostgreSQL backends Notice message or not. +; Notice message logging require a little overheads. +pgsql.ignore_notice = 0 + +; Log PostgreSQL backends Noitce message or not. +; Unless pgsql.ignore_notice=0, module cannot log notice message. +pgsql.log_notice = 0 + +[Sybase] +; Allow or prevent persistent links. +sybase.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +sybase.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +sybase.max_links = -1 + +;sybase.interface_file = "/usr/sybase/interfaces" + +; Minimum error severity to display. +sybase.min_error_severity = 10 + +; Minimum message severity to display. +sybase.min_message_severity = 10 + +; Compatability mode with old versions of PHP 3.0. +; If on, this will cause PHP to automatically assign types to results according +; to their Sybase type, instead of treating them all as strings. This +; compatability mode will probably not stay around forever, so try applying +; whatever necessary changes to your code, and turn it off. +sybase.compatability_mode = Off + +[Sybase-CT] +; Allow or prevent persistent links. +sybct.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +sybct.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +sybct.max_links = -1 + +; Minimum server message severity to display. +sybct.min_server_severity = 10 + +; Minimum client message severity to display. +sybct.min_client_severity = 10 + +[bcmath] +; Number of decimal digits for all bcmath functions. +bcmath.scale = 0 + +[browscap] +;browscap = extra/browscap.ini + +[Informix] +; Default host for ifx_connect() (doesn't apply in safe mode). +ifx.default_host = + +; Default user for ifx_connect() (doesn't apply in safe mode). +ifx.default_user = + +; Default password for ifx_connect() (doesn't apply in safe mode). +ifx.default_password = + +; Allow or prevent persistent links. +ifx.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +ifx.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +ifx.max_links = -1 + +; If on, select statements return the contents of a text blob instead of its id. +ifx.textasvarchar = 0 + +; If on, select statements return the contents of a byte blob instead of its id. +ifx.byteasvarchar = 0 + +; Trailing blanks are stripped from fixed-length char columns. May help the +; life of Informix SE users. +ifx.charasvarchar = 0 + +; If on, the contents of text and byte blobs are dumped to a file instead of +; keeping them in memory. +ifx.blobinfile = 0 + +; NULL's are returned as empty strings, unless this is set to 1. In that case, +; NULL's are returned as string 'NULL'. +ifx.nullformat = 0 + +[Session] +; Handler used to store/retrieve data. +session.save_handler = files + +; Argument passed to save_handler. In the case of files, this is the path +; where data files are stored. Note: Windows users have to change this +; variable in order to use PHP's session functions. +; +; As of PHP 4.0.1, you can define the path as: +; +; session.save_path = "N;/path" +; +; where N is an integer. Instead of storing all the session files in +; /path, what this will do is use subdirectories N-levels deep, and +; store the session data in those directories. This is useful if you +; or your OS have problems with lots of files in one directory, and is +; a more efficient layout for servers that handle lots of sessions. +; +; NOTE 1: PHP will not create this directory structure automatically. +; You can use the script in the ext/session dir for that purpose. +; NOTE 2: See the section on garbage collection below if you choose to +; use subdirectories for session storage +; +; The file storage module creates files using mode 600 by default. +; You can change that by using +; +; session.save_path = "N;MODE;/path" +; +; where MODE is the octal representation of the mode. Note that this +; does not overwrite the process's umask. +session.save_path = "/var/lib/php/session" + +; Whether to use cookies. +session.use_cookies = 1 + +; This option enables administrators to make their users invulnerable to +; attacks which involve passing session ids in URLs; defaults to 0. +; session.use_only_cookies = 1 + +; Name of the session (used as cookie name). +session.name = PHPSESSID + +; Initialize session on request startup. +session.auto_start = 0 + +; Lifetime in seconds of cookie or, if 0, until browser is restarted. +session.cookie_lifetime = 0 + +; The path for which the cookie is valid. +session.cookie_path = / + +; The domain for which the cookie is valid. +session.cookie_domain = + +; Handler used to serialize data. php is the standard serializer of PHP. +session.serialize_handler = php + +; Define the probability that the 'garbage collection' process is started +; on every session initialization. +; The probability is calculated by using gc_probability/gc_divisor, +; e.g. 1/100 means there is a 1% chance that the GC process starts +; on each request. + +session.gc_probability = 1 +session.gc_divisor = 1000 + +; After this number of seconds, stored data will be seen as 'garbage' and +; cleaned up by the garbage collection process. +session.gc_maxlifetime = 1440 + +; NOTE: If you are using the subdirectory option for storing session files +; (see session.save_path above), then garbage collection does *not* +; happen automatically. You will need to do your own garbage +; collection through a shell script, cron entry, or some other method. +; For example, the following script would is the equivalent of +; setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): +; cd /path/to/sessions; find -cmin +24 | xargs rm + +; PHP 4.2 and less have an undocumented feature/bug that allows you to +; to initialize a session variable in the global scope, albeit register_globals +; is disabled. PHP 4.3 and later will warn you, if this feature is used. +; You can disable the feature and the warning separately. At this time, +; the warning is only displayed, if bug_compat_42 is enabled. + +session.bug_compat_42 = 0 +session.bug_compat_warn = 1 + +; Check HTTP Referer to invalidate externally stored URLs containing ids. +; HTTP_REFERER has to contain this substring for the session to be +; considered as valid. +session.referer_check = + +; How many bytes to read from the file. +session.entropy_length = 0 + +; Specified here to create the session id. +session.entropy_file = + +;session.entropy_length = 16 + +;session.entropy_file = /dev/urandom + +; Set to {nocache,private,public,} to determine HTTP caching aspects +; or leave this empty to avoid sending anti-caching headers. +session.cache_limiter = nocache + +; Document expires after n minutes. +session.cache_expire = 180 + +; trans sid support is disabled by default. +; Use of trans sid may risk your users security. +; Use this option with caution. +; - User may send URL contains active session ID +; to other person via. email/irc/etc. +; - URL that contains active session ID may be stored +; in publically accessible computer. +; - User may access your site with the same session ID +; always using URL stored in browser's history or bookmarks. +session.use_trans_sid = 0 + +; Select a hash function +; 0: MD5 (128 bits) +; 1: SHA-1 (160 bits) +session.hash_function = 0 + +; Define how many bits are stored in each character when converting +; the binary hash data to something readable. +; +; 4 bits: 0-9, a-f +; 5 bits: 0-9, a-v +; 6 bits: 0-9, a-z, A-Z, "-", "," +session.hash_bits_per_character = 5 + +; The URL rewriter will look for URLs in a defined set of HTML tags. +; form/fieldset are special; if you include them here, the rewriter will +; add a hidden field with the info which is otherwise appended +; to URLs. If you want XHTML conformity, remove the form entry. +; Note that all valid entries require a "=", even if no value follows. +url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry" + +[MSSQL] +; Allow or prevent persistent links. +mssql.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +mssql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +mssql.max_links = -1 + +; Minimum error severity to display. +mssql.min_error_severity = 10 + +; Minimum message severity to display. +mssql.min_message_severity = 10 + +; Compatability mode with old versions of PHP 3.0. +mssql.compatability_mode = Off + +; Connect timeout +;mssql.connect_timeout = 5 + +; Query timeout +;mssql.timeout = 60 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textlimit = 4096 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textsize = 4096 + +; Limits the number of records in each batch. 0 = all records in one batch. +;mssql.batchsize = 0 + +; Specify how datetime and datetim4 columns are returned +; On => Returns data converted to SQL server settings +; Off => Returns values as YYYY-MM-DD hh:mm:ss +;mssql.datetimeconvert = On + +; Use NT authentication when connecting to the server +mssql.secure_connection = Off + +; Specify max number of processes. -1 = library default +; msdlib defaults to 25 +; FreeTDS defaults to 4096 +;mssql.max_procs = -1 + +; Specify client character set. +; If empty or not set the client charset from freetds.comf is used +; This is only used when compiled with FreeTDS +;mssql.charset = "ISO-8859-1" + +[Assertion] +; Assert(expr); active by default. +;assert.active = On + +; Issue a PHP warning for each failed assertion. +;assert.warning = On + +; Don't bail out by default. +;assert.bail = Off + +; User-function to be called if an assertion fails. +;assert.callback = 0 + +; Eval the expression with current error_reporting(). Set to true if you want +; error_reporting(0) around the eval(). +;assert.quiet_eval = 0 + +[Verisign Payflow Pro] +; Default Payflow Pro server. +pfpro.defaulthost = "test-payflow.verisign.com" + +; Default port to connect to. +pfpro.defaultport = 443 + +; Default timeout in seconds. +pfpro.defaulttimeout = 30 + +; Default proxy IP address (if required). +;pfpro.proxyaddress = + +; Default proxy port. +;pfpro.proxyport = + +; Default proxy logon. +;pfpro.proxylogon = + +; Default proxy password. +;pfpro.proxypassword = + +[COM] +; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs +;com.typelib_file = +; allow Distributed-COM calls +;com.allow_dcom = true +; autoregister constants of a components typlib on com_load() +;com.autoregister_typelib = true +; register constants casesensitive +;com.autoregister_casesensitive = false +; show warnings on duplicate constat registrations +;com.autoregister_verbose = true + +[mbstring] +; language for internal character representation. +;mbstring.language = Japanese + +; internal/script encoding. +; Some encoding cannot work as internal encoding. +; (e.g. SJIS, BIG5, ISO-2022-*) +;mbstring.internal_encoding = EUC-JP + +; http input encoding. +;mbstring.http_input = auto + +; http output encoding. mb_output_handler must be +; registered as output buffer to function +;mbstring.http_output = SJIS + +; enable automatic encoding translation according to +; mbstring.internal_encoding setting. Input chars are +; converted to internal encoding by setting this to On. +; Note: Do _not_ use automatic encoding translation for +; portable libs/applications. +;mbstring.encoding_translation = Off + +; automatic encoding detection order. +; auto means +;mbstring.detect_order = auto + +; substitute_character used when character cannot be converted +; one from another +;mbstring.substitute_character = none; + +; overload(replace) single byte functions by mbstring functions. +; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(), +; etc. Possible values are 0,1,2,4 or combination of them. +; For example, 7 for overload everything. +; 0: No overload +; 1: Overload mail() function +; 2: Overload str*() functions +; 4: Overload ereg*() functions +;mbstring.func_overload = 0 + +; enable strict encoding detection. +;mbstring.strict_encoding = Off + +[FrontBase] +;fbsql.allow_persistent = On +;fbsql.autocommit = On +;fbsql.default_database = +;fbsql.default_database_password = +;fbsql.default_host = +;fbsql.default_password = +;fbsql.default_user = "_SYSTEM" +;fbsql.generate_warnings = Off +;fbsql.max_connections = 128 +;fbsql.max_links = 128 +;fbsql.max_persistent = -1 +;fbsql.max_results = 128 +;fbsql.batchSize = 1000 + +[gd] +; Tell the jpeg decode to libjpeg warnings and try to create +; a gd image. The warning will then be displayed as notices +; disabled by default +;gd.jpeg_ignore_warning = 0 + +[exif] +; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. +; With mbstring support this will automatically be converted into the encoding +; given by corresponding encode setting. When empty mbstring.internal_encoding +; is used. For the decode settings you can distinguish between motorola and +; intel byte order. A decode setting cannot be empty. +;exif.encode_unicode = ISO-8859-15 +;exif.decode_unicode_motorola = UCS-2BE +;exif.decode_unicode_intel = UCS-2LE +;exif.encode_jis = +;exif.decode_jis_motorola = JIS +;exif.decode_jis_intel = JIS + +[Tidy] +; The path to a default tidy configuration file to use when using tidy +;tidy.default_config = /usr/local/lib/php/default.tcfg + +; Should tidy clean and repair output automatically? +; WARNING: Do not use this option if you are generating non-html content +; such as dynamic images +tidy.clean_output = Off + +[soap] +; Enables or disables WSDL caching feature. +soap.wsdl_cache_enabled=1 +; Sets the directory name where SOAP extension will put cache files. +soap.wsdl_cache_dir="/tmp" +; (time to live) Sets the number of second while cached file will be used +; instead of original one. +soap.wsdl_cache_ttl=86400 + +; Local Variables: +; tab-width: 4 +; End: + +<% @directives.sort_by { |key, val| key }.each do |directive, value| -%> +<%= "#{directive}=\"#{value}\"" %> +<% end -%> diff --git a/cookbooks/php/templates/debian/php.ini.erb b/cookbooks/php/templates/debian/php.ini.erb new file mode 100644 index 0000000..1bd0a47 --- /dev/null +++ b/cookbooks/php/templates/debian/php.ini.erb @@ -0,0 +1,1857 @@ +[PHP] + +;;;;;;;;;;;;;;;;;;; +; About php.ini ; +;;;;;;;;;;;;;;;;;;; +; PHP's initialization file, generally called php.ini, is responsible for +; configuring many of the aspects of PHP's behavior. + +; PHP attempts to find and load this configuration from a number of locations. +; The following is a summary of its search order: +; 1. SAPI module specific location. +; 2. The PHPRC environment variable. (As of PHP 5.2.0) +; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0) +; 4. Current working directory (except CLI) +; 5. The web server's directory (for SAPI modules), or directory of PHP +; (otherwise in Windows) +; 6. The directory from the --with-config-file-path compile time option, or the +; Windows directory (C:\windows or C:\winnt) +; See the PHP docs for more specific information. +; http://php.net/configuration.file + +; The syntax of the file is extremely simple. Whitespace and Lines +; beginning with a semicolon are silently ignored (as you probably guessed). +; Section headers (e.g. [Foo]) are also silently ignored, even though +; they might mean something in the future. + +; Directives following the section heading [PATH=/www/mysite] only +; apply to PHP files in the /www/mysite directory. Directives +; following the section heading [HOST=www.example.com] only apply to +; PHP files served from www.example.com. Directives set in these +; special sections cannot be overridden by user-defined INI files or +; at runtime. Currently, [PATH=] and [HOST=] sections only work under +; CGI/FastCGI. +; http://php.net/ini.sections + +; Directives are specified using the following syntax: +; directive = value +; Directive names are *case sensitive* - foo=bar is different from FOO=bar. +; Directives are variables used to configure PHP or PHP extensions. +; There is no name validation. If PHP can't find an expected +; directive because it is not set or is mistyped, a default value will be used. + +; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one +; of the INI constants (On, Off, True, False, Yes, No and None) or an expression +; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a +; previously set variable or directive (e.g. ${foo}) + +; Expressions in the INI file are limited to bitwise operators and parentheses: +; | bitwise OR +; ^ bitwise XOR +; & bitwise AND +; ~ bitwise NOT +; ! boolean NOT + +; Boolean flags can be turned on using the values 1, On, True or Yes. +; They can be turned off using the values 0, Off, False or No. + +; An empty string can be denoted by simply not writing anything after the equal +; sign, or by using the None keyword: + +; foo = ; sets foo to an empty string +; foo = None ; sets foo to an empty string +; foo = "None" ; sets foo to the string 'None' + +; If you use constants in your value, and these constants belong to a +; dynamically loaded extension (either a PHP extension or a Zend extension), +; you may only use these constants *after* the line that loads the extension. + +;;;;;;;;;;;;;;;;;;; +; About this file ; +;;;;;;;;;;;;;;;;;;; +; PHP comes packaged with two INI files. One that is recommended to be used +; in production environments and one that is recommended to be used in +; development environments. + +; php.ini-production contains settings which hold security, performance and +; best practices at its core. But please be aware, these settings may break +; compatibility with older or less security conscience applications. We +; recommending using the production ini in production and testing environments. + +; php.ini-development is very similar to its production variant, except it's +; much more verbose when it comes to errors. We recommending using the +; development version only in development environments as errors shown to +; application users can inadvertently leak otherwise secure information. + +;;;;;;;;;;;;;;;;;;; +; Quick Reference ; +;;;;;;;;;;;;;;;;;;; +; The following are all the settings which are different in either the production +; or development versions of the INIs with respect to PHP's default behavior. +; Please see the actual settings later in the document for more details as to why +; we recommend these changes in PHP's behavior. + +; allow_call_time_pass_reference +; Default Value: On +; Development Value: Off +; Production Value: Off + +; display_errors +; Default Value: On +; Development Value: On +; Production Value: Off + +; display_startup_errors +; Default Value: Off +; Development Value: On +; Production Value: Off + +; error_reporting +; Default Value: E_ALL & ~E_NOTICE +; Development Value: E_ALL | E_STRICT +; Production Value: E_ALL & ~E_DEPRECATED + +; html_errors +; Default Value: On +; Development Value: On +; Production value: Off + +; log_errors +; Default Value: Off +; Development Value: On +; Production Value: On + +; magic_quotes_gpc +; Default Value: On +; Development Value: Off +; Production Value: Off + +; max_input_time +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) + +; output_buffering +; Default Value: Off +; Development Value: 4096 +; Production Value: 4096 + +; register_argc_argv +; Default Value: On +; Development Value: Off +; Production Value: Off + +; register_long_arrays +; Default Value: On +; Development Value: Off +; Production Value: Off + +; request_order +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" + +; session.bug_compat_42 +; Default Value: On +; Development Value: On +; Production Value: Off + +; session.bug_compat_warn +; Default Value: On +; Development Value: On +; Production Value: Off + +; session.gc_divisor +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 + +; session.hash_bits_per_character +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 + +; short_open_tag +; Default Value: On +; Development Value: Off +; Production Value: Off + +; track_errors +; Default Value: Off +; Development Value: On +; Production Value: Off + +; url_rewriter.tags +; Default Value: "a=href,area=href,frame=src,form=,fieldset=" +; Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry" + +; variables_order +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS" + +;;;;;;;;;;;;;;;;;;;; +; php.ini Options ; +;;;;;;;;;;;;;;;;;;;; +; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini" +;user_ini.filename = ".user.ini" + +; To disable this feature set this option to empty value +;user_ini.filename = + +; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes) +;user_ini.cache_ttl = 300 + +;;;;;;;;;;;;;;;;;;;; +; Language Options ; +;;;;;;;;;;;;;;;;;;;; + +; Enable the PHP scripting language engine under Apache. +; http://php.net/engine +engine = On + +; This directive determines whether or not PHP will recognize code between +; tags as PHP source which should be processed as such. It's been +; recommended for several years that you not use the short tag "short cut" and +; instead to use the full tag combination. With the wide spread use +; of XML and use of these tags by other languages, the server can become easily +; confused and end up parsing the wrong code in the wrong context. But because +; this short cut has been a feature for such a long time, it's currently still +; supported for backwards compatibility, but we recommend you don't use them. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/short-open-tag +short_open_tag = On + +; Allow ASP-style <% %> tags. +; http://php.net/asp-tags +asp_tags = Off + +; The number of significant digits displayed in floating point numbers. +; http://php.net/precision +precision = 14 + +; Enforce year 2000 compliance (will cause problems with non-compliant browsers) +; http://php.net/y2k-compliance +y2k_compliance = On + +; Output buffering is a mechanism for controlling how much output data +; (excluding headers and cookies) PHP should keep internally before pushing that +; data to the client. If your application's output exceeds this setting, PHP +; will send that data in chunks of roughly the size you specify. +; Turning on this setting and managing its maximum buffer size can yield some +; interesting side-effects depending on your application and web server. +; You may be able to send headers and cookies after you've already sent output +; through print or echo. You also may see performance benefits if your server is +; emitting less packets due to buffered output versus PHP streaming the output +; as it gets it. On production servers, 4096 bytes is a good setting for performance +; reasons. +; Note: Output buffering can also be controlled via Output Buffering Control +; functions. +; Possible Values: +; On = Enabled and buffer is unlimited. (Use with caution) +; Off = Disabled +; Integer = Enables the buffer and sets its maximum size in bytes. +; Note: This directive is hardcoded to Off for the CLI SAPI +; Default Value: Off +; Development Value: 4096 +; Production Value: 4096 +; http://php.net/output-buffering +output_buffering = 4096 + +; You can redirect all of the output of your scripts to a function. For +; example, if you set output_handler to "mb_output_handler", character +; encoding will be transparently converted to the specified encoding. +; Setting any output handler automatically turns on output buffering. +; Note: People who wrote portable scripts should not depend on this ini +; directive. Instead, explicitly set the output handler using ob_start(). +; Using this ini directive may cause problems unless you know what script +; is doing. +; Note: You cannot use both "mb_output_handler" with "ob_iconv_handler" +; and you cannot use both "ob_gzhandler" and "zlib.output_compression". +; Note: output_handler must be empty if this is set 'On' !!!! +; Instead you must use zlib.output_handler. +; http://php.net/output-handler +;output_handler = + +; Transparent output compression using the zlib library +; Valid values for this option are 'off', 'on', or a specific buffer size +; to be used for compression (default is 4KB) +; Note: Resulting chunk size may vary due to nature of compression. PHP +; outputs chunks that are few hundreds bytes each as a result of +; compression. If you prefer a larger chunk size for better +; performance, enable output_buffering in addition. +; Note: You need to use zlib.output_handler instead of the standard +; output_handler, or otherwise the output will be corrupted. +; http://php.net/zlib.output-compression +zlib.output_compression = Off + +; http://php.net/zlib.output-compression-level +;zlib.output_compression_level = -1 + +; You cannot specify additional output handlers if zlib.output_compression +; is activated here. This setting does the same as output_handler but in +; a different order. +; http://php.net/zlib.output-handler +;zlib.output_handler = + +; Implicit flush tells PHP to tell the output layer to flush itself +; automatically after every output block. This is equivalent to calling the +; PHP function flush() after each and every call to print() or echo() and each +; and every HTML block. Turning this option on has serious performance +; implications and is generally recommended for debugging purposes only. +; http://php.net/implicit-flush +; Note: This directive is hardcoded to On for the CLI SAPI +implicit_flush = Off + +; The unserialize callback function will be called (with the undefined class' +; name as parameter), if the unserializer finds an undefined class +; which should be instantiated. A warning appears if the specified function is +; not defined, or if the function doesn't include/implement the missing class. +; So only set this entry, if you really want to implement such a +; callback-function. +unserialize_callback_func = + +; When floats & doubles are serialized store serialize_precision significant +; digits after the floating point. The default value ensures that when floats +; are decoded with unserialize, the data will remain the same. +serialize_precision = 100 + +; This directive allows you to enable and disable warnings which PHP will issue +; if you pass a value by reference at function call time. Passing values by +; reference at function call time is a deprecated feature which will be removed +; from PHP at some point in the near future. The acceptable method for passing a +; value by reference to a function is by declaring the reference in the functions +; definition, not at call time. This directive does not disable this feature, it +; only determines whether PHP will warn you about it or not. These warnings +; should enabled in development environments only. +; Default Value: On (Suppress warnings) +; Development Value: Off (Issue warnings) +; Production Value: Off (Issue warnings) +; http://php.net/allow-call-time-pass-reference +allow_call_time_pass_reference = Off + +; Safe Mode +; http://php.net/safe-mode +safe_mode = Off + +; By default, Safe Mode does a UID compare check when +; opening files. If you want to relax this to a GID compare, +; then turn on safe_mode_gid. +; http://php.net/safe-mode-gid +safe_mode_gid = Off + +; When safe_mode is on, UID/GID checks are bypassed when +; including files from this directory and its subdirectories. +; (directory must also be in include_path or full path must +; be used when including) +; http://php.net/safe-mode-include-dir +safe_mode_include_dir = + +; When safe_mode is on, only executables located in the safe_mode_exec_dir +; will be allowed to be executed via the exec family of functions. +; http://php.net/safe-mode-exec-dir +safe_mode_exec_dir = + +; Setting certain environment variables may be a potential security breach. +; This directive contains a comma-delimited list of prefixes. In Safe Mode, +; the user may only alter environment variables whose names begin with the +; prefixes supplied here. By default, users will only be able to set +; environment variables that begin with PHP_ (e.g. PHP_FOO=BAR). +; Note: If this directive is empty, PHP will let the user modify ANY +; environment variable! +; http://php.net/safe-mode-allowed-env-vars +safe_mode_allowed_env_vars = PHP_ + +; This directive contains a comma-delimited list of environment variables that +; the end user won't be able to change using putenv(). These variables will be +; protected even if safe_mode_allowed_env_vars is set to allow to change them. +; http://php.net/safe-mode-protected-env-vars +safe_mode_protected_env_vars = LD_LIBRARY_PATH + +; open_basedir, if set, limits all file operations to the defined directory +; and below. This directive makes most sense if used in a per-directory +; or per-virtualhost web server configuration file. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/open-basedir +;open_basedir = + +; This directive allows you to disable certain functions for security reasons. +; It receives a comma-delimited list of function names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/disable-functions +disable_functions = + +; This directive allows you to disable certain classes for security reasons. +; It receives a comma-delimited list of class names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/disable-classes +disable_classes = + +; Colors for Syntax Highlighting mode. Anything that's acceptable in +; would work. +; http://php.net/syntax-highlighting +;highlight.string = #DD0000 +;highlight.comment = #FF9900 +;highlight.keyword = #007700 +;highlight.bg = #FFFFFF +;highlight.default = #0000BB +;highlight.html = #000000 + +; If enabled, the request will be allowed to complete even if the user aborts +; the request. Consider enabling it if executing long requests, which may end up +; being interrupted by the user or a browser timing out. PHP's default behavior +; is to disable this feature. +; http://php.net/ignore-user-abort +;ignore_user_abort = On + +; Determines the size of the realpath cache to be used by PHP. This value should +; be increased on systems where PHP opens many files to reflect the quantity of +; the file operations performed. +; http://php.net/realpath-cache-size +;realpath_cache_size = 16k + +; Duration of time, in seconds for which to cache realpath information for a given +; file or directory. For systems with rarely changing files, consider increasing this +; value. +; http://php.net/realpath-cache-ttl +;realpath_cache_ttl = 120 + +;;;;;;;;;;;;;;;;; +; Miscellaneous ; +;;;;;;;;;;;;;;;;; + +; Decides whether PHP may expose the fact that it is installed on the server +; (e.g. by adding its signature to the Web server header). It is no security +; threat in any way, but it makes it possible to determine whether you use PHP +; on your server or not. +; http://php.net/expose-php +expose_php = On + +;;;;;;;;;;;;;;;;;;; +; Resource Limits ; +;;;;;;;;;;;;;;;;;;; + +; Maximum execution time of each script, in seconds +; http://php.net/max-execution-time +; Note: This directive is hardcoded to 0 for the CLI SAPI +max_execution_time = 30 + +; Maximum amount of time each script may spend parsing request data. It's a good +; idea to limit this time on productions servers in order to eliminate unexpectedly +; long running scripts. +; Note: This directive is hardcoded to -1 for the CLI SAPI +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) +; http://php.net/max-input-time +max_input_time = 60 + +; Maximum input variable nesting level +; http://php.net/max-input-nesting-level +;max_input_nesting_level = 64 + +; Maximum amount of memory a script may consume (128MB) +; http://php.net/memory-limit +memory_limit = -1 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Error handling and logging ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; This directive informs PHP of which errors, warnings and notices you would like +; it to take action for. The recommended way of setting values for this +; directive is through the use of the error level constants and bitwise +; operators. The error level constants are below here for convenience as well as +; some common settings and their meanings. +; By default, PHP is set to take action on all errors, notices and warnings EXCEPT +; those related to E_NOTICE and E_STRICT, which together cover best practices and +; recommended coding standards in PHP. For performance reasons, this is the +; recommend error reporting setting. Your production server shouldn't be wasting +; resources complaining about best practices and coding standards. That's what +; development servers and development settings are for. +; Note: The php.ini-development file has this setting as E_ALL | E_STRICT. This +; means it pretty much reports everything which is exactly what you want during +; development and early testing. +; +; Error Level Constants: +; E_ALL - All errors and warnings (includes E_STRICT as of PHP 6.0.0) +; E_ERROR - fatal run-time errors +; E_RECOVERABLE_ERROR - almost fatal run-time errors +; E_WARNING - run-time warnings (non-fatal errors) +; E_PARSE - compile-time parse errors +; E_NOTICE - run-time notices (these are warnings which often result +; from a bug in your code, but it's possible that it was +; intentional (e.g., using an uninitialized variable and +; relying on the fact it's automatically initialized to an +; empty string) +; E_STRICT - run-time notices, enable to have PHP suggest changes +; to your code which will ensure the best interoperability +; and forward compatibility of your code +; E_CORE_ERROR - fatal errors that occur during PHP's initial startup +; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's +; initial startup +; E_COMPILE_ERROR - fatal compile-time errors +; E_COMPILE_WARNING - compile-time warnings (non-fatal errors) +; E_USER_ERROR - user-generated error message +; E_USER_WARNING - user-generated warning message +; E_USER_NOTICE - user-generated notice message +; E_DEPRECATED - warn about code that will not work in future versions +; of PHP +; E_USER_DEPRECATED - user-generated deprecation warnings +; +; Common Values: +; E_ALL & ~E_NOTICE (Show all errors, except for notices and coding standards warnings.) +; E_ALL & ~E_NOTICE | E_STRICT (Show all errors, except for notices) +; E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR (Show only errors) +; E_ALL | E_STRICT (Show all errors, warnings and notices including coding standards.) +; Default Value: E_ALL & ~E_NOTICE +; Development Value: E_ALL | E_STRICT +; Production Value: E_ALL & ~E_DEPRECATED +; http://php.net/error-reporting +error_reporting = E_ALL & ~E_DEPRECATED + +; This directive controls whether or not and where PHP will output errors, +; notices and warnings too. Error output is very useful during development, but +; it could be very dangerous in production environments. Depending on the code +; which is triggering the error, sensitive information could potentially leak +; out of your application such as database usernames and passwords or worse. +; It's recommended that errors be logged on production servers rather than +; having the errors sent to STDOUT. +; Possible Values: +; Off = Do not display any errors +; stderr = Display errors to STDERR (affects only CGI/CLI binaries!) +; On or stdout = Display errors to STDOUT +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/display-errors +display_errors = Off + +; The display of errors which occur during PHP's startup sequence are handled +; separately from display_errors. PHP's default behavior is to suppress those +; errors from clients. Turning the display of startup errors on can be useful in +; debugging configuration problems. But, it's strongly recommended that you +; leave this setting off on production servers. +; Default Value: Off +; Development Value: On +; Production Value: Off +; http://php.net/display-startup-errors +display_startup_errors = Off + +; Besides displaying errors, PHP can also log errors to locations such as a +; server-specific log, STDERR, or a location specified by the error_log +; directive found below. While errors should not be displayed on productions +; servers they should still be monitored and logging is a great way to do that. +; Default Value: Off +; Development Value: On +; Production Value: On +; http://php.net/log-errors +log_errors = On + +; Set maximum length of log_errors. In error_log information about the source is +; added. The default is 1024 and 0 allows to not apply any maximum length at all. +; http://php.net/log-errors-max-len +log_errors_max_len = 1024 + +; Do not log repeated messages. Repeated errors must occur in same file on same +; line unless ignore_repeated_source is set true. +; http://php.net/ignore-repeated-errors +ignore_repeated_errors = Off + +; Ignore source of message when ignoring repeated messages. When this setting +; is On you will not log errors with repeated messages from different files or +; source lines. +; http://php.net/ignore-repeated-source +ignore_repeated_source = Off + +; If this parameter is set to Off, then memory leaks will not be shown (on +; stdout or in the log). This has only effect in a debug compile, and if +; error reporting includes E_WARNING in the allowed list +; http://php.net/report-memleaks +report_memleaks = On + +; This setting is on by default. +;report_zend_debug = 0 + +; Store the last error/warning message in $php_errormsg (boolean). Setting this value +; to On can assist in debugging and is appropriate for development servers. It should +; however be disabled on production servers. +; Default Value: Off +; Development Value: On +; Production Value: Off +; http://php.net/track-errors +track_errors = Off + +; Turn off normal error reporting and emit XML-RPC error XML +; http://php.net/xmlrpc-errors +;xmlrpc_errors = 0 + +; An XML-RPC faultCode +;xmlrpc_error_number = 0 + +; When PHP displays or logs an error, it has the capability of inserting html +; links to documentation related to that error. This directive controls whether +; those HTML links appear in error messages or not. For performance and security +; reasons, it's recommended you disable this on production servers. +; Note: This directive is hardcoded to Off for the CLI SAPI +; Default Value: On +; Development Value: On +; Production value: Off +; http://php.net/html-errors +html_errors = Off + +; If html_errors is set On PHP produces clickable error messages that direct +; to a page describing the error or function causing the error in detail. +; You can download a copy of the PHP manual from http://php.net/docs +; and change docref_root to the base URL of your local copy including the +; leading '/'. You must also specify the file extension being used including +; the dot. PHP's default behavior is to leave these settings empty. +; Note: Never use this feature for production boxes. +; http://php.net/docref-root +; Examples +;docref_root = "/phpmanual/" + +; http://php.net/docref-ext +;docref_ext = .html + +; String to output before an error message. PHP's default behavior is to leave +; this setting blank. +; http://php.net/error-prepend-string +; Example: +;error_prepend_string = "" + +; String to output after an error message. PHP's default behavior is to leave +; this setting blank. +; http://php.net/error-append-string +; Example: +;error_append_string = "" + +; Log errors to specified file. PHP's default behavior is to leave this value +; empty. +; http://php.net/error-log +; Example: +;error_log = php_errors.log +; Log errors to syslog (Event Log on NT, not valid in Windows 95). +;error_log = syslog + +;;;;;;;;;;;;;;;;; +; Data Handling ; +;;;;;;;;;;;;;;;;; + +; The separator used in PHP generated URLs to separate arguments. +; PHP's default setting is "&". +; http://php.net/arg-separator.output +; Example: +;arg_separator.output = "&" + +; List of separator(s) used by PHP to parse input URLs into variables. +; PHP's default setting is "&". +; NOTE: Every character in this directive is considered as separator! +; http://php.net/arg-separator.input +; Example: +;arg_separator.input = ";&" + +; This directive determines which super global arrays are registered when PHP +; starts up. If the register_globals directive is enabled, it also determines +; what order variables are populated into the global space. G,P,C,E & S are +; abbreviations for the following respective super globals: GET, POST, COOKIE, +; ENV and SERVER. There is a performance penalty paid for the registration of +; these arrays and because ENV is not as commonly used as the others, ENV is +; is not recommended on productions servers. You can still get access to +; the environment variables through getenv() should you need to. +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS"; +; http://php.net/variables-order +variables_order = "GPCS" + +; This directive determines which super global data (G,P,C,E & S) should +; be registered into the super global array REQUEST. If so, it also determines +; the order in which that data is registered. The values for this directive are +; specified in the same manner as the variables_order directive, EXCEPT one. +; Leaving this value empty will cause PHP to use the value set in the +; variables_order directive. It does not mean it will leave the super globals +; array REQUEST empty. +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" +; http://php.net/request-order +request_order = "GP" + +; Whether or not to register the EGPCS variables as global variables. You may +; want to turn this off if you don't want to clutter your scripts' global scope +; with user data. +; You should do your best to write your scripts so that they do not require +; register_globals to be on; Using form variables as globals can easily lead +; to possible security problems, if the code is not very well thought of. +; http://php.net/register-globals +register_globals = Off + +; Determines whether the deprecated long $HTTP_*_VARS type predefined variables +; are registered by PHP or not. As they are deprecated, we obviously don't +; recommend you use them. They are on by default for compatibility reasons but +; they are not recommended on production servers. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/register-long-arrays +register_long_arrays = Off + +; This directive determines whether PHP registers $argv & $argc each time it +; runs. $argv contains an array of all the arguments passed to PHP when a script +; is invoked. $argc contains an integer representing the number of arguments +; that were passed when the script was invoked. These arrays are extremely +; useful when running scripts from the command line. When this directive is +; enabled, registering these variables consumes CPU cycles and memory each time +; a script is executed. For performance reasons, this feature should be disabled +; on production servers. +; Note: This directive is hardcoded to On for the CLI SAPI +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/register-argc-argv +register_argc_argv = Off + +; When enabled, the SERVER and ENV variables are created when they're first +; used (Just In Time) instead of when the script starts. If these variables +; are not used within a script, having this directive on will result in a +; performance gain. The PHP directives register_globals, register_long_arrays, +; and register_argc_argv must be disabled for this directive to have any affect. +; http://php.net/auto-globals-jit +auto_globals_jit = On + +; Maximum size of POST data that PHP will accept. +; http://php.net/post-max-size +post_max_size = 8M + +; Magic quotes are a preprocessing feature of PHP where PHP will attempt to +; escape any character sequences in GET, POST, COOKIE and ENV data which might +; otherwise corrupt data being placed in resources such as databases before +; making that data available to you. Because of character encoding issues and +; non-standard SQL implementations across many databases, it's not currently +; possible for this feature to be 100% accurate. PHP's default behavior is to +; enable the feature. We strongly recommend you use the escaping mechanisms +; designed specifically for the database your using instead of relying on this +; feature. Also note, this feature has been deprecated as of PHP 5.3.0 and is +; scheduled for removal in PHP 6. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/magic-quotes-gpc +magic_quotes_gpc = Off + +; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc. +; http://php.net/magic-quotes-runtime +magic_quotes_runtime = Off + +; Use Sybase-style magic quotes (escape ' with '' instead of \'). +; http://php.net/magic-quotes-sybase +magic_quotes_sybase = Off + +; Automatically add files before PHP document. +; http://php.net/auto-prepend-file +auto_prepend_file = + +; Automatically add files after PHP document. +; http://php.net/auto-append-file +auto_append_file = + +; By default, PHP will output a character encoding using +; the Content-type: header. To disable sending of the charset, simply +; set it to be empty. +; +; PHP's built-in default is text/html +; http://php.net/default-mimetype +default_mimetype = "text/html" + +; PHP's default character set is set to empty. +; http://php.net/default-charset +;default_charset = "iso-8859-1" + +; Always populate the $HTTP_RAW_POST_DATA variable. PHP's default behavior is +; to disable this feature. +; http://php.net/always-populate-raw-post-data +;always_populate_raw_post_data = On + +;;;;;;;;;;;;;;;;;;;;;;;;; +; Paths and Directories ; +;;;;;;;;;;;;;;;;;;;;;;;;; + +; UNIX: "/path1:/path2" +;include_path = ".:/usr/share/php" +; +; Windows: "\path1;\path2" +;include_path = ".;c:\php\includes" +; +; PHP's default setting for include_path is ".;/path/to/php/pear" +; http://php.net/include-path + +; The root of the PHP pages, used only if nonempty. +; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root +; if you are running php as a CGI under any web server (other than IIS) +; see documentation for security issues. The alternate is to use the +; cgi.force_redirect configuration below +; http://php.net/doc-root +doc_root = + +; The directory under which PHP opens the script using /~username used only +; if nonempty. +; http://php.net/user-dir +user_dir = + +; Directory in which the loadable extensions (modules) reside. +; http://php.net/extension-dir +; extension_dir = "./" +; On windows: +; extension_dir = "ext" + +; Whether or not to enable the dl() function. The dl() function does NOT work +; properly in multithreaded servers, such as IIS or Zeus, and is automatically +; disabled on them. +; http://php.net/enable-dl +enable_dl = Off + +; cgi.force_redirect is necessary to provide security running PHP as a CGI under +; most web servers. Left undefined, PHP turns this on by default. You can +; turn it off here AT YOUR OWN RISK +; **You CAN safely turn this off for IIS, in fact, you MUST.** +; http://php.net/cgi.force-redirect +;cgi.force_redirect = 1 + +; if cgi.nph is enabled it will force cgi to always sent Status: 200 with +; every request. PHP's default behavior is to disable this feature. +;cgi.nph = 1 + +; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape +; (iPlanet) web servers, you MAY need to set an environment variable name that PHP +; will look for to know it is OK to continue execution. Setting this variable MAY +; cause security issues, KNOW WHAT YOU ARE DOING FIRST. +; http://php.net/cgi.redirect-status-env +;cgi.redirect_status_env = ; + +; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's +; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok +; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting +; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting +; of zero causes PHP to behave as before. Default is 1. You should fix your scripts +; to use SCRIPT_FILENAME rather than PATH_TRANSLATED. +; http://php.net/cgi.fix-pathinfo +;cgi.fix_pathinfo=1 + +; FastCGI under IIS (on WINNT based OS) supports the ability to impersonate +; security tokens of the calling client. This allows IIS to define the +; security context that the request runs under. mod_fastcgi under Apache +; does not currently support this feature (03/17/2002) +; Set to 1 if running under IIS. Default is zero. +; http://php.net/fastcgi.impersonate +;fastcgi.impersonate = 1; + +; Disable logging through FastCGI connection. PHP's default behavior is to enable +; this feature. +;fastcgi.logging = 0 + +; cgi.rfc2616_headers configuration option tells PHP what type of headers to +; use when sending HTTP response code. If it's set 0 PHP sends Status: header that +; is supported by Apache. When this option is set to 1 PHP will send +; RFC2616 compliant header. +; Default is zero. +; http://php.net/cgi.rfc2616-headers +;cgi.rfc2616_headers = 0 + +;;;;;;;;;;;;;;;; +; File Uploads ; +;;;;;;;;;;;;;;;; + +; Whether to allow HTTP file uploads. +; http://php.net/file-uploads +file_uploads = On + +; Temporary directory for HTTP uploaded files (will use system default if not +; specified). +; http://php.net/upload-tmp-dir +;upload_tmp_dir = + +; Maximum allowed size for uploaded files. +; http://php.net/upload-max-filesize +upload_max_filesize = 2M + +; Maximum number of files that can be uploaded via a single request +max_file_uploads = 20 + +;;;;;;;;;;;;;;;;;; +; Fopen wrappers ; +;;;;;;;;;;;;;;;;;; + +; Whether to allow the treatment of URLs (like http:// or ftp://) as files. +; http://php.net/allow-url-fopen +allow_url_fopen = On + +; Whether to allow include/require to open URLs (like http:// or ftp://) as files. +; http://php.net/allow-url-include +allow_url_include = Off + +; Define the anonymous ftp password (your email address). PHP's default setting +; for this is empty. +; http://php.net/from +;from="john@doe.com" + +; Define the User-Agent string. PHP's default setting for this is empty. +; http://php.net/user-agent +;user_agent="PHP" + +; Default timeout for socket based streams (seconds) +; http://php.net/default-socket-timeout +default_socket_timeout = 60 + +; If your scripts have to deal with files from Macintosh systems, +; or you are running on a Mac and need to deal with files from +; unix or win32 systems, setting this flag will cause PHP to +; automatically detect the EOL character in those files so that +; fgets() and file() will work regardless of the source of the file. +; http://php.net/auto-detect-line-endings +;auto_detect_line_endings = Off + +;;;;;;;;;;;;;;;;;;;;;; +; Dynamic Extensions ; +;;;;;;;;;;;;;;;;;;;;;; + +; If you wish to have an extension loaded automatically, use the following +; syntax: +; +; extension=modulename.extension +; +; For example, on Windows: +; +; extension=msql.dll +; +; ... or under UNIX: +; +; extension=msql.so +; +; ... or with a path: +; +; extension=/path/to/extension/msql.so +; +; If you only provide the name of the extension, PHP will look for it in its +; default extension directory. +; + +;;;;;;;;;;;;;;;;;;; +; Module Settings ; +;;;;;;;;;;;;;;;;;;; + +[Date] +; Defines the default timezone used by the date functions +; http://php.net/date.timezone +;date.timezone = + +; http://php.net/date.default-latitude +;date.default_latitude = 31.7667 + +; http://php.net/date.default-longitude +;date.default_longitude = 35.2333 + +; http://php.net/date.sunrise-zenith +;date.sunrise_zenith = 90.583333 + +; http://php.net/date.sunset-zenith +;date.sunset_zenith = 90.583333 + +[filter] +; http://php.net/filter.default +;filter.default = unsafe_raw + +; http://php.net/filter.default-flags +;filter.default_flags = + +[iconv] +;iconv.input_encoding = ISO-8859-1 +;iconv.internal_encoding = ISO-8859-1 +;iconv.output_encoding = ISO-8859-1 + +[intl] +;intl.default_locale = +; This directive allows you to produce PHP errors when some error +; happens within intl functions. The value is the level of the error produced. +; Default is 0, which does not produce any errors. +;intl.error_level = E_WARNING + +[sqlite] +; http://php.net/sqlite.assoc-case +;sqlite.assoc_case = 0 + +[sqlite3] +;sqlite3.extension_dir = + +[Pcre] +;PCRE library backtracking limit. +; http://php.net/pcre.backtrack-limit +;pcre.backtrack_limit=100000 + +;PCRE library recursion limit. +;Please note that if you set this value to a high number you may consume all +;the available process stack and eventually crash PHP (due to reaching the +;stack size limit imposed by the Operating System). +; http://php.net/pcre.recursion-limit +;pcre.recursion_limit=100000 + +[Pdo] +; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off" +; http://php.net/pdo-odbc.connection-pooling +;pdo_odbc.connection_pooling=strict + +;pdo_odbc.db2_instance_name + +[Pdo_mysql] +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/pdo_mysql.cache_size +pdo_mysql.cache_size = 2000 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/pdo_mysql.default-socket +pdo_mysql.default_socket= + +[Phar] +; http://php.net/phar.readonly +;phar.readonly = On + +; http://php.net/phar.require-hash +;phar.require_hash = On + +;phar.cache_list = + +[Syslog] +; Whether or not to define the various syslog variables (e.g. $LOG_PID, +; $LOG_CRON, etc.). Turning it off is a good idea performance-wise. In +; runtime, you can define these variables by calling define_syslog_variables(). +; http://php.net/define-syslog-variables +define_syslog_variables = Off + +[mail function] +; For Win32 only. +; http://php.net/smtp +SMTP = localhost +; http://php.net/smtp-port +smtp_port = 25 + +; For Win32 only. +; http://php.net/sendmail-from +;sendmail_from = me@example.com + +; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). +; http://php.net/sendmail-path +;sendmail_path = + +; Force the addition of the specified parameters to be passed as extra parameters +; to the sendmail binary. These parameters will always replace the value of +; the 5th parameter to mail(), even in safe mode. +;mail.force_extra_parameters = + +; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename +mail.add_x_header = On + +; Log all mail() calls including the full path of the script, line #, to address and headers +;mail.log = + +[SQL] +; http://php.net/sql.safe-mode +sql.safe_mode = Off + +[ODBC] +; http://php.net/odbc.default-db +;odbc.default_db = Not yet implemented + +; http://php.net/odbc.default-user +;odbc.default_user = Not yet implemented + +; http://php.net/odbc.default-pw +;odbc.default_pw = Not yet implemented + +; Controls the ODBC cursor model. +; Default: SQL_CURSOR_STATIC (default). +;odbc.default_cursortype + +; Allow or prevent persistent links. +; http://php.net/odbc.allow-persistent +odbc.allow_persistent = On + +; Check that a connection is still valid before reuse. +; http://php.net/odbc.check-persistent +odbc.check_persistent = On + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/odbc.max-persistent +odbc.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/odbc.max-links +odbc.max_links = -1 + +; Handling of LONG fields. Returns number of bytes to variables. 0 means +; passthru. +; http://php.net/odbc.defaultlrl +odbc.defaultlrl = 4096 + +; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char. +; See the documentation on odbc_binmode and odbc_longreadlen for an explanation +; of odbc.defaultlrl and odbc.defaultbinmode +; http://php.net/odbc.defaultbinmode +odbc.defaultbinmode = 1 + +;birdstep.max_links = -1 + +[Interbase] +; Allow or prevent persistent links. +ibase.allow_persistent = 1 + +; Maximum number of persistent links. -1 means no limit. +ibase.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +ibase.max_links = -1 + +; Default database name for ibase_connect(). +;ibase.default_db = + +; Default username for ibase_connect(). +;ibase.default_user = + +; Default password for ibase_connect(). +;ibase.default_password = + +; Default charset for ibase_connect(). +;ibase.default_charset = + +; Default timestamp format. +ibase.timestampformat = "%Y-%m-%d %H:%M:%S" + +; Default date format. +ibase.dateformat = "%Y-%m-%d" + +; Default time format. +ibase.timeformat = "%H:%M:%S" + +[MySQL] +; Allow accessing, from PHP's perspective, local files with LOAD DATA statements +; http://php.net/mysql.allow_local_infile +mysql.allow_local_infile = On + +; Allow or prevent persistent links. +; http://php.net/mysql.allow-persistent +mysql.allow_persistent = On + +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/mysql.cache_size +mysql.cache_size = 2000 + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/mysql.max-persistent +mysql.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/mysql.max-links +mysql.max_links = -1 + +; Default port number for mysql_connect(). If unset, mysql_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +; http://php.net/mysql.default-port +mysql.default_port = + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/mysql.default-socket +mysql.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysql.default-host +mysql.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysql.default-user +mysql.default_user = + +; Default password for mysql_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysql.default_password") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +; http://php.net/mysql.default-password +mysql.default_password = + +; Maximum time (in seconds) for connect timeout. -1 means no limit +; http://php.net/mysql.connect-timeout +mysql.connect_timeout = 60 + +; Trace mode. When trace_mode is active (=On), warnings for table/index scans and +; SQL-Errors will be displayed. +; http://php.net/mysql.trace-mode +mysql.trace_mode = Off + +[MySQLi] + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/mysqli.max-persistent +mysqli.max_persistent = -1 + +; Allow accessing, from PHP's perspective, local files with LOAD DATA statements +; http://php.net/mysqli.allow_local_infile +;mysqli.allow_local_infile = On + +; Allow or prevent persistent links. +; http://php.net/mysqli.allow-persistent +mysqli.allow_persistent = On + +; Maximum number of links. -1 means no limit. +; http://php.net/mysqli.max-links +mysqli.max_links = -1 + +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/mysqli.cache_size +mysqli.cache_size = 2000 + +; Default port number for mysqli_connect(). If unset, mysqli_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +; http://php.net/mysqli.default-port +mysqli.default_port = 3306 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/mysqli.default-socket +mysqli.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysqli.default-host +mysqli.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysqli.default-user +mysqli.default_user = + +; Default password for mysqli_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +; http://php.net/mysqli.default-pw +mysqli.default_pw = + +; Allow or prevent reconnect +mysqli.reconnect = Off + +[mysqlnd] +; Enable / Disable collection of general statstics by mysqlnd which can be +; used to tune and monitor MySQL operations. +; http://php.net/mysqlnd.collect_statistics +mysqlnd.collect_statistics = On + +; Enable / Disable collection of memory usage statstics by mysqlnd which can be +; used to tune and monitor MySQL operations. +; http://php.net/mysqlnd.collect_memory_statistics +mysqlnd.collect_memory_statistics = Off + +; Size of a pre-allocated buffer used when sending commands to MySQL in bytes. +; http://php.net/mysqlnd.net_cmd_buffer_size +;mysqlnd.net_cmd_buffer_size = 2048 + +; Size of a pre-allocated buffer used for reading data sent by the server in +; bytes. +; http://php.net/mysqlnd.net_read_buffer_size +;mysqlnd.net_read_buffer_size = 32768 + +[OCI8] + +; Connection: Enables privileged connections using external +; credentials (OCI_SYSOPER, OCI_SYSDBA) +; http://php.net/oci8.privileged-connect +;oci8.privileged_connect = Off + +; Connection: The maximum number of persistent OCI8 connections per +; process. Using -1 means no limit. +; http://php.net/oci8.max-persistent +;oci8.max_persistent = -1 + +; Connection: The maximum number of seconds a process is allowed to +; maintain an idle persistent connection. Using -1 means idle +; persistent connections will be maintained forever. +; http://php.net/oci8.persistent-timeout +;oci8.persistent_timeout = -1 + +; Connection: The number of seconds that must pass before issuing a +; ping during oci_pconnect() to check the connection validity. When +; set to 0, each oci_pconnect() will cause a ping. Using -1 disables +; pings completely. +; http://php.net/oci8.ping-interval +;oci8.ping_interval = 60 + +; Connection: Set this to a user chosen connection class to be used +; for all pooled server requests with Oracle 11g Database Resident +; Connection Pooling (DRCP). To use DRCP, this value should be set to +; the same string for all web servers running the same application, +; the database pool must be configured, and the connection string must +; specify to use a pooled server. +;oci8.connection_class = + +; High Availability: Using On lets PHP receive Fast Application +; Notification (FAN) events generated when a database node fails. The +; database must also be configured to post FAN events. +;oci8.events = Off + +; Tuning: This option enables statement caching, and specifies how +; many statements to cache. Using 0 disables statement caching. +; http://php.net/oci8.statement-cache-size +;oci8.statement_cache_size = 20 + +; Tuning: Enables statement prefetching and sets the default number of +; rows that will be fetched automatically after statement execution. +; http://php.net/oci8.default-prefetch +;oci8.default_prefetch = 100 + +; Compatibility. Using On means oci_close() will not close +; oci_connect() and oci_new_connect() connections. +; http://php.net/oci8.old-oci-close-semantics +;oci8.old_oci_close_semantics = Off + +[PostgresSQL] +; Allow or prevent persistent links. +; http://php.net/pgsql.allow-persistent +pgsql.allow_persistent = On + +; Detect broken persistent links always with pg_pconnect(). +; Auto reset feature requires a little overheads. +; http://php.net/pgsql.auto-reset-persistent +pgsql.auto_reset_persistent = Off + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/pgsql.max-persistent +pgsql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +; http://php.net/pgsql.max-links +pgsql.max_links = -1 + +; Ignore PostgreSQL backends Notice message or not. +; Notice message logging require a little overheads. +; http://php.net/pgsql.ignore-notice +pgsql.ignore_notice = 0 + +; Log PostgreSQL backends Noitce message or not. +; Unless pgsql.ignore_notice=0, module cannot log notice message. +; http://php.net/pgsql.log-notice +pgsql.log_notice = 0 + +[Sybase-CT] +; Allow or prevent persistent links. +; http://php.net/sybct.allow-persistent +sybct.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/sybct.max-persistent +sybct.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/sybct.max-links +sybct.max_links = -1 + +; Minimum server message severity to display. +; http://php.net/sybct.min-server-severity +sybct.min_server_severity = 10 + +; Minimum client message severity to display. +; http://php.net/sybct.min-client-severity +sybct.min_client_severity = 10 + +; Set per-context timeout +; http://php.net/sybct.timeout +;sybct.timeout= + +;sybct.packet_size + +; The maximum time in seconds to wait for a connection attempt to succeed before returning failure. +; Default: one minute +;sybct.login_timeout= + +; The name of the host you claim to be connecting from, for display by sp_who. +; Default: none +;sybct.hostname= + +; Allows you to define how often deadlocks are to be retried. -1 means "forever". +; Default: 0 +;sybct.deadlock_retry_count= + +[bcmath] +; Number of decimal digits for all bcmath functions. +; http://php.net/bcmath.scale +bcmath.scale = 0 + +[browscap] +; http://php.net/browscap +;browscap = extra/browscap.ini + +[Session] +; Handler used to store/retrieve data. +; http://php.net/session.save-handler +session.save_handler = files + +; Argument passed to save_handler. In the case of files, this is the path +; where data files are stored. Note: Windows users have to change this +; variable in order to use PHP's session functions. +; +; The path can be defined as: +; +; session.save_path = "N;/path" +; +; where N is an integer. Instead of storing all the session files in +; /path, what this will do is use subdirectories N-levels deep, and +; store the session data in those directories. This is useful if you +; or your OS have problems with lots of files in one directory, and is +; a more efficient layout for servers that handle lots of sessions. +; +; NOTE 1: PHP will not create this directory structure automatically. +; You can use the script in the ext/session dir for that purpose. +; NOTE 2: See the section on garbage collection below if you choose to +; use subdirectories for session storage +; +; The file storage module creates files using mode 600 by default. +; You can change that by using +; +; session.save_path = "N;MODE;/path" +; +; where MODE is the octal representation of the mode. Note that this +; does not overwrite the process's umask. +; http://php.net/session.save-path +;session.save_path = "/tmp" + +; Whether to use cookies. +; http://php.net/session.use-cookies +session.use_cookies = 1 + +; http://php.net/session.cookie-secure +;session.cookie_secure = + +; This option forces PHP to fetch and use a cookie for storing and maintaining +; the session id. We encourage this operation as it's very helpful in combatting +; session hijacking when not specifying and managing your own session id. It is +; not the end all be all of session hijacking defense, but it's a good start. +; http://php.net/session.use-only-cookies +session.use_only_cookies = 1 + +; Name of the session (used as cookie name). +; http://php.net/session.name +session.name = PHPSESSID + +; Initialize session on request startup. +; http://php.net/session.auto-start +session.auto_start = 0 + +; Lifetime in seconds of cookie or, if 0, until browser is restarted. +; http://php.net/session.cookie-lifetime +session.cookie_lifetime = 0 + +; The path for which the cookie is valid. +; http://php.net/session.cookie-path +session.cookie_path = / + +; The domain for which the cookie is valid. +; http://php.net/session.cookie-domain +session.cookie_domain = + +; Whether or not to add the httpOnly flag to the cookie, which makes it inaccessible to browser scripting languages such as JavaScript. +; http://php.net/session.cookie-httponly +session.cookie_httponly = + +; Handler used to serialize data. php is the standard serializer of PHP. +; http://php.net/session.serialize-handler +session.serialize_handler = php + +; Defines the probability that the 'garbage collection' process is started +; on every session initialization. The probability is calculated by using +; gc_probability/gc_divisor. Where session.gc_probability is the numerator +; and gc_divisor is the denominator in the equation. Setting this value to 1 +; when the session.gc_divisor value is 100 will give you approximately a 1% chance +; the gc will run on any give request. +; Default Value: 1 +; Development Value: 1 +; Production Value: 1 +; http://php.net/session.gc-probability +session.gc_probability = 1 + +; Defines the probability that the 'garbage collection' process is started on every +; session initialization. The probability is calculated by using the following equation: +; gc_probability/gc_divisor. Where session.gc_probability is the numerator and +; session.gc_divisor is the denominator in the equation. Setting this value to 1 +; when the session.gc_divisor value is 100 will give you approximately a 1% chance +; the gc will run on any give request. Increasing this value to 1000 will give you +; a 0.1% chance the gc will run on any give request. For high volume production servers, +; this is a more efficient approach. +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 +; http://php.net/session.gc-divisor +session.gc_divisor = 1000 + +; After this number of seconds, stored data will be seen as 'garbage' and +; cleaned up by the garbage collection process. +; http://php.net/session.gc-maxlifetime +session.gc_maxlifetime = 1440 + +; NOTE: If you are using the subdirectory option for storing session files +; (see session.save_path above), then garbage collection does *not* +; happen automatically. You will need to do your own garbage +; collection through a shell script, cron entry, or some other method. +; For example, the following script would is the equivalent of +; setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): +; cd /path/to/sessions; find -cmin +24 | xargs rm + +; PHP 4.2 and less have an undocumented feature/bug that allows you to +; to initialize a session variable in the global scope, even when register_globals +; is disabled. PHP 4.3 and later will warn you, if this feature is used. +; You can disable the feature and the warning separately. At this time, +; the warning is only displayed, if bug_compat_42 is enabled. This feature +; introduces some serious security problems if not handled correctly. It's +; recommended that you do not use this feature on production servers. But you +; should enable this on development servers and enable the warning as well. If you +; do not enable the feature on development servers, you won't be warned when it's +; used and debugging errors caused by this can be difficult to track down. +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/session.bug-compat-42 +session.bug_compat_42 = Off + +; This setting controls whether or not you are warned by PHP when initializing a +; session value into the global space. session.bug_compat_42 must be enabled before +; these warnings can be issued by PHP. See the directive above for more information. +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/session.bug-compat-warn +session.bug_compat_warn = Off + +; Check HTTP Referer to invalidate externally stored URLs containing ids. +; HTTP_REFERER has to contain this substring for the session to be +; considered as valid. +; http://php.net/session.referer-check +session.referer_check = + +; How many bytes to read from the file. +; http://php.net/session.entropy-length +session.entropy_length = 0 + +; Specified here to create the session id. +; http://php.net/session.entropy-file +;session.entropy_file = /dev/urandom +session.entropy_file = + +; http://php.net/session.entropy-length +;session.entropy_length = 16 + +; Set to {nocache,private,public,} to determine HTTP caching aspects +; or leave this empty to avoid sending anti-caching headers. +; http://php.net/session.cache-limiter +session.cache_limiter = nocache + +; Document expires after n minutes. +; http://php.net/session.cache-expire +session.cache_expire = 180 + +; trans sid support is disabled by default. +; Use of trans sid may risk your users security. +; Use this option with caution. +; - User may send URL contains active session ID +; to other person via. email/irc/etc. +; - URL that contains active session ID may be stored +; in publically accessible computer. +; - User may access your site with the same session ID +; always using URL stored in browser's history or bookmarks. +; http://php.net/session.use-trans-sid +session.use_trans_sid = 0 + +; Select a hash function for use in generating session ids. +; Possible Values +; 0 (MD5 128 bits) +; 1 (SHA-1 160 bits) +; This option may also be set to the name of any hash function supported by +; the hash extension. A list of available hashes is returned by the hash_alogs() +; function. +; http://php.net/session.hash-function +session.hash_function = 0 + +; Define how many bits are stored in each character when converting +; the binary hash data to something readable. +; Possible values: +; 4 (4 bits: 0-9, a-f) +; 5 (5 bits: 0-9, a-v) +; 6 (6 bits: 0-9, a-z, A-Z, "-", ",") +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 +; http://php.net/session.hash-bits-per-character +session.hash_bits_per_character = 5 + +; The URL rewriter will look for URLs in a defined set of HTML tags. +; form/fieldset are special; if you include them here, the rewriter will +; add a hidden field with the info which is otherwise appended +; to URLs. If you want XHTML conformity, remove the form entry. +; Note that all valid entries require a "=", even if no value follows. +; Default Value: "a=href,area=href,frame=src,form=,fieldset=" +; Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; http://php.net/url-rewriter.tags +url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry" + +[MSSQL] +; Allow or prevent persistent links. +mssql.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +mssql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +mssql.max_links = -1 + +; Minimum error severity to display. +mssql.min_error_severity = 10 + +; Minimum message severity to display. +mssql.min_message_severity = 10 + +; Compatibility mode with old versions of PHP 3.0. +mssql.compatability_mode = Off + +; Connect timeout +;mssql.connect_timeout = 5 + +; Query timeout +;mssql.timeout = 60 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textlimit = 4096 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textsize = 4096 + +; Limits the number of records in each batch. 0 = all records in one batch. +;mssql.batchsize = 0 + +; Specify how datetime and datetim4 columns are returned +; On => Returns data converted to SQL server settings +; Off => Returns values as YYYY-MM-DD hh:mm:ss +;mssql.datetimeconvert = On + +; Use NT authentication when connecting to the server +mssql.secure_connection = Off + +; Specify max number of processes. -1 = library default +; msdlib defaults to 25 +; FreeTDS defaults to 4096 +;mssql.max_procs = -1 + +; Specify client character set. +; If empty or not set the client charset from freetds.comf is used +; This is only used when compiled with FreeTDS +;mssql.charset = "ISO-8859-1" + +[Assertion] +; Assert(expr); active by default. +; http://php.net/assert.active +;assert.active = On + +; Issue a PHP warning for each failed assertion. +; http://php.net/assert.warning +;assert.warning = On + +; Don't bail out by default. +; http://php.net/assert.bail +;assert.bail = Off + +; User-function to be called if an assertion fails. +; http://php.net/assert.callback +;assert.callback = 0 + +; Eval the expression with current error_reporting(). Set to true if you want +; error_reporting(0) around the eval(). +; http://php.net/assert.quiet-eval +;assert.quiet_eval = 0 + +[COM] +; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs +; http://php.net/com.typelib-file +;com.typelib_file = + +; allow Distributed-COM calls +; http://php.net/com.allow-dcom +;com.allow_dcom = true + +; autoregister constants of a components typlib on com_load() +; http://php.net/com.autoregister-typelib +;com.autoregister_typelib = true + +; register constants casesensitive +; http://php.net/com.autoregister-casesensitive +;com.autoregister_casesensitive = false + +; show warnings on duplicate constant registrations +; http://php.net/com.autoregister-verbose +;com.autoregister_verbose = true + +; The default character set code-page to use when passing strings to and from COM objects. +; Default: system ANSI code page +;com.code_page= + +[mbstring] +; language for internal character representation. +; http://php.net/mbstring.language +;mbstring.language = Japanese + +; internal/script encoding. +; Some encoding cannot work as internal encoding. +; (e.g. SJIS, BIG5, ISO-2022-*) +; http://php.net/mbstring.internal-encoding +;mbstring.internal_encoding = EUC-JP + +; http input encoding. +; http://php.net/mbstring.http-input +;mbstring.http_input = auto + +; http output encoding. mb_output_handler must be +; registered as output buffer to function +; http://php.net/mbstring.http-output +;mbstring.http_output = SJIS + +; enable automatic encoding translation according to +; mbstring.internal_encoding setting. Input chars are +; converted to internal encoding by setting this to On. +; Note: Do _not_ use automatic encoding translation for +; portable libs/applications. +; http://php.net/mbstring.encoding-translation +;mbstring.encoding_translation = Off + +; automatic encoding detection order. +; auto means +; http://php.net/mbstring.detect-order +;mbstring.detect_order = auto + +; substitute_character used when character cannot be converted +; one from another +; http://php.net/mbstring.substitute-character +;mbstring.substitute_character = none; + +; overload(replace) single byte functions by mbstring functions. +; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(), +; etc. Possible values are 0,1,2,4 or combination of them. +; For example, 7 for overload everything. +; 0: No overload +; 1: Overload mail() function +; 2: Overload str*() functions +; 4: Overload ereg*() functions +; http://php.net/mbstring.func-overload +;mbstring.func_overload = 0 + +; enable strict encoding detection. +;mbstring.strict_detection = Off + +; This directive specifies the regex pattern of content types for which mb_output_handler() +; is activated. +; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml) +;mbstring.http_output_conv_mimetype= + +; Allows to set script encoding. Only affects if PHP is compiled with --enable-zend-multibyte +; Default: "" +;mbstring.script_encoding= + +[gd] +; Tell the jpeg decode to ignore warnings and try to create +; a gd image. The warning will then be displayed as notices +; disabled by default +; http://php.net/gd.jpeg-ignore-warning +;gd.jpeg_ignore_warning = 0 + +[exif] +; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. +; With mbstring support this will automatically be converted into the encoding +; given by corresponding encode setting. When empty mbstring.internal_encoding +; is used. For the decode settings you can distinguish between motorola and +; intel byte order. A decode setting cannot be empty. +; http://php.net/exif.encode-unicode +;exif.encode_unicode = ISO-8859-15 + +; http://php.net/exif.decode-unicode-motorola +;exif.decode_unicode_motorola = UCS-2BE + +; http://php.net/exif.decode-unicode-intel +;exif.decode_unicode_intel = UCS-2LE + +; http://php.net/exif.encode-jis +;exif.encode_jis = + +; http://php.net/exif.decode-jis-motorola +;exif.decode_jis_motorola = JIS + +; http://php.net/exif.decode-jis-intel +;exif.decode_jis_intel = JIS + +[Tidy] +; The path to a default tidy configuration file to use when using tidy +; http://php.net/tidy.default-config +;tidy.default_config = /usr/local/lib/php/default.tcfg + +; Should tidy clean and repair output automatically? +; WARNING: Do not use this option if you are generating non-html content +; such as dynamic images +; http://php.net/tidy.clean-output +tidy.clean_output = Off + +[soap] +; Enables or disables WSDL caching feature. +; http://php.net/soap.wsdl-cache-enabled +soap.wsdl_cache_enabled=1 + +; Sets the directory name where SOAP extension will put cache files. +; http://php.net/soap.wsdl-cache-dir +soap.wsdl_cache_dir="/tmp" + +; (time to live) Sets the number of second while cached file will be used +; instead of original one. +; http://php.net/soap.wsdl-cache-ttl +soap.wsdl_cache_ttl=86400 + +; Sets the size of the cache limit. (Max. number of WSDL files to cache) +soap.wsdl_cache_limit = 5 + +[sysvshm] +; A default size of the shared memory segment +;sysvshm.init_mem = 10000 + +[ldap] +; Sets the maximum number of open links or -1 for unlimited. +ldap.max_links = -1 + +[mcrypt] +; For more information about mcrypt settings see http://php.net/mcrypt-module-open + +; Directory where to load mcrypt algorithms +; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt) +;mcrypt.algorithms_dir= + +; Directory where to load mcrypt modes +; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt) +;mcrypt.modes_dir= + +[dba] +;dba.default_handler= + +; Local Variables: +; tab-width: 4 +; End: + +<% @directives.sort_by { |key, val| key }.each do |directive, value| -%> +<%= "#{directive}=\"#{value}\"" %> +<% end -%> diff --git a/cookbooks/php/templates/default/extension.ini.erb b/cookbooks/php/templates/default/extension.ini.erb new file mode 100644 index 0000000..11a9830 --- /dev/null +++ b/cookbooks/php/templates/default/extension.ini.erb @@ -0,0 +1,7 @@ +; configuration for php <%= @name %> module +<% @extensions.each do |filepath, zend| -%> +<%= 'zend_' if zend %>extension=<%= filepath %> +<% end -%> +<% @directives.each do |k,v| -%> +<%= "#{@name}.#{k}=\"#{v}\"" %> +<% end -%> diff --git a/cookbooks/php/templates/default/php.ini.erb b/cookbooks/php/templates/default/php.ini.erb new file mode 100644 index 0000000..55efd4c --- /dev/null +++ b/cookbooks/php/templates/default/php.ini.erb @@ -0,0 +1,1900 @@ +[PHP] + +;;;;;;;;;;;;;;;;;;; +; About php.ini ; +;;;;;;;;;;;;;;;;;;; +; PHP's initialization file, generally called php.ini, is responsible for +; configuring many of the aspects of PHP's behavior. + +; PHP attempts to find and load this configuration from a number of locations. +; The following is a summary of its search order: +; 1. SAPI module specific location. +; 2. The PHPRC environment variable. (As of PHP 5.2.0) +; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0) +; 4. Current working directory (except CLI) +; 5. The web server's directory (for SAPI modules), or directory of PHP +; (otherwise in Windows) +; 6. The directory from the --with-config-file-path compile time option, or the +; Windows directory (C:\windows or C:\winnt) +; See the PHP docs for more specific information. +; http://php.net/configuration.file + +; The syntax of the file is extremely simple. Whitespace and Lines +; beginning with a semicolon are silently ignored (as you probably guessed). +; Section headers (e.g. [Foo]) are also silently ignored, even though +; they might mean something in the future. + +; Directives following the section heading [PATH=/www/mysite] only +; apply to PHP files in the /www/mysite directory. Directives +; following the section heading [HOST=www.example.com] only apply to +; PHP files served from www.example.com. Directives set in these +; special sections cannot be overridden by user-defined INI files or +; at runtime. Currently, [PATH=] and [HOST=] sections only work under +; CGI/FastCGI. +; http://php.net/ini.sections + +; Directives are specified using the following syntax: +; directive = value +; Directive names are *case sensitive* - foo=bar is different from FOO=bar. +; Directives are variables used to configure PHP or PHP extensions. +; There is no name validation. If PHP can't find an expected +; directive because it is not set or is mistyped, a default value will be used. + +; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one +; of the INI constants (On, Off, True, False, Yes, No and None) or an expression +; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a +; previously set variable or directive (e.g. ${foo}) + +; Expressions in the INI file are limited to bitwise operators and parentheses: +; | bitwise OR +; ^ bitwise XOR +; & bitwise AND +; ~ bitwise NOT +; ! boolean NOT + +; Boolean flags can be turned on using the values 1, On, True or Yes. +; They can be turned off using the values 0, Off, False or No. + +; An empty string can be denoted by simply not writing anything after the equal +; sign, or by using the None keyword: + +; foo = ; sets foo to an empty string +; foo = None ; sets foo to an empty string +; foo = "None" ; sets foo to the string 'None' + +; If you use constants in your value, and these constants belong to a +; dynamically loaded extension (either a PHP extension or a Zend extension), +; you may only use these constants *after* the line that loads the extension. + +;;;;;;;;;;;;;;;;;;; +; About this file ; +;;;;;;;;;;;;;;;;;;; +; PHP comes packaged with two INI files. One that is recommended to be used +; in production environments and one that is recommended to be used in +; development environments. + +; php.ini-production contains settings which hold security, performance and +; best practices at its core. But please be aware, these settings may break +; compatibility with older or less security conscience applications. We +; recommending using the production ini in production and testing environments. + +; php.ini-development is very similar to its production variant, except it's +; much more verbose when it comes to errors. We recommending using the +; development version only in development environments as errors shown to +; application users can inadvertently leak otherwise secure information. + +;;;;;;;;;;;;;;;;;;; +; Quick Reference ; +;;;;;;;;;;;;;;;;;;; +; The following are all the settings which are different in either the production +; or development versions of the INIs with respect to PHP's default behavior. +; Please see the actual settings later in the document for more details as to why +; we recommend these changes in PHP's behavior. + +; allow_call_time_pass_reference +; Default Value: On +; Development Value: Off +; Production Value: Off + +; display_errors +; Default Value: On +; Development Value: On +; Production Value: Off + +; display_startup_errors +; Default Value: Off +; Development Value: On +; Production Value: Off + +; error_reporting +; Default Value: E_ALL & ~E_NOTICE +; Development Value: E_ALL | E_STRICT +; Production Value: E_ALL & ~E_DEPRECATED + +; html_errors +; Default Value: On +; Development Value: On +; Production value: Off + +; log_errors +; Default Value: Off +; Development Value: On +; Production Value: On + +; magic_quotes_gpc +; Default Value: On +; Development Value: Off +; Production Value: Off + +; max_input_time +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) + +; output_buffering +; Default Value: Off +; Development Value: 4096 +; Production Value: 4096 + +; register_argc_argv +; Default Value: On +; Development Value: Off +; Production Value: Off + +; register_long_arrays +; Default Value: On +; Development Value: Off +; Production Value: Off + +; request_order +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" + +; session.bug_compat_42 +; Default Value: On +; Development Value: On +; Production Value: Off + +; session.bug_compat_warn +; Default Value: On +; Development Value: On +; Production Value: Off + +; session.gc_divisor +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 + +; session.hash_bits_per_character +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 + +; short_open_tag +; Default Value: On +; Development Value: Off +; Production Value: Off + +; track_errors +; Default Value: Off +; Development Value: On +; Production Value: Off + +; url_rewriter.tags +; Default Value: "a=href,area=href,frame=src,form=,fieldset=" +; Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry" + +; variables_order +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS" + +;;;;;;;;;;;;;;;;;;;; +; php.ini Options ; +;;;;;;;;;;;;;;;;;;;; +; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini" +;user_ini.filename = ".user.ini" + +; To disable this feature set this option to empty value +;user_ini.filename = + +; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes) +;user_ini.cache_ttl = 300 + +;;;;;;;;;;;;;;;;;;;; +; Language Options ; +;;;;;;;;;;;;;;;;;;;; + +; Enable the PHP scripting language engine under Apache. +; http://php.net/engine +engine = On + +; This directive determines whether or not PHP will recognize code between +; tags as PHP source which should be processed as such. It's been +; recommended for several years that you not use the short tag "short cut" and +; instead to use the full tag combination. With the wide spread use +; of XML and use of these tags by other languages, the server can become easily +; confused and end up parsing the wrong code in the wrong context. But because +; this short cut has been a feature for such a long time, it's currently still +; supported for backwards compatibility, but we recommend you don't use them. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/short-open-tag +short_open_tag = Off + +; Allow ASP-style <% %> tags. +; http://php.net/asp-tags +asp_tags = Off + +; The number of significant digits displayed in floating point numbers. +; http://php.net/precision +precision = 14 + +; Enforce year 2000 compliance (will cause problems with non-compliant browsers) +; http://php.net/y2k-compliance +y2k_compliance = On + +; Output buffering is a mechanism for controlling how much output data +; (excluding headers and cookies) PHP should keep internally before pushing that +; data to the client. If your application's output exceeds this setting, PHP +; will send that data in chunks of roughly the size you specify. +; Turning on this setting and managing its maximum buffer size can yield some +; interesting side-effects depending on your application and web server. +; You may be able to send headers and cookies after you've already sent output +; through print or echo. You also may see performance benefits if your server is +; emitting less packets due to buffered output versus PHP streaming the output +; as it gets it. On production servers, 4096 bytes is a good setting for performance +; reasons. +; Note: Output buffering can also be controlled via Output Buffering Control +; functions. +; Possible Values: +; On = Enabled and buffer is unlimited. (Use with caution) +; Off = Disabled +; Integer = Enables the buffer and sets its maximum size in bytes. +; Note: This directive is hardcoded to Off for the CLI SAPI +; Default Value: Off +; Development Value: 4096 +; Production Value: 4096 +; http://php.net/output-buffering +output_buffering = 4096 + +; You can redirect all of the output of your scripts to a function. For +; example, if you set output_handler to "mb_output_handler", character +; encoding will be transparently converted to the specified encoding. +; Setting any output handler automatically turns on output buffering. +; Note: People who wrote portable scripts should not depend on this ini +; directive. Instead, explicitly set the output handler using ob_start(). +; Using this ini directive may cause problems unless you know what script +; is doing. +; Note: You cannot use both "mb_output_handler" with "ob_iconv_handler" +; and you cannot use both "ob_gzhandler" and "zlib.output_compression". +; Note: output_handler must be empty if this is set 'On' !!!! +; Instead you must use zlib.output_handler. +; http://php.net/output-handler +;output_handler = + +; Transparent output compression using the zlib library +; Valid values for this option are 'off', 'on', or a specific buffer size +; to be used for compression (default is 4KB) +; Note: Resulting chunk size may vary due to nature of compression. PHP +; outputs chunks that are few hundreds bytes each as a result of +; compression. If you prefer a larger chunk size for better +; performance, enable output_buffering in addition. +; Note: You need to use zlib.output_handler instead of the standard +; output_handler, or otherwise the output will be corrupted. +; http://php.net/zlib.output-compression +zlib.output_compression = Off + +; http://php.net/zlib.output-compression-level +;zlib.output_compression_level = -1 + +; You cannot specify additional output handlers if zlib.output_compression +; is activated here. This setting does the same as output_handler but in +; a different order. +; http://php.net/zlib.output-handler +;zlib.output_handler = + +; Implicit flush tells PHP to tell the output layer to flush itself +; automatically after every output block. This is equivalent to calling the +; PHP function flush() after each and every call to print() or echo() and each +; and every HTML block. Turning this option on has serious performance +; implications and is generally recommended for debugging purposes only. +; http://php.net/implicit-flush +; Note: This directive is hardcoded to On for the CLI SAPI +implicit_flush = Off + +; The unserialize callback function will be called (with the undefined class' +; name as parameter), if the unserializer finds an undefined class +; which should be instantiated. A warning appears if the specified function is +; not defined, or if the function doesn't include/implement the missing class. +; So only set this entry, if you really want to implement such a +; callback-function. +unserialize_callback_func = + +; When floats & doubles are serialized store serialize_precision significant +; digits after the floating point. The default value ensures that when floats +; are decoded with unserialize, the data will remain the same. +serialize_precision = 100 + +; This directive allows you to enable and disable warnings which PHP will issue +; if you pass a value by reference at function call time. Passing values by +; reference at function call time is a deprecated feature which will be removed +; from PHP at some point in the near future. The acceptable method for passing a +; value by reference to a function is by declaring the reference in the functions +; definition, not at call time. This directive does not disable this feature, it +; only determines whether PHP will warn you about it or not. These warnings +; should enabled in development environments only. +; Default Value: On (Suppress warnings) +; Development Value: Off (Issue warnings) +; Production Value: Off (Issue warnings) +; http://php.net/allow-call-time-pass-reference +allow_call_time_pass_reference = Off + +; Safe Mode +; http://php.net/safe-mode +safe_mode = Off + +; By default, Safe Mode does a UID compare check when +; opening files. If you want to relax this to a GID compare, +; then turn on safe_mode_gid. +; http://php.net/safe-mode-gid +safe_mode_gid = Off + +; When safe_mode is on, UID/GID checks are bypassed when +; including files from this directory and its subdirectories. +; (directory must also be in include_path or full path must +; be used when including) +; http://php.net/safe-mode-include-dir +safe_mode_include_dir = + +; When safe_mode is on, only executables located in the safe_mode_exec_dir +; will be allowed to be executed via the exec family of functions. +; http://php.net/safe-mode-exec-dir +safe_mode_exec_dir = + +; Setting certain environment variables may be a potential security breach. +; This directive contains a comma-delimited list of prefixes. In Safe Mode, +; the user may only alter environment variables whose names begin with the +; prefixes supplied here. By default, users will only be able to set +; environment variables that begin with PHP_ (e.g. PHP_FOO=BAR). +; Note: If this directive is empty, PHP will let the user modify ANY +; environment variable! +; http://php.net/safe-mode-allowed-env-vars +safe_mode_allowed_env_vars = PHP_ + +; This directive contains a comma-delimited list of environment variables that +; the end user won't be able to change using putenv(). These variables will be +; protected even if safe_mode_allowed_env_vars is set to allow to change them. +; http://php.net/safe-mode-protected-env-vars +safe_mode_protected_env_vars = LD_LIBRARY_PATH + +; open_basedir, if set, limits all file operations to the defined directory +; and below. This directive makes most sense if used in a per-directory +; or per-virtualhost web server configuration file. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/open-basedir +;open_basedir = + +; This directive allows you to disable certain functions for security reasons. +; It receives a comma-delimited list of function names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/disable-functions +disable_functions = + +; This directive allows you to disable certain classes for security reasons. +; It receives a comma-delimited list of class names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/disable-classes +disable_classes = + +; Colors for Syntax Highlighting mode. Anything that's acceptable in +; would work. +; http://php.net/syntax-highlighting +;highlight.string = #DD0000 +;highlight.comment = #FF9900 +;highlight.keyword = #007700 +;highlight.bg = #FFFFFF +;highlight.default = #0000BB +;highlight.html = #000000 + +; If enabled, the request will be allowed to complete even if the user aborts +; the request. Consider enabling it if executing long requests, which may end up +; being interrupted by the user or a browser timing out. PHP's default behavior +; is to disable this feature. +; http://php.net/ignore-user-abort +;ignore_user_abort = On + +; Determines the size of the realpath cache to be used by PHP. This value should +; be increased on systems where PHP opens many files to reflect the quantity of +; the file operations performed. +; http://php.net/realpath-cache-size +;realpath_cache_size = 16k + +; Duration of time, in seconds for which to cache realpath information for a given +; file or directory. For systems with rarely changing files, consider increasing this +; value. +; http://php.net/realpath-cache-ttl +;realpath_cache_ttl = 120 + +;;;;;;;;;;;;;;;;; +; Miscellaneous ; +;;;;;;;;;;;;;;;;; + +; Decides whether PHP may expose the fact that it is installed on the server +; (e.g. by adding its signature to the Web server header). It is no security +; threat in any way, but it makes it possible to determine whether you use PHP +; on your server or not. +; http://php.net/expose-php +expose_php = On + +;;;;;;;;;;;;;;;;;;; +; Resource Limits ; +;;;;;;;;;;;;;;;;;;; + +; Maximum execution time of each script, in seconds +; http://php.net/max-execution-time +; Note: This directive is hardcoded to 0 for the CLI SAPI +max_execution_time = 30 + +; Maximum amount of time each script may spend parsing request data. It's a good +; idea to limit this time on productions servers in order to eliminate unexpectedly +; long running scripts. +; Note: This directive is hardcoded to -1 for the CLI SAPI +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) +; http://php.net/max-input-time +max_input_time = 60 + +; Maximum input variable nesting level +; http://php.net/max-input-nesting-level +;max_input_nesting_level = 64 + +; Maximum amount of memory a script may consume (128MB) +; http://php.net/memory-limit +memory_limit = 128M + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Error handling and logging ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; This directive informs PHP of which errors, warnings and notices you would like +; it to take action for. The recommended way of setting values for this +; directive is through the use of the error level constants and bitwise +; operators. The error level constants are below here for convenience as well as +; some common settings and their meanings. +; By default, PHP is set to take action on all errors, notices and warnings EXCEPT +; those related to E_NOTICE and E_STRICT, which together cover best practices and +; recommended coding standards in PHP. For performance reasons, this is the +; recommend error reporting setting. Your production server shouldn't be wasting +; resources complaining about best practices and coding standards. That's what +; development servers and development settings are for. +; Note: The php.ini-development file has this setting as E_ALL | E_STRICT. This +; means it pretty much reports everything which is exactly what you want during +; development and early testing. +; +; Error Level Constants: +; E_ALL - All errors and warnings (includes E_STRICT as of PHP 6.0.0) +; E_ERROR - fatal run-time errors +; E_RECOVERABLE_ERROR - almost fatal run-time errors +; E_WARNING - run-time warnings (non-fatal errors) +; E_PARSE - compile-time parse errors +; E_NOTICE - run-time notices (these are warnings which often result +; from a bug in your code, but it's possible that it was +; intentional (e.g., using an uninitialized variable and +; relying on the fact it's automatically initialized to an +; empty string) +; E_STRICT - run-time notices, enable to have PHP suggest changes +; to your code which will ensure the best interoperability +; and forward compatibility of your code +; E_CORE_ERROR - fatal errors that occur during PHP's initial startup +; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's +; initial startup +; E_COMPILE_ERROR - fatal compile-time errors +; E_COMPILE_WARNING - compile-time warnings (non-fatal errors) +; E_USER_ERROR - user-generated error message +; E_USER_WARNING - user-generated warning message +; E_USER_NOTICE - user-generated notice message +; E_DEPRECATED - warn about code that will not work in future versions +; of PHP +; E_USER_DEPRECATED - user-generated deprecation warnings +; +; Common Values: +; E_ALL & ~E_NOTICE (Show all errors, except for notices and coding standards warnings.) +; E_ALL & ~E_NOTICE | E_STRICT (Show all errors, except for notices) +; E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR (Show only errors) +; E_ALL | E_STRICT (Show all errors, warnings and notices including coding standards.) +; Default Value: E_ALL & ~E_NOTICE +; Development Value: E_ALL | E_STRICT +; Production Value: E_ALL & ~E_DEPRECATED +; http://php.net/error-reporting +error_reporting = E_ALL & ~E_DEPRECATED + +; This directive controls whether or not and where PHP will output errors, +; notices and warnings too. Error output is very useful during development, but +; it could be very dangerous in production environments. Depending on the code +; which is triggering the error, sensitive information could potentially leak +; out of your application such as database usernames and passwords or worse. +; It's recommended that errors be logged on production servers rather than +; having the errors sent to STDOUT. +; Possible Values: +; Off = Do not display any errors +; stderr = Display errors to STDERR (affects only CGI/CLI binaries!) +; On or stdout = Display errors to STDOUT +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/display-errors +display_errors = Off + +; The display of errors which occur during PHP's startup sequence are handled +; separately from display_errors. PHP's default behavior is to suppress those +; errors from clients. Turning the display of startup errors on can be useful in +; debugging configuration problems. But, it's strongly recommended that you +; leave this setting off on production servers. +; Default Value: Off +; Development Value: On +; Production Value: Off +; http://php.net/display-startup-errors +display_startup_errors = Off + +; Besides displaying errors, PHP can also log errors to locations such as a +; server-specific log, STDERR, or a location specified by the error_log +; directive found below. While errors should not be displayed on productions +; servers they should still be monitored and logging is a great way to do that. +; Default Value: Off +; Development Value: On +; Production Value: On +; http://php.net/log-errors +log_errors = On + +; Set maximum length of log_errors. In error_log information about the source is +; added. The default is 1024 and 0 allows to not apply any maximum length at all. +; http://php.net/log-errors-max-len +log_errors_max_len = 1024 + +; Do not log repeated messages. Repeated errors must occur in same file on same +; line unless ignore_repeated_source is set true. +; http://php.net/ignore-repeated-errors +ignore_repeated_errors = Off + +; Ignore source of message when ignoring repeated messages. When this setting +; is On you will not log errors with repeated messages from different files or +; source lines. +; http://php.net/ignore-repeated-source +ignore_repeated_source = Off + +; If this parameter is set to Off, then memory leaks will not be shown (on +; stdout or in the log). This has only effect in a debug compile, and if +; error reporting includes E_WARNING in the allowed list +; http://php.net/report-memleaks +report_memleaks = On + +; This setting is on by default. +;report_zend_debug = 0 + +; Store the last error/warning message in $php_errormsg (boolean). Setting this value +; to On can assist in debugging and is appropriate for development servers. It should +; however be disabled on production servers. +; Default Value: Off +; Development Value: On +; Production Value: Off +; http://php.net/track-errors +track_errors = Off + +; Turn off normal error reporting and emit XML-RPC error XML +; http://php.net/xmlrpc-errors +;xmlrpc_errors = 0 + +; An XML-RPC faultCode +;xmlrpc_error_number = 0 + +; When PHP displays or logs an error, it has the capability of inserting html +; links to documentation related to that error. This directive controls whether +; those HTML links appear in error messages or not. For performance and security +; reasons, it's recommended you disable this on production servers. +; Note: This directive is hardcoded to Off for the CLI SAPI +; Default Value: On +; Development Value: On +; Production value: Off +; http://php.net/html-errors +html_errors = Off + +; If html_errors is set On PHP produces clickable error messages that direct +; to a page describing the error or function causing the error in detail. +; You can download a copy of the PHP manual from http://php.net/docs +; and change docref_root to the base URL of your local copy including the +; leading '/'. You must also specify the file extension being used including +; the dot. PHP's default behavior is to leave these settings empty. +; Note: Never use this feature for production boxes. +; http://php.net/docref-root +; Examples +;docref_root = "/phpmanual/" + +; http://php.net/docref-ext +;docref_ext = .html + +; String to output before an error message. PHP's default behavior is to leave +; this setting blank. +; http://php.net/error-prepend-string +; Example: +;error_prepend_string = "" + +; String to output after an error message. PHP's default behavior is to leave +; this setting blank. +; http://php.net/error-append-string +; Example: +;error_append_string = "" + +; Log errors to specified file. PHP's default behavior is to leave this value +; empty. +; http://php.net/error-log +; Example: +;error_log = php_errors.log +; Log errors to syslog (Event Log on NT, not valid in Windows 95). +;error_log = syslog + +;;;;;;;;;;;;;;;;; +; Data Handling ; +;;;;;;;;;;;;;;;;; + +; The separator used in PHP generated URLs to separate arguments. +; PHP's default setting is "&". +; http://php.net/arg-separator.output +; Example: +;arg_separator.output = "&" + +; List of separator(s) used by PHP to parse input URLs into variables. +; PHP's default setting is "&". +; NOTE: Every character in this directive is considered as separator! +; http://php.net/arg-separator.input +; Example: +;arg_separator.input = ";&" + +; This directive determines which super global arrays are registered when PHP +; starts up. If the register_globals directive is enabled, it also determines +; what order variables are populated into the global space. G,P,C,E & S are +; abbreviations for the following respective super globals: GET, POST, COOKIE, +; ENV and SERVER. There is a performance penalty paid for the registration of +; these arrays and because ENV is not as commonly used as the others, ENV is +; is not recommended on productions servers. You can still get access to +; the environment variables through getenv() should you need to. +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS"; +; http://php.net/variables-order +variables_order = "GPCS" + +; This directive determines which super global data (G,P,C,E & S) should +; be registered into the super global array REQUEST. If so, it also determines +; the order in which that data is registered. The values for this directive are +; specified in the same manner as the variables_order directive, EXCEPT one. +; Leaving this value empty will cause PHP to use the value set in the +; variables_order directive. It does not mean it will leave the super globals +; array REQUEST empty. +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" +; http://php.net/request-order +request_order = "GP" + +; Whether or not to register the EGPCS variables as global variables. You may +; want to turn this off if you don't want to clutter your scripts' global scope +; with user data. +; You should do your best to write your scripts so that they do not require +; register_globals to be on; Using form variables as globals can easily lead +; to possible security problems, if the code is not very well thought of. +; http://php.net/register-globals +register_globals = Off + +; Determines whether the deprecated long $HTTP_*_VARS type predefined variables +; are registered by PHP or not. As they are deprecated, we obviously don't +; recommend you use them. They are on by default for compatibility reasons but +; they are not recommended on production servers. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/register-long-arrays +register_long_arrays = Off + +; This directive determines whether PHP registers $argv & $argc each time it +; runs. $argv contains an array of all the arguments passed to PHP when a script +; is invoked. $argc contains an integer representing the number of arguments +; that were passed when the script was invoked. These arrays are extremely +; useful when running scripts from the command line. When this directive is +; enabled, registering these variables consumes CPU cycles and memory each time +; a script is executed. For performance reasons, this feature should be disabled +; on production servers. +; Note: This directive is hardcoded to On for the CLI SAPI +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/register-argc-argv +register_argc_argv = Off + +; When enabled, the SERVER and ENV variables are created when they're first +; used (Just In Time) instead of when the script starts. If these variables +; are not used within a script, having this directive on will result in a +; performance gain. The PHP directives register_globals, register_long_arrays, +; and register_argc_argv must be disabled for this directive to have any affect. +; http://php.net/auto-globals-jit +auto_globals_jit = On + +; Maximum size of POST data that PHP will accept. +; http://php.net/post-max-size +post_max_size = 8M + +; Magic quotes are a preprocessing feature of PHP where PHP will attempt to +; escape any character sequences in GET, POST, COOKIE and ENV data which might +; otherwise corrupt data being placed in resources such as databases before +; making that data available to you. Because of character encoding issues and +; non-standard SQL implementations across many databases, it's not currently +; possible for this feature to be 100% accurate. PHP's default behavior is to +; enable the feature. We strongly recommend you use the escaping mechanisms +; designed specifically for the database your using instead of relying on this +; feature. Also note, this feature has been deprecated as of PHP 5.3.0 and is +; scheduled for removal in PHP 6. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/magic-quotes-gpc +magic_quotes_gpc = Off + +; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc. +; http://php.net/magic-quotes-runtime +magic_quotes_runtime = Off + +; Use Sybase-style magic quotes (escape ' with '' instead of \'). +; http://php.net/magic-quotes-sybase +magic_quotes_sybase = Off + +; Automatically add files before PHP document. +; http://php.net/auto-prepend-file +auto_prepend_file = + +; Automatically add files after PHP document. +; http://php.net/auto-append-file +auto_append_file = + +; By default, PHP will output a character encoding using +; the Content-type: header. To disable sending of the charset, simply +; set it to be empty. +; +; PHP's built-in default is text/html +; http://php.net/default-mimetype +default_mimetype = "text/html" + +; PHP's default character set is set to empty. +; http://php.net/default-charset +;default_charset = "iso-8859-1" + +; Always populate the $HTTP_RAW_POST_DATA variable. PHP's default behavior is +; to disable this feature. +; http://php.net/always-populate-raw-post-data +;always_populate_raw_post_data = On + +;;;;;;;;;;;;;;;;;;;;;;;;; +; Paths and Directories ; +;;;;;;;;;;;;;;;;;;;;;;;;; + +; UNIX: "/path1:/path2" +;include_path = ".:/php/includes" +; +; Windows: "\path1;\path2" +;include_path = ".;c:\php\includes" +; +; PHP's default setting for include_path is ".;/path/to/php/pear" +; http://php.net/include-path + +; The root of the PHP pages, used only if nonempty. +; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root +; if you are running php as a CGI under any web server (other than IIS) +; see documentation for security issues. The alternate is to use the +; cgi.force_redirect configuration below +; http://php.net/doc-root +doc_root = + +; The directory under which PHP opens the script using /~username used only +; if nonempty. +; http://php.net/user-dir +user_dir = + +; Directory in which the loadable extensions (modules) reside. +; http://php.net/extension-dir +; extension_dir = "./" +; On windows: +; extension_dir = "ext" + +; Whether or not to enable the dl() function. The dl() function does NOT work +; properly in multithreaded servers, such as IIS or Zeus, and is automatically +; disabled on them. +; http://php.net/enable-dl +enable_dl = Off + +; cgi.force_redirect is necessary to provide security running PHP as a CGI under +; most web servers. Left undefined, PHP turns this on by default. You can +; turn it off here AT YOUR OWN RISK +; **You CAN safely turn this off for IIS, in fact, you MUST.** +; http://php.net/cgi.force-redirect +;cgi.force_redirect = 1 + +; if cgi.nph is enabled it will force cgi to always sent Status: 200 with +; every request. PHP's default behavior is to disable this feature. +;cgi.nph = 1 + +; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape +; (iPlanet) web servers, you MAY need to set an environment variable name that PHP +; will look for to know it is OK to continue execution. Setting this variable MAY +; cause security issues, KNOW WHAT YOU ARE DOING FIRST. +; http://php.net/cgi.redirect-status-env +;cgi.redirect_status_env = ; + +; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's +; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok +; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting +; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting +; of zero causes PHP to behave as before. Default is 1. You should fix your scripts +; to use SCRIPT_FILENAME rather than PATH_TRANSLATED. +; http://php.net/cgi.fix-pathinfo +;cgi.fix_pathinfo=1 + +; FastCGI under IIS (on WINNT based OS) supports the ability to impersonate +; security tokens of the calling client. This allows IIS to define the +; security context that the request runs under. mod_fastcgi under Apache +; does not currently support this feature (03/17/2002) +; Set to 1 if running under IIS. Default is zero. +; http://php.net/fastcgi.impersonate +;fastcgi.impersonate = 1; + +; Disable logging through FastCGI connection. PHP's default behavior is to enable +; this feature. +;fastcgi.logging = 0 + +; cgi.rfc2616_headers configuration option tells PHP what type of headers to +; use when sending HTTP response code. If it's set 0 PHP sends Status: header that +; is supported by Apache. When this option is set to 1 PHP will send +; RFC2616 compliant header. +; Default is zero. +; http://php.net/cgi.rfc2616-headers +;cgi.rfc2616_headers = 0 + +;;;;;;;;;;;;;;;; +; File Uploads ; +;;;;;;;;;;;;;;;; + +; Whether to allow HTTP file uploads. +; http://php.net/file-uploads +file_uploads = On + +; Temporary directory for HTTP uploaded files (will use system default if not +; specified). +; http://php.net/upload-tmp-dir +;upload_tmp_dir = + +; Maximum allowed size for uploaded files. +; http://php.net/upload-max-filesize +upload_max_filesize = 2M + +; Maximum number of files that can be uploaded via a single request +max_file_uploads = 20 + +;;;;;;;;;;;;;;;;;; +; Fopen wrappers ; +;;;;;;;;;;;;;;;;;; + +; Whether to allow the treatment of URLs (like http:// or ftp://) as files. +; http://php.net/allow-url-fopen +allow_url_fopen = On + +; Whether to allow include/require to open URLs (like http:// or ftp://) as files. +; http://php.net/allow-url-include +allow_url_include = Off + +; Define the anonymous ftp password (your email address). PHP's default setting +; for this is empty. +; http://php.net/from +;from="john@doe.com" + +; Define the User-Agent string. PHP's default setting for this is empty. +; http://php.net/user-agent +;user_agent="PHP" + +; Default timeout for socket based streams (seconds) +; http://php.net/default-socket-timeout +default_socket_timeout = 60 + +; If your scripts have to deal with files from Macintosh systems, +; or you are running on a Mac and need to deal with files from +; unix or win32 systems, setting this flag will cause PHP to +; automatically detect the EOL character in those files so that +; fgets() and file() will work regardless of the source of the file. +; http://php.net/auto-detect-line-endings +;auto_detect_line_endings = Off + +;;;;;;;;;;;;;;;;;;;;;; +; Dynamic Extensions ; +;;;;;;;;;;;;;;;;;;;;;; + +; If you wish to have an extension loaded automatically, use the following +; syntax: +; +; extension=modulename.extension +; +; For example, on Windows: +; +; extension=msql.dll +; +; ... or under UNIX: +; +; extension=msql.so +; +; ... or with a path: +; +; extension=/path/to/extension/msql.so +; +; If you only provide the name of the extension, PHP will look for it in its +; default extension directory. +; +; Windows Extensions +; Note that ODBC support is built in, so no dll is needed for it. +; Note that many DLL files are located in the extensions/ (PHP 4) ext/ (PHP 5) +; extension folders as well as the separate PECL DLL download (PHP 5). +; Be sure to appropriately set the extension_dir directive. +; +;extension=php_bz2.dll +;extension=php_curl.dll +;extension=php_fileinfo.dll +;extension=php_gd2.dll +;extension=php_gettext.dll +;extension=php_gmp.dll +;extension=php_intl.dll +;extension=php_imap.dll +;extension=php_interbase.dll +;extension=php_ldap.dll +;extension=php_mbstring.dll +;extension=php_exif.dll ; Must be after mbstring as it depends on it +;extension=php_mysql.dll +;extension=php_mysqli.dll +;extension=php_oci8.dll ; Use with Oracle 10gR2 Instant Client +;extension=php_oci8_11g.dll ; Use with Oracle 11g Instant Client +;extension=php_openssl.dll +;extension=php_pdo_firebird.dll +;extension=php_pdo_mssql.dll +;extension=php_pdo_mysql.dll +;extension=php_pdo_oci.dll +;extension=php_pdo_odbc.dll +;extension=php_pdo_pgsql.dll +;extension=php_pdo_sqlite.dll +;extension=php_pgsql.dll +;extension=php_pspell.dll +;extension=php_shmop.dll +;extension=php_snmp.dll +;extension=php_soap.dll +;extension=php_sockets.dll +;extension=php_sqlite.dll +;extension=php_sqlite3.dll +;extension=php_sybase_ct.dll +;extension=php_tidy.dll +;extension=php_xmlrpc.dll +;extension=php_xsl.dll +;extension=php_zip.dll + +;;;;;;;;;;;;;;;;;;; +; Module Settings ; +;;;;;;;;;;;;;;;;;;; + +[Date] +; Defines the default timezone used by the date functions +; http://php.net/date.timezone +;date.timezone = + +; http://php.net/date.default-latitude +;date.default_latitude = 31.7667 + +; http://php.net/date.default-longitude +;date.default_longitude = 35.2333 + +; http://php.net/date.sunrise-zenith +;date.sunrise_zenith = 90.583333 + +; http://php.net/date.sunset-zenith +;date.sunset_zenith = 90.583333 + +[filter] +; http://php.net/filter.default +;filter.default = unsafe_raw + +; http://php.net/filter.default-flags +;filter.default_flags = + +[iconv] +;iconv.input_encoding = ISO-8859-1 +;iconv.internal_encoding = ISO-8859-1 +;iconv.output_encoding = ISO-8859-1 + +[intl] +;intl.default_locale = +; This directive allows you to produce PHP errors when some error +; happens within intl functions. The value is the level of the error produced. +; Default is 0, which does not produce any errors. +;intl.error_level = E_WARNING + +[sqlite] +; http://php.net/sqlite.assoc-case +;sqlite.assoc_case = 0 + +[sqlite3] +;sqlite3.extension_dir = + +[Pcre] +;PCRE library backtracking limit. +; http://php.net/pcre.backtrack-limit +;pcre.backtrack_limit=100000 + +;PCRE library recursion limit. +;Please note that if you set this value to a high number you may consume all +;the available process stack and eventually crash PHP (due to reaching the +;stack size limit imposed by the Operating System). +; http://php.net/pcre.recursion-limit +;pcre.recursion_limit=100000 + +[Pdo] +; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off" +; http://php.net/pdo-odbc.connection-pooling +;pdo_odbc.connection_pooling=strict + +;pdo_odbc.db2_instance_name + +[Pdo_mysql] +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/pdo_mysql.cache_size +pdo_mysql.cache_size = 2000 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/pdo_mysql.default-socket +pdo_mysql.default_socket= + +[Phar] +; http://php.net/phar.readonly +;phar.readonly = On + +; http://php.net/phar.require-hash +;phar.require_hash = On + +;phar.cache_list = + +[Syslog] +; Whether or not to define the various syslog variables (e.g. $LOG_PID, +; $LOG_CRON, etc.). Turning it off is a good idea performance-wise. In +; runtime, you can define these variables by calling define_syslog_variables(). +; http://php.net/define-syslog-variables +define_syslog_variables = Off + +[mail function] +; For Win32 only. +; http://php.net/smtp +SMTP = localhost +; http://php.net/smtp-port +smtp_port = 25 + +; For Win32 only. +; http://php.net/sendmail-from +;sendmail_from = me@example.com + +; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). +; http://php.net/sendmail-path +;sendmail_path = + +; Force the addition of the specified parameters to be passed as extra parameters +; to the sendmail binary. These parameters will always replace the value of +; the 5th parameter to mail(), even in safe mode. +;mail.force_extra_parameters = + +; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename +mail.add_x_header = On + +; The path to a log file that will log all mail() calls. Log entries include +; the full path of the script, line number, To address and headers. +;mail.log = + +[SQL] +; http://php.net/sql.safe-mode +sql.safe_mode = Off + +[ODBC] +; http://php.net/odbc.default-db +;odbc.default_db = Not yet implemented + +; http://php.net/odbc.default-user +;odbc.default_user = Not yet implemented + +; http://php.net/odbc.default-pw +;odbc.default_pw = Not yet implemented + +; Controls the ODBC cursor model. +; Default: SQL_CURSOR_STATIC (default). +;odbc.default_cursortype + +; Allow or prevent persistent links. +; http://php.net/odbc.allow-persistent +odbc.allow_persistent = On + +; Check that a connection is still valid before reuse. +; http://php.net/odbc.check-persistent +odbc.check_persistent = On + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/odbc.max-persistent +odbc.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/odbc.max-links +odbc.max_links = -1 + +; Handling of LONG fields. Returns number of bytes to variables. 0 means +; passthru. +; http://php.net/odbc.defaultlrl +odbc.defaultlrl = 4096 + +; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char. +; See the documentation on odbc_binmode and odbc_longreadlen for an explanation +; of odbc.defaultlrl and odbc.defaultbinmode +; http://php.net/odbc.defaultbinmode +odbc.defaultbinmode = 1 + +;birdstep.max_links = -1 + +[Interbase] +; Allow or prevent persistent links. +ibase.allow_persistent = 1 + +; Maximum number of persistent links. -1 means no limit. +ibase.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +ibase.max_links = -1 + +; Default database name for ibase_connect(). +;ibase.default_db = + +; Default username for ibase_connect(). +;ibase.default_user = + +; Default password for ibase_connect(). +;ibase.default_password = + +; Default charset for ibase_connect(). +;ibase.default_charset = + +; Default timestamp format. +ibase.timestampformat = "%Y-%m-%d %H:%M:%S" + +; Default date format. +ibase.dateformat = "%Y-%m-%d" + +; Default time format. +ibase.timeformat = "%H:%M:%S" + +[MySQL] +; Allow accessing, from PHP's perspective, local files with LOAD DATA statements +; http://php.net/mysql.allow_local_infile +mysql.allow_local_infile = On + +; Allow or prevent persistent links. +; http://php.net/mysql.allow-persistent +mysql.allow_persistent = On + +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/mysql.cache_size +mysql.cache_size = 2000 + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/mysql.max-persistent +mysql.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/mysql.max-links +mysql.max_links = -1 + +; Default port number for mysql_connect(). If unset, mysql_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +; http://php.net/mysql.default-port +mysql.default_port = + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/mysql.default-socket +mysql.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysql.default-host +mysql.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysql.default-user +mysql.default_user = + +; Default password for mysql_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysql.default_password") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +; http://php.net/mysql.default-password +mysql.default_password = + +; Maximum time (in seconds) for connect timeout. -1 means no limit +; http://php.net/mysql.connect-timeout +mysql.connect_timeout = 60 + +; Trace mode. When trace_mode is active (=On), warnings for table/index scans and +; SQL-Errors will be displayed. +; http://php.net/mysql.trace-mode +mysql.trace_mode = Off + +[MySQLi] + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/mysqli.max-persistent +mysqli.max_persistent = -1 + +; Allow accessing, from PHP's perspective, local files with LOAD DATA statements +; http://php.net/mysqli.allow_local_infile +;mysqli.allow_local_infile = On + +; Allow or prevent persistent links. +; http://php.net/mysqli.allow-persistent +mysqli.allow_persistent = On + +; Maximum number of links. -1 means no limit. +; http://php.net/mysqli.max-links +mysqli.max_links = -1 + +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/mysqli.cache_size +mysqli.cache_size = 2000 + +; Default port number for mysqli_connect(). If unset, mysqli_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +; http://php.net/mysqli.default-port +mysqli.default_port = 3306 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/mysqli.default-socket +mysqli.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysqli.default-host +mysqli.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysqli.default-user +mysqli.default_user = + +; Default password for mysqli_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +; http://php.net/mysqli.default-pw +mysqli.default_pw = + +; Allow or prevent reconnect +mysqli.reconnect = Off + +[mysqlnd] +; Enable / Disable collection of general statstics by mysqlnd which can be +; used to tune and monitor MySQL operations. +; http://php.net/mysqlnd.collect_statistics +mysqlnd.collect_statistics = On + +; Enable / Disable collection of memory usage statstics by mysqlnd which can be +; used to tune and monitor MySQL operations. +; http://php.net/mysqlnd.collect_memory_statistics +mysqlnd.collect_memory_statistics = Off + +; Size of a pre-allocated buffer used when sending commands to MySQL in bytes. +; http://php.net/mysqlnd.net_cmd_buffer_size +;mysqlnd.net_cmd_buffer_size = 2048 + +; Size of a pre-allocated buffer used for reading data sent by the server in +; bytes. +; http://php.net/mysqlnd.net_read_buffer_size +;mysqlnd.net_read_buffer_size = 32768 + +[OCI8] + +; Connection: Enables privileged connections using external +; credentials (OCI_SYSOPER, OCI_SYSDBA) +; http://php.net/oci8.privileged-connect +;oci8.privileged_connect = Off + +; Connection: The maximum number of persistent OCI8 connections per +; process. Using -1 means no limit. +; http://php.net/oci8.max-persistent +;oci8.max_persistent = -1 + +; Connection: The maximum number of seconds a process is allowed to +; maintain an idle persistent connection. Using -1 means idle +; persistent connections will be maintained forever. +; http://php.net/oci8.persistent-timeout +;oci8.persistent_timeout = -1 + +; Connection: The number of seconds that must pass before issuing a +; ping during oci_pconnect() to check the connection validity. When +; set to 0, each oci_pconnect() will cause a ping. Using -1 disables +; pings completely. +; http://php.net/oci8.ping-interval +;oci8.ping_interval = 60 + +; Connection: Set this to a user chosen connection class to be used +; for all pooled server requests with Oracle 11g Database Resident +; Connection Pooling (DRCP). To use DRCP, this value should be set to +; the same string for all web servers running the same application, +; the database pool must be configured, and the connection string must +; specify to use a pooled server. +;oci8.connection_class = + +; High Availability: Using On lets PHP receive Fast Application +; Notification (FAN) events generated when a database node fails. The +; database must also be configured to post FAN events. +;oci8.events = Off + +; Tuning: This option enables statement caching, and specifies how +; many statements to cache. Using 0 disables statement caching. +; http://php.net/oci8.statement-cache-size +;oci8.statement_cache_size = 20 + +; Tuning: Enables statement prefetching and sets the default number of +; rows that will be fetched automatically after statement execution. +; http://php.net/oci8.default-prefetch +;oci8.default_prefetch = 100 + +; Compatibility. Using On means oci_close() will not close +; oci_connect() and oci_new_connect() connections. +; http://php.net/oci8.old-oci-close-semantics +;oci8.old_oci_close_semantics = Off + +[PostgresSQL] +; Allow or prevent persistent links. +; http://php.net/pgsql.allow-persistent +pgsql.allow_persistent = On + +; Detect broken persistent links always with pg_pconnect(). +; Auto reset feature requires a little overheads. +; http://php.net/pgsql.auto-reset-persistent +pgsql.auto_reset_persistent = Off + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/pgsql.max-persistent +pgsql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +; http://php.net/pgsql.max-links +pgsql.max_links = -1 + +; Ignore PostgreSQL backends Notice message or not. +; Notice message logging require a little overheads. +; http://php.net/pgsql.ignore-notice +pgsql.ignore_notice = 0 + +; Log PostgreSQL backends Notice message or not. +; Unless pgsql.ignore_notice=0, module cannot log notice message. +; http://php.net/pgsql.log-notice +pgsql.log_notice = 0 + +[Sybase-CT] +; Allow or prevent persistent links. +; http://php.net/sybct.allow-persistent +sybct.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/sybct.max-persistent +sybct.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/sybct.max-links +sybct.max_links = -1 + +; Minimum server message severity to display. +; http://php.net/sybct.min-server-severity +sybct.min_server_severity = 10 + +; Minimum client message severity to display. +; http://php.net/sybct.min-client-severity +sybct.min_client_severity = 10 + +; Set per-context timeout +; http://php.net/sybct.timeout +;sybct.timeout= + +;sybct.packet_size + +; The maximum time in seconds to wait for a connection attempt to succeed before returning failure. +; Default: one minute +;sybct.login_timeout= + +; The name of the host you claim to be connecting from, for display by sp_who. +; Default: none +;sybct.hostname= + +; Allows you to define how often deadlocks are to be retried. -1 means "forever". +; Default: 0 +;sybct.deadlock_retry_count= + +[bcmath] +; Number of decimal digits for all bcmath functions. +; http://php.net/bcmath.scale +bcmath.scale = 0 + +[browscap] +; http://php.net/browscap +;browscap = extra/browscap.ini + +[Session] +; Handler used to store/retrieve data. +; http://php.net/session.save-handler +session.save_handler = files + +; Argument passed to save_handler. In the case of files, this is the path +; where data files are stored. Note: Windows users have to change this +; variable in order to use PHP's session functions. +; +; The path can be defined as: +; +; session.save_path = "N;/path" +; +; where N is an integer. Instead of storing all the session files in +; /path, what this will do is use subdirectories N-levels deep, and +; store the session data in those directories. This is useful if you +; or your OS have problems with lots of files in one directory, and is +; a more efficient layout for servers that handle lots of sessions. +; +; NOTE 1: PHP will not create this directory structure automatically. +; You can use the script in the ext/session dir for that purpose. +; NOTE 2: See the section on garbage collection below if you choose to +; use subdirectories for session storage +; +; The file storage module creates files using mode 600 by default. +; You can change that by using +; +; session.save_path = "N;MODE;/path" +; +; where MODE is the octal representation of the mode. Note that this +; does not overwrite the process's umask. +; http://php.net/session.save-path +;session.save_path = "/tmp" + +; Whether to use cookies. +; http://php.net/session.use-cookies +session.use_cookies = 1 + +; http://php.net/session.cookie-secure +;session.cookie_secure = + +; This option forces PHP to fetch and use a cookie for storing and maintaining +; the session id. We encourage this operation as it's very helpful in combatting +; session hijacking when not specifying and managing your own session id. It is +; not the end all be all of session hijacking defense, but it's a good start. +; http://php.net/session.use-only-cookies +session.use_only_cookies = 1 + +; Name of the session (used as cookie name). +; http://php.net/session.name +session.name = PHPSESSID + +; Initialize session on request startup. +; http://php.net/session.auto-start +session.auto_start = 0 + +; Lifetime in seconds of cookie or, if 0, until browser is restarted. +; http://php.net/session.cookie-lifetime +session.cookie_lifetime = 0 + +; The path for which the cookie is valid. +; http://php.net/session.cookie-path +session.cookie_path = / + +; The domain for which the cookie is valid. +; http://php.net/session.cookie-domain +session.cookie_domain = + +; Whether or not to add the httpOnly flag to the cookie, which makes it inaccessible to browser scripting languages such as JavaScript. +; http://php.net/session.cookie-httponly +session.cookie_httponly = + +; Handler used to serialize data. php is the standard serializer of PHP. +; http://php.net/session.serialize-handler +session.serialize_handler = php + +; Defines the probability that the 'garbage collection' process is started +; on every session initialization. The probability is calculated by using +; gc_probability/gc_divisor. Where session.gc_probability is the numerator +; and gc_divisor is the denominator in the equation. Setting this value to 1 +; when the session.gc_divisor value is 100 will give you approximately a 1% chance +; the gc will run on any give request. +; Default Value: 1 +; Development Value: 1 +; Production Value: 1 +; http://php.net/session.gc-probability +session.gc_probability = 1 + +; Defines the probability that the 'garbage collection' process is started on every +; session initialization. The probability is calculated by using the following equation: +; gc_probability/gc_divisor. Where session.gc_probability is the numerator and +; session.gc_divisor is the denominator in the equation. Setting this value to 1 +; when the session.gc_divisor value is 100 will give you approximately a 1% chance +; the gc will run on any give request. Increasing this value to 1000 will give you +; a 0.1% chance the gc will run on any give request. For high volume production servers, +; this is a more efficient approach. +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 +; http://php.net/session.gc-divisor +session.gc_divisor = 1000 + +; After this number of seconds, stored data will be seen as 'garbage' and +; cleaned up by the garbage collection process. +; http://php.net/session.gc-maxlifetime +session.gc_maxlifetime = 1440 + +; NOTE: If you are using the subdirectory option for storing session files +; (see session.save_path above), then garbage collection does *not* +; happen automatically. You will need to do your own garbage +; collection through a shell script, cron entry, or some other method. +; For example, the following script would is the equivalent of +; setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): +; find /path/to/sessions -cmin +24 | xargs rm + +; PHP 4.2 and less have an undocumented feature/bug that allows you to +; to initialize a session variable in the global scope, even when register_globals +; is disabled. PHP 4.3 and later will warn you, if this feature is used. +; You can disable the feature and the warning separately. At this time, +; the warning is only displayed, if bug_compat_42 is enabled. This feature +; introduces some serious security problems if not handled correctly. It's +; recommended that you do not use this feature on production servers. But you +; should enable this on development servers and enable the warning as well. If you +; do not enable the feature on development servers, you won't be warned when it's +; used and debugging errors caused by this can be difficult to track down. +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/session.bug-compat-42 +session.bug_compat_42 = Off + +; This setting controls whether or not you are warned by PHP when initializing a +; session value into the global space. session.bug_compat_42 must be enabled before +; these warnings can be issued by PHP. See the directive above for more information. +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/session.bug-compat-warn +session.bug_compat_warn = Off + +; Check HTTP Referer to invalidate externally stored URLs containing ids. +; HTTP_REFERER has to contain this substring for the session to be +; considered as valid. +; http://php.net/session.referer-check +session.referer_check = + +; How many bytes to read from the file. +; http://php.net/session.entropy-length +session.entropy_length = 0 + +; Specified here to create the session id. +; http://php.net/session.entropy-file +; On systems that don't have /dev/urandom /dev/arandom can be used +; On windows, setting the entropy_length setting will activate the +; Windows random source (using the CryptoAPI) +;session.entropy_file = /dev/urandom + +; Set to {nocache,private,public,} to determine HTTP caching aspects +; or leave this empty to avoid sending anti-caching headers. +; http://php.net/session.cache-limiter +session.cache_limiter = nocache + +; Document expires after n minutes. +; http://php.net/session.cache-expire +session.cache_expire = 180 + +; trans sid support is disabled by default. +; Use of trans sid may risk your users security. +; Use this option with caution. +; - User may send URL contains active session ID +; to other person via. email/irc/etc. +; - URL that contains active session ID may be stored +; in publically accessible computer. +; - User may access your site with the same session ID +; always using URL stored in browser's history or bookmarks. +; http://php.net/session.use-trans-sid +session.use_trans_sid = 0 + +; Select a hash function for use in generating session ids. +; Possible Values +; 0 (MD5 128 bits) +; 1 (SHA-1 160 bits) +; This option may also be set to the name of any hash function supported by +; the hash extension. A list of available hashes is returned by the hash_algos() +; function. +; http://php.net/session.hash-function +session.hash_function = 0 + +; Define how many bits are stored in each character when converting +; the binary hash data to something readable. +; Possible values: +; 4 (4 bits: 0-9, a-f) +; 5 (5 bits: 0-9, a-v) +; 6 (6 bits: 0-9, a-z, A-Z, "-", ",") +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 +; http://php.net/session.hash-bits-per-character +session.hash_bits_per_character = 5 + +; The URL rewriter will look for URLs in a defined set of HTML tags. +; form/fieldset are special; if you include them here, the rewriter will +; add a hidden field with the info which is otherwise appended +; to URLs. If you want XHTML conformity, remove the form entry. +; Note that all valid entries require a "=", even if no value follows. +; Default Value: "a=href,area=href,frame=src,form=,fieldset=" +; Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; http://php.net/url-rewriter.tags +url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry" + +[MSSQL] +; Allow or prevent persistent links. +mssql.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +mssql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +mssql.max_links = -1 + +; Minimum error severity to display. +mssql.min_error_severity = 10 + +; Minimum message severity to display. +mssql.min_message_severity = 10 + +; Compatibility mode with old versions of PHP 3.0. +mssql.compatability_mode = Off + +; Connect timeout +;mssql.connect_timeout = 5 + +; Query timeout +;mssql.timeout = 60 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textlimit = 4096 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textsize = 4096 + +; Limits the number of records in each batch. 0 = all records in one batch. +;mssql.batchsize = 0 + +; Specify how datetime and datetim4 columns are returned +; On => Returns data converted to SQL server settings +; Off => Returns values as YYYY-MM-DD hh:mm:ss +;mssql.datetimeconvert = On + +; Use NT authentication when connecting to the server +mssql.secure_connection = Off + +; Specify max number of processes. -1 = library default +; msdlib defaults to 25 +; FreeTDS defaults to 4096 +;mssql.max_procs = -1 + +; Specify client character set. +; If empty or not set the client charset from freetds.comf is used +; This is only used when compiled with FreeTDS +;mssql.charset = "ISO-8859-1" + +[Assertion] +; Assert(expr); active by default. +; http://php.net/assert.active +;assert.active = On + +; Issue a PHP warning for each failed assertion. +; http://php.net/assert.warning +;assert.warning = On + +; Don't bail out by default. +; http://php.net/assert.bail +;assert.bail = Off + +; User-function to be called if an assertion fails. +; http://php.net/assert.callback +;assert.callback = 0 + +; Eval the expression with current error_reporting(). Set to true if you want +; error_reporting(0) around the eval(). +; http://php.net/assert.quiet-eval +;assert.quiet_eval = 0 + +[COM] +; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs +; http://php.net/com.typelib-file +;com.typelib_file = + +; allow Distributed-COM calls +; http://php.net/com.allow-dcom +;com.allow_dcom = true + +; autoregister constants of a components typlib on com_load() +; http://php.net/com.autoregister-typelib +;com.autoregister_typelib = true + +; register constants casesensitive +; http://php.net/com.autoregister-casesensitive +;com.autoregister_casesensitive = false + +; show warnings on duplicate constant registrations +; http://php.net/com.autoregister-verbose +;com.autoregister_verbose = true + +; The default character set code-page to use when passing strings to and from COM objects. +; Default: system ANSI code page +;com.code_page= + +[mbstring] +; language for internal character representation. +; http://php.net/mbstring.language +;mbstring.language = Japanese + +; internal/script encoding. +; Some encoding cannot work as internal encoding. +; (e.g. SJIS, BIG5, ISO-2022-*) +; http://php.net/mbstring.internal-encoding +;mbstring.internal_encoding = EUC-JP + +; http input encoding. +; http://php.net/mbstring.http-input +;mbstring.http_input = auto + +; http output encoding. mb_output_handler must be +; registered as output buffer to function +; http://php.net/mbstring.http-output +;mbstring.http_output = SJIS + +; enable automatic encoding translation according to +; mbstring.internal_encoding setting. Input chars are +; converted to internal encoding by setting this to On. +; Note: Do _not_ use automatic encoding translation for +; portable libs/applications. +; http://php.net/mbstring.encoding-translation +;mbstring.encoding_translation = Off + +; automatic encoding detection order. +; auto means +; http://php.net/mbstring.detect-order +;mbstring.detect_order = auto + +; substitute_character used when character cannot be converted +; one from another +; http://php.net/mbstring.substitute-character +;mbstring.substitute_character = none; + +; overload(replace) single byte functions by mbstring functions. +; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(), +; etc. Possible values are 0,1,2,4 or combination of them. +; For example, 7 for overload everything. +; 0: No overload +; 1: Overload mail() function +; 2: Overload str*() functions +; 4: Overload ereg*() functions +; http://php.net/mbstring.func-overload +;mbstring.func_overload = 0 + +; enable strict encoding detection. +;mbstring.strict_detection = Off + +; This directive specifies the regex pattern of content types for which mb_output_handler() +; is activated. +; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml) +;mbstring.http_output_conv_mimetype= + +; Allows to set script encoding. Only affects if PHP is compiled with --enable-zend-multibyte +; Default: "" +;mbstring.script_encoding= + +[gd] +; Tell the jpeg decode to ignore warnings and try to create +; a gd image. The warning will then be displayed as notices +; disabled by default +; http://php.net/gd.jpeg-ignore-warning +;gd.jpeg_ignore_warning = 0 + +[exif] +; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. +; With mbstring support this will automatically be converted into the encoding +; given by corresponding encode setting. When empty mbstring.internal_encoding +; is used. For the decode settings you can distinguish between motorola and +; intel byte order. A decode setting cannot be empty. +; http://php.net/exif.encode-unicode +;exif.encode_unicode = ISO-8859-15 + +; http://php.net/exif.decode-unicode-motorola +;exif.decode_unicode_motorola = UCS-2BE + +; http://php.net/exif.decode-unicode-intel +;exif.decode_unicode_intel = UCS-2LE + +; http://php.net/exif.encode-jis +;exif.encode_jis = + +; http://php.net/exif.decode-jis-motorola +;exif.decode_jis_motorola = JIS + +; http://php.net/exif.decode-jis-intel +;exif.decode_jis_intel = JIS + +[Tidy] +; The path to a default tidy configuration file to use when using tidy +; http://php.net/tidy.default-config +;tidy.default_config = /usr/local/lib/php/default.tcfg + +; Should tidy clean and repair output automatically? +; WARNING: Do not use this option if you are generating non-html content +; such as dynamic images +; http://php.net/tidy.clean-output +tidy.clean_output = Off + +[soap] +; Enables or disables WSDL caching feature. +; http://php.net/soap.wsdl-cache-enabled +soap.wsdl_cache_enabled=1 + +; Sets the directory name where SOAP extension will put cache files. +; http://php.net/soap.wsdl-cache-dir +soap.wsdl_cache_dir="/tmp" + +; (time to live) Sets the number of second while cached file will be used +; instead of original one. +; http://php.net/soap.wsdl-cache-ttl +soap.wsdl_cache_ttl=86400 + +; Sets the size of the cache limit. (Max. number of WSDL files to cache) +soap.wsdl_cache_limit = 5 + +[sysvshm] +; A default size of the shared memory segment +;sysvshm.init_mem = 10000 + +[ldap] +; Sets the maximum number of open links or -1 for unlimited. +ldap.max_links = -1 + +[mcrypt] +; For more information about mcrypt settings see http://php.net/mcrypt-module-open + +; Directory where to load mcrypt algorithms +; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt) +;mcrypt.algorithms_dir= + +; Directory where to load mcrypt modes +; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt) +;mcrypt.modes_dir= + +[dba] +;dba.default_handler= + +; Local Variables: +; tab-width: 4 +; End: + +<% @directives.sort_by { |key, val| key }.each do |directive, value| -%> +<%= "#{directive}=\"#{value}\"" %> +<% end -%> diff --git a/cookbooks/php/templates/redhat/php.ini.erb b/cookbooks/php/templates/redhat/php.ini.erb new file mode 100644 index 0000000..c84d60d --- /dev/null +++ b/cookbooks/php/templates/redhat/php.ini.erb @@ -0,0 +1,1225 @@ +[PHP] + +;;;;;;;;;;;;;;;;;;; +; About php.ini ; +;;;;;;;;;;;;;;;;;;; +; This file controls many aspects of PHP's behavior. In order for PHP to +; read it, it must be named 'php.ini'. PHP looks for it in the current +; working directory, in the path designated by the environment variable +; PHPRC, and in the path that was defined in compile time (in that order). +; Under Windows, the compile-time path is the Windows directory. The +; path in which the php.ini file is looked for can be overridden using +; the -c argument in command line mode. +; +; The syntax of the file is extremely simple. Whitespace and Lines +; beginning with a semicolon are silently ignored (as you probably guessed). +; Section headers (e.g. [Foo]) are also silently ignored, even though +; they might mean something in the future. +; +; Directives are specified using the following syntax: +; directive = value +; Directive names are *case sensitive* - foo=bar is different from FOO=bar. +; +; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one +; of the INI constants (On, Off, True, False, Yes, No and None) or an expression +; (e.g. E_ALL & ~E_NOTICE), or a quoted string ("foo"). +; +; Expressions in the INI file are limited to bitwise operators and parentheses: +; | bitwise OR +; & bitwise AND +; ~ bitwise NOT +; ! boolean NOT +; +; Boolean flags can be turned on using the values 1, On, True or Yes. +; They can be turned off using the values 0, Off, False or No. +; +; An empty string can be denoted by simply not writing anything after the equal +; sign, or by using the None keyword: +; +; foo = ; sets foo to an empty string +; foo = none ; sets foo to an empty string +; foo = "none" ; sets foo to the string 'none' +; +; If you use constants in your value, and these constants belong to a +; dynamically loaded extension (either a PHP extension or a Zend extension), +; you may only use these constants *after* the line that loads the extension. +; +; +;;;;;;;;;;;;;;;;;;; +; About this file ; +;;;;;;;;;;;;;;;;;;; +; This is the recommended, PHP 5-style version of the php.ini-dist file. It +; sets some non standard settings, that make PHP more efficient, more secure, +; and encourage cleaner coding. +; +; The price is that with these settings, PHP may be incompatible with some +; applications, and sometimes, more difficult to develop with. Using this +; file is warmly recommended for production sites. As all of the changes from +; the standard settings are thoroughly documented, you can go over each one, +; and decide whether you want to use it or not. +; +; For general information about the php.ini file, please consult the php.ini-dist +; file, included in your PHP distribution. +; +; This file is different from the php.ini-dist file in the fact that it features +; different values for several directives, in order to improve performance, while +; possibly breaking compatibility with the standard out-of-the-box behavior of +; PHP. Please make sure you read what's different, and modify your scripts +; accordingly, if you decide to use this file instead. +; +; - register_globals = Off [Security, Performance] +; Global variables are no longer registered for input data (POST, GET, cookies, +; environment and other server variables). Instead of using $foo, you must use +; you can use $_REQUEST["foo"] (includes any variable that arrives through the +; request, namely, POST, GET and cookie variables), or use one of the specific +; $_GET["foo"], $_POST["foo"], $_COOKIE["foo"] or $_FILES["foo"], depending +; on where the input originates. Also, you can look at the +; import_request_variables() function. +; Note that register_globals is going to be depracated (i.e., turned off by +; default) in the next version of PHP, because it often leads to security bugs. +; Read http://php.net/manual/en/security.registerglobals.php for further +; information. +; - register_long_arrays = Off [Performance] +; Disables registration of the older (and deprecated) long predefined array +; variables ($HTTP_*_VARS). Instead, use the superglobals that were +; introduced in PHP 4.1.0 +; - display_errors = Off [Security] +; With this directive set to off, errors that occur during the execution of +; scripts will no longer be displayed as a part of the script output, and thus, +; will no longer be exposed to remote users. With some errors, the error message +; content may expose information about your script, web server, or database +; server that may be exploitable for hacking. Production sites should have this +; directive set to off. +; - log_errors = On [Security] +; This directive complements the above one. Any errors that occur during the +; execution of your script will be logged (typically, to your server's error log, +; but can be configured in several ways). Along with setting display_errors to off, +; this setup gives you the ability to fully understand what may have gone wrong, +; without exposing any sensitive information to remote users. +; - output_buffering = 4096 [Performance] +; Set a 4KB output buffer. Enabling output buffering typically results in less +; writes, and sometimes less packets sent on the wire, which can often lead to +; better performance. The gain this directive actually yields greatly depends +; on which Web server you're working with, and what kind of scripts you're using. +; - register_argc_argv = Off [Performance] +; Disables registration of the somewhat redundant $argv and $argc global +; variables. +; - magic_quotes_gpc = Off [Performance] +; Input data is no longer escaped with slashes so that it can be sent into +; SQL databases without further manipulation. Instead, you should use the +; function addslashes() on each input element you wish to send to a database. +; - variables_order = "GPCS" [Performance] +; The environment variables are not hashed into the $_ENV. To access +; environment variables, you can use getenv() instead. +; - error_reporting = E_ALL [Code Cleanliness, Security(?)] +; By default, PHP surpresses errors of type E_NOTICE. These error messages +; are emitted for non-critical errors, but that could be a symptom of a bigger +; problem. Most notably, this will cause error messages about the use +; of uninitialized variables to be displayed. +; - allow_call_time_pass_reference = Off [Code cleanliness] +; It's not possible to decide to force a variable to be passed by reference +; when calling a function. The PHP 4 style to do this is by making the +; function require the relevant argument by reference. + + +;;;;;;;;;;;;;;;;;;;; +; Language Options ; +;;;;;;;;;;;;;;;;;;;; + +; Enable the PHP scripting language engine under Apache. +engine = On + +; Enable compatibility mode with Zend Engine 1 (PHP 4.x) +zend.ze1_compatibility_mode = Off + +; Allow the tags are recognized. +; NOTE: Using short tags should be avoided when developing applications or +; libraries that are meant for redistribution, or deployment on PHP +; servers which are not under your control, because short tags may not +; be supported on the target server. For portable, redistributable code, +; be sure not to use short tags. +short_open_tag = On + +; Allow ASP-style <% %> tags. +asp_tags = Off + +; The number of significant digits displayed in floating point numbers. +precision = 14 + +; Enforce year 2000 compliance (will cause problems with non-compliant browsers) +y2k_compliance = On + +; Output buffering allows you to send header lines (including cookies) even +; after you send body content, at the price of slowing PHP's output layer a +; bit. You can enable output buffering during runtime by calling the output +; buffering functions. You can also enable output buffering for all files by +; setting this directive to On. If you wish to limit the size of the buffer +; to a certain size - you can use a maximum number of bytes instead of 'On', as +; a value for this directive (e.g., output_buffering=4096). +output_buffering = 4096 + +; You can redirect all of the output of your scripts to a function. For +; example, if you set output_handler to "mb_output_handler", character +; encoding will be transparently converted to the specified encoding. +; Setting any output handler automatically turns on output buffering. +; Note: People who wrote portable scripts should not depend on this ini +; directive. Instead, explicitly set the output handler using ob_start(). +; Using this ini directive may cause problems unless you know what script +; is doing. +; Note: You cannot use both "mb_output_handler" with "ob_iconv_handler" +; and you cannot use both "ob_gzhandler" and "zlib.output_compression". +; Note: output_handler must be empty if this is set 'On' !!!! +; Instead you must use zlib.output_handler. +;output_handler = + +; Transparent output compression using the zlib library +; Valid values for this option are 'off', 'on', or a specific buffer size +; to be used for compression (default is 4KB) +; Note: Resulting chunk size may vary due to nature of compression. PHP +; outputs chunks that are few hundreds bytes each as a result of +; compression. If you prefer a larger chunk size for better +; performance, enable output_buffering in addition. +; Note: You need to use zlib.output_handler instead of the standard +; output_handler, or otherwise the output will be corrupted. +zlib.output_compression = Off + +; You cannot specify additional output handlers if zlib.output_compression +; is activated here. This setting does the same as output_handler but in +; a different order. +;zlib.output_handler = + +; Implicit flush tells PHP to tell the output layer to flush itself +; automatically after every output block. This is equivalent to calling the +; PHP function flush() after each and every call to print() or echo() and each +; and every HTML block. Turning this option on has serious performance +; implications and is generally recommended for debugging purposes only. +implicit_flush = Off + +; The unserialize callback function will be called (with the undefined class' +; name as parameter), if the unserializer finds an undefined class +; which should be instantiated. +; A warning appears if the specified function is not defined, or if the +; function doesn't include/implement the missing class. +; So only set this entry, if you really want to implement such a +; callback-function. +unserialize_callback_func= + +; When floats & doubles are serialized store serialize_precision significant +; digits after the floating point. The default value ensures that when floats +; are decoded with unserialize, the data will remain the same. +serialize_precision = 100 + +; Whether to enable the ability to force arguments to be passed by reference +; at function call time. This method is deprecated and is likely to be +; unsupported in future versions of PHP/Zend. The encouraged method of +; specifying which arguments should be passed by reference is in the function +; declaration. You're encouraged to try and turn this option Off and make +; sure your scripts work properly with it in order to ensure they will work +; with future versions of the language (you will receive a warning each time +; you use this feature, and the argument will be passed by value instead of by +; reference). +allow_call_time_pass_reference = Off + +; +; Safe Mode +; +safe_mode = Off + +; By default, Safe Mode does a UID compare check when +; opening files. If you want to relax this to a GID compare, +; then turn on safe_mode_gid. +safe_mode_gid = Off + +; When safe_mode is on, UID/GID checks are bypassed when +; including files from this directory and its subdirectories. +; (directory must also be in include_path or full path must +; be used when including) +safe_mode_include_dir = + +; When safe_mode is on, only executables located in the safe_mode_exec_dir +; will be allowed to be executed via the exec family of functions. +safe_mode_exec_dir = + +; Setting certain environment variables may be a potential security breach. +; This directive contains a comma-delimited list of prefixes. In Safe Mode, +; the user may only alter environment variables whose names begin with the +; prefixes supplied here. By default, users will only be able to set +; environment variables that begin with PHP_ (e.g. PHP_FOO=BAR). +; +; Note: If this directive is empty, PHP will let the user modify ANY +; environment variable! +safe_mode_allowed_env_vars = PHP_ + +; This directive contains a comma-delimited list of environment variables that +; the end user won't be able to change using putenv(). These variables will be +; protected even if safe_mode_allowed_env_vars is set to allow to change them. +safe_mode_protected_env_vars = LD_LIBRARY_PATH + +; open_basedir, if set, limits all file operations to the defined directory +; and below. This directive makes most sense if used in a per-directory +; or per-virtualhost web server configuration file. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +;open_basedir = + +; This directive allows you to disable certain functions for security reasons. +; It receives a comma-delimited list of function names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +disable_functions = + +; This directive allows you to disable certain classes for security reasons. +; It receives a comma-delimited list of class names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +disable_classes = + +; Colors for Syntax Highlighting mode. Anything that's acceptable in +; would work. +;highlight.string = #DD0000 +;highlight.comment = #FF9900 +;highlight.keyword = #007700 +;highlight.bg = #FFFFFF +;highlight.default = #0000BB +;highlight.html = #000000 + +; If enabled, the request will be allowed to complete even if the user aborts +; the request. Consider enabling it if executing long request, which may end up +; being interrupted by the user or a browser timing out. +; ignore_user_abort = On + +; Determines the size of the realpath cache to be used by PHP. This value should +; be increased on systems where PHP opens many files to reflect the quantity of +; the file operations performed. +; realpath_cache_size=16k + +; Duration of time, in seconds for which to cache realpath information for a given +; file or directory. For systems with rarely changing files, consider increasing this +; value. +; realpath_cache_ttl=120 + +; +; Misc +; +; Decides whether PHP may expose the fact that it is installed on the server +; (e.g. by adding its signature to the Web server header). It is no security +; threat in any way, but it makes it possible to determine whether you use PHP +; on your server or not. +expose_php = On + + +;;;;;;;;;;;;;;;;;;; +; Resource Limits ; +;;;;;;;;;;;;;;;;;;; + +max_execution_time = 30 ; Maximum execution time of each script, in seconds +max_input_time = 60 ; Maximum amount of time each script may spend parsing request data +memory_limit = 128M ; Maximum amount of memory a script may consume + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Error handling and logging ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; error_reporting is a bit-field. Or each number up to get desired error +; reporting level +; E_ALL - All errors and warnings (doesn't include E_STRICT) +; E_ERROR - fatal run-time errors +; E_WARNING - run-time warnings (non-fatal errors) +; E_PARSE - compile-time parse errors +; E_NOTICE - run-time notices (these are warnings which often result +; from a bug in your code, but it's possible that it was +; intentional (e.g., using an uninitialized variable and +; relying on the fact it's automatically initialized to an +; empty string) +; E_STRICT - run-time notices, enable to have PHP suggest changes +; to your code which will ensure the best interoperability +; and forward compatibility of your code +; E_CORE_ERROR - fatal errors that occur during PHP's initial startup +; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's +; initial startup +; E_COMPILE_ERROR - fatal compile-time errors +; E_COMPILE_WARNING - compile-time warnings (non-fatal errors) +; E_USER_ERROR - user-generated error message +; E_USER_WARNING - user-generated warning message +; E_USER_NOTICE - user-generated notice message +; +; Examples: +; +; - Show all errors, except for notices and coding standards warnings +; +;error_reporting = E_ALL & ~E_NOTICE +; +; - Show all errors, except for notices +; +;error_reporting = E_ALL & ~E_NOTICE | E_STRICT +; +; - Show only errors +; +;error_reporting = E_COMPILE_ERROR|E_ERROR|E_CORE_ERROR +; +; - Show all errors, except coding standards warnings +; +error_reporting = E_ALL + +; Print out errors (as a part of the output). For production web sites, +; you're strongly encouraged to turn this feature off, and use error logging +; instead (see below). Keeping display_errors enabled on a production web site +; may reveal security information to end users, such as file paths on your Web +; server, your database schema or other information. +display_errors = Off + +; Even when display_errors is on, errors that occur during PHP's startup +; sequence are not displayed. It's strongly recommended to keep +; display_startup_errors off, except for when debugging. +display_startup_errors = Off + +; Log errors into a log file (server-specific log, stderr, or error_log (below)) +; As stated above, you're strongly advised to use error logging in place of +; error displaying on production web sites. +log_errors = On + +; Set maximum length of log_errors. In error_log information about the source is +; added. The default is 1024 and 0 allows to not apply any maximum length at all. +log_errors_max_len = 1024 + +; Do not log repeated messages. Repeated errors must occur in same file on same +; line until ignore_repeated_source is set true. +ignore_repeated_errors = Off + +; Ignore source of message when ignoring repeated messages. When this setting +; is On you will not log errors with repeated messages from different files or +; sourcelines. +ignore_repeated_source = Off + +; If this parameter is set to Off, then memory leaks will not be shown (on +; stdout or in the log). This has only effect in a debug compile, and if +; error reporting includes E_WARNING in the allowed list +report_memleaks = On + +; Store the last error/warning message in $php_errormsg (boolean). +track_errors = Off + +; Disable the inclusion of HTML tags in error messages. +; Note: Never use this feature for production boxes. +;html_errors = Off + +; If html_errors is set On PHP produces clickable error messages that direct +; to a page describing the error or function causing the error in detail. +; You can download a copy of the PHP manual from http://www.php.net/docs.php +; and change docref_root to the base URL of your local copy including the +; leading '/'. You must also specify the file extension being used including +; the dot. +; Note: Never use this feature for production boxes. +;docref_root = "/phpmanual/" +;docref_ext = .html + +; String to output before an error message. +;error_prepend_string = "" + +; String to output after an error message. +;error_append_string = "" + +; Log errors to specified file. +;error_log = filename + +; Log errors to syslog (Event Log on NT, not valid in Windows 95). +;error_log = syslog + + +;;;;;;;;;;;;;;;;; +; Data Handling ; +;;;;;;;;;;;;;;;;; +; +; Note - track_vars is ALWAYS enabled as of PHP 4.0.3 + +; The separator used in PHP generated URLs to separate arguments. +; Default is "&". +;arg_separator.output = "&" + +; List of separator(s) used by PHP to parse input URLs into variables. +; Default is "&". +; NOTE: Every character in this directive is considered as separator! +;arg_separator.input = ";&" + +; This directive describes the order in which PHP registers GET, POST, Cookie, +; Environment and Built-in variables (G, P, C, E & S respectively, often +; referred to as EGPCS or GPC). Registration is done from left to right, newer +; values override older values. +variables_order = "EGPCS" + +; Whether or not to register the EGPCS variables as global variables. You may +; want to turn this off if you don't want to clutter your scripts' global scope +; with user data. This makes most sense when coupled with track_vars - in which +; case you can access all of the GPC variables through the $HTTP_*_VARS[], +; variables. +; +; You should do your best to write your scripts so that they do not require +; register_globals to be on; Using form variables as globals can easily lead +; to possible security problems, if the code is not very well thought of. +register_globals = Off + +; Whether or not to register the old-style input arrays, HTTP_GET_VARS +; and friends. If you're not using them, it's recommended to turn them off, +; for performance reasons. +register_long_arrays = Off + +; This directive tells PHP whether to declare the argv&argc variables (that +; would contain the GET information). If you don't use these variables, you +; should turn it off for increased performance. +register_argc_argv = Off + +; When enabled, the SERVER and ENV variables are created when they're first +; used (Just In Time) instead of when the script starts. If these variables +; are not used within a script, having this directive on will result in a +; performance gain. The PHP directives register_globals, register_long_arrays, +; and register_argc_argv must be disabled for this directive to have any affect. +auto_globals_jit = On + +; Maximum size of POST data that PHP will accept. +post_max_size = 8M + +; Magic quotes +; + +; Magic quotes for incoming GET/POST/Cookie data. +magic_quotes_gpc = Off + +; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc. +magic_quotes_runtime = Off + +; Use Sybase-style magic quotes (escape ' with '' instead of \'). +magic_quotes_sybase = Off + +; Automatically add files before or after any PHP document. +auto_prepend_file = +auto_append_file = + +; As of 4.0b4, PHP always outputs a character encoding by default in +; the Content-type: header. To disable sending of the charset, simply +; set it to be empty. +; +; PHP's built-in default is text/html +default_mimetype = "text/html" +;default_charset = "iso-8859-1" + +; Always populate the $HTTP_RAW_POST_DATA variable. +;always_populate_raw_post_data = On + + +;;;;;;;;;;;;;;;;;;;;;;;;; +; Paths and Directories ; +;;;;;;;;;;;;;;;;;;;;;;;;; + +; UNIX: "/path1:/path2" +;include_path = ".:/php/includes" +; +; Windows: "\path1;\path2" +;include_path = ".;c:\php\includes" + +; The root of the PHP pages, used only if nonempty. +; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root +; if you are running php as a CGI under any web server (other than IIS) +; see documentation for security issues. The alternate is to use the +; cgi.force_redirect configuration below +doc_root = + +; The directory under which PHP opens the script using /~username used only +; if nonempty. +user_dir = + +; Directory in which the loadable extensions (modules) reside. +extension_dir = "<%= node['php']['ext_dir'] %>" + +; Whether or not to enable the dl() function. The dl() function does NOT work +; properly in multithreaded servers, such as IIS or Zeus, and is automatically +; disabled on them. +enable_dl = On + +; cgi.force_redirect is necessary to provide security running PHP as a CGI under +; most web servers. Left undefined, PHP turns this on by default. You can +; turn it off here AT YOUR OWN RISK +; **You CAN safely turn this off for IIS, in fact, you MUST.** +; cgi.force_redirect = 1 + +; if cgi.nph is enabled it will force cgi to always sent Status: 200 with +; every request. +; cgi.nph = 1 + +; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape +; (iPlanet) web servers, you MAY need to set an environment variable name that PHP +; will look for to know it is OK to continue execution. Setting this variable MAY +; cause security issues, KNOW WHAT YOU ARE DOING FIRST. +; cgi.redirect_status_env = ; + +; FastCGI under IIS (on WINNT based OS) supports the ability to impersonate +; security tokens of the calling client. This allows IIS to define the +; security context that the request runs under. mod_fastcgi under Apache +; does not currently support this feature (03/17/2002) +; Set to 1 if running under IIS. Default is zero. +; fastcgi.impersonate = 1; + +; Disable logging through FastCGI connection +; fastcgi.log = 0 + +; cgi.rfc2616_headers configuration option tells PHP what type of headers to +; use when sending HTTP response code. If it's set 0 PHP sends Status: header that +; is supported by Apache. When this option is set to 1 PHP will send +; RFC2616 compliant header. +; Default is zero. +;cgi.rfc2616_headers = 0 + + +;;;;;;;;;;;;;;;; +; File Uploads ; +;;;;;;;;;;;;;;;; + +; Whether to allow HTTP file uploads. +file_uploads = On + +; Temporary directory for HTTP uploaded files (will use system default if not +; specified). +;upload_tmp_dir = + +; Maximum allowed size for uploaded files. +upload_max_filesize = 2M + + +;;;;;;;;;;;;;;;;;; +; Fopen wrappers ; +;;;;;;;;;;;;;;;;;; + +; Whether to allow the treatment of URLs (like http:// or ftp://) as files. +allow_url_fopen = On + +; Define the anonymous ftp password (your email address) +;from="john@doe.com" + +; Define the User-Agent string +; user_agent="PHP" + +; Default timeout for socket based streams (seconds) +default_socket_timeout = 60 + +; If your scripts have to deal with files from Macintosh systems, +; or you are running on a Mac and need to deal with files from +; unix or win32 systems, setting this flag will cause PHP to +; automatically detect the EOL character in those files so that +; fgets() and file() will work regardless of the source of the file. +; auto_detect_line_endings = Off + + +;;;;;;;;;;;;;;;;;;;;;; +; Dynamic Extensions ; +;;;;;;;;;;;;;;;;;;;;;; +; +; If you wish to have an extension loaded automatically, use the following +; syntax: +; +; extension=modulename.extension +; +; For example: +; +; extension=msql.so +; +; Note that it should be the name of the module only; no directory information +; needs to go here. Specify the location of the extension with the +; extension_dir directive above. + + +;;;; +; Note: packaged extension modules are now loaded via the .ini files +; found in the directory /etc/php.d; these are loaded by default. +;;;; + + +;;;;;;;;;;;;;;;;;;; +; Module Settings ; +;;;;;;;;;;;;;;;;;;; + +[Date] +; Defines the default timezone used by the date functions +;date.timezone = + +[Syslog] +; Whether or not to define the various syslog variables (e.g. $LOG_PID, +; $LOG_CRON, etc.). Turning it off is a good idea performance-wise. In +; runtime, you can define these variables by calling define_syslog_variables(). +define_syslog_variables = Off + +[mail function] +; For Win32 only. +SMTP = localhost +smtp_port = 25 + +; For Win32 only. +;sendmail_from = me@example.com + +; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). +sendmail_path = /usr/sbin/sendmail -t -i + +; Force the addition of the specified parameters to be passed as extra parameters +; to the sendmail binary. These parameters will always replace the value of +; the 5th parameter to mail(), even in safe mode. +;mail.force_extra_parameters = + +[SQL] +sql.safe_mode = Off + +[ODBC] +;odbc.default_db = Not yet implemented +;odbc.default_user = Not yet implemented +;odbc.default_pw = Not yet implemented + +; Allow or prevent persistent links. +odbc.allow_persistent = On + +; Check that a connection is still valid before reuse. +odbc.check_persistent = On + +; Maximum number of persistent links. -1 means no limit. +odbc.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +odbc.max_links = -1 + +; Handling of LONG fields. Returns number of bytes to variables. 0 means +; passthru. +odbc.defaultlrl = 4096 + +; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char. +; See the documentation on odbc_binmode and odbc_longreadlen for an explanation +; of uodbc.defaultlrl and uodbc.defaultbinmode +odbc.defaultbinmode = 1 + +[MySQL] +; Allow or prevent persistent links. +mysql.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +mysql.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +mysql.max_links = -1 + +; Default port number for mysql_connect(). If unset, mysql_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +mysql.default_port = + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +mysql.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +mysql.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +mysql.default_user = + +; Default password for mysql_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysql.default_password") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +mysql.default_password = + +; Maximum time (in secondes) for connect timeout. -1 means no limit +mysql.connect_timeout = 60 + +; Trace mode. When trace_mode is active (=On), warnings for table/index scans and +; SQL-Errors will be displayed. +mysql.trace_mode = Off + +[MySQLi] + +; Maximum number of links. -1 means no limit. +mysqli.max_links = -1 + +; Default port number for mysqli_connect(). If unset, mysqli_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +mysqli.default_port = 3306 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +mysqli.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +mysqli.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +mysqli.default_user = + +; Default password for mysqli_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +mysqli.default_pw = + +; Allow or prevent reconnect +mysqli.reconnect = Off + +[mSQL] +; Allow or prevent persistent links. +msql.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +msql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +msql.max_links = -1 + +[PostgresSQL] +; Allow or prevent persistent links. +pgsql.allow_persistent = On + +; Detect broken persistent links always with pg_pconnect(). +; Auto reset feature requires a little overheads. +pgsql.auto_reset_persistent = Off + +; Maximum number of persistent links. -1 means no limit. +pgsql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +pgsql.max_links = -1 + +; Ignore PostgreSQL backends Notice message or not. +; Notice message logging require a little overheads. +pgsql.ignore_notice = 0 + +; Log PostgreSQL backends Noitce message or not. +; Unless pgsql.ignore_notice=0, module cannot log notice message. +pgsql.log_notice = 0 + +[Sybase] +; Allow or prevent persistent links. +sybase.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +sybase.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +sybase.max_links = -1 + +;sybase.interface_file = "/usr/sybase/interfaces" + +; Minimum error severity to display. +sybase.min_error_severity = 10 + +; Minimum message severity to display. +sybase.min_message_severity = 10 + +; Compatability mode with old versions of PHP 3.0. +; If on, this will cause PHP to automatically assign types to results according +; to their Sybase type, instead of treating them all as strings. This +; compatability mode will probably not stay around forever, so try applying +; whatever necessary changes to your code, and turn it off. +sybase.compatability_mode = Off + +[Sybase-CT] +; Allow or prevent persistent links. +sybct.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +sybct.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +sybct.max_links = -1 + +; Minimum server message severity to display. +sybct.min_server_severity = 10 + +; Minimum client message severity to display. +sybct.min_client_severity = 10 + +[bcmath] +; Number of decimal digits for all bcmath functions. +bcmath.scale = 0 + +[browscap] +;browscap = extra/browscap.ini + +[Informix] +; Default host for ifx_connect() (doesn't apply in safe mode). +ifx.default_host = + +; Default user for ifx_connect() (doesn't apply in safe mode). +ifx.default_user = + +; Default password for ifx_connect() (doesn't apply in safe mode). +ifx.default_password = + +; Allow or prevent persistent links. +ifx.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +ifx.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +ifx.max_links = -1 + +; If on, select statements return the contents of a text blob instead of its id. +ifx.textasvarchar = 0 + +; If on, select statements return the contents of a byte blob instead of its id. +ifx.byteasvarchar = 0 + +; Trailing blanks are stripped from fixed-length char columns. May help the +; life of Informix SE users. +ifx.charasvarchar = 0 + +; If on, the contents of text and byte blobs are dumped to a file instead of +; keeping them in memory. +ifx.blobinfile = 0 + +; NULL's are returned as empty strings, unless this is set to 1. In that case, +; NULL's are returned as string 'NULL'. +ifx.nullformat = 0 + +[Session] +; Handler used to store/retrieve data. +session.save_handler = files + +; Argument passed to save_handler. In the case of files, this is the path +; where data files are stored. Note: Windows users have to change this +; variable in order to use PHP's session functions. +; +; As of PHP 4.0.1, you can define the path as: +; +; session.save_path = "N;/path" +; +; where N is an integer. Instead of storing all the session files in +; /path, what this will do is use subdirectories N-levels deep, and +; store the session data in those directories. This is useful if you +; or your OS have problems with lots of files in one directory, and is +; a more efficient layout for servers that handle lots of sessions. +; +; NOTE 1: PHP will not create this directory structure automatically. +; You can use the script in the ext/session dir for that purpose. +; NOTE 2: See the section on garbage collection below if you choose to +; use subdirectories for session storage +; +; The file storage module creates files using mode 600 by default. +; You can change that by using +; +; session.save_path = "N;MODE;/path" +; +; where MODE is the octal representation of the mode. Note that this +; does not overwrite the process's umask. +session.save_path = "/var/lib/php/session" + +; Whether to use cookies. +session.use_cookies = 1 + +; This option enables administrators to make their users invulnerable to +; attacks which involve passing session ids in URLs; defaults to 0. +; session.use_only_cookies = 1 + +; Name of the session (used as cookie name). +session.name = PHPSESSID + +; Initialize session on request startup. +session.auto_start = 0 + +; Lifetime in seconds of cookie or, if 0, until browser is restarted. +session.cookie_lifetime = 0 + +; The path for which the cookie is valid. +session.cookie_path = / + +; The domain for which the cookie is valid. +session.cookie_domain = + +; Handler used to serialize data. php is the standard serializer of PHP. +session.serialize_handler = php + +; Define the probability that the 'garbage collection' process is started +; on every session initialization. +; The probability is calculated by using gc_probability/gc_divisor, +; e.g. 1/100 means there is a 1% chance that the GC process starts +; on each request. + +session.gc_probability = 1 +session.gc_divisor = 1000 + +; After this number of seconds, stored data will be seen as 'garbage' and +; cleaned up by the garbage collection process. +session.gc_maxlifetime = 1440 + +; NOTE: If you are using the subdirectory option for storing session files +; (see session.save_path above), then garbage collection does *not* +; happen automatically. You will need to do your own garbage +; collection through a shell script, cron entry, or some other method. +; For example, the following script would is the equivalent of +; setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): +; cd /path/to/sessions; find -cmin +24 | xargs rm + +; PHP 4.2 and less have an undocumented feature/bug that allows you to +; to initialize a session variable in the global scope, albeit register_globals +; is disabled. PHP 4.3 and later will warn you, if this feature is used. +; You can disable the feature and the warning separately. At this time, +; the warning is only displayed, if bug_compat_42 is enabled. + +session.bug_compat_42 = 0 +session.bug_compat_warn = 1 + +; Check HTTP Referer to invalidate externally stored URLs containing ids. +; HTTP_REFERER has to contain this substring for the session to be +; considered as valid. +session.referer_check = + +; How many bytes to read from the file. +session.entropy_length = 0 + +; Specified here to create the session id. +session.entropy_file = + +;session.entropy_length = 16 + +;session.entropy_file = /dev/urandom + +; Set to {nocache,private,public,} to determine HTTP caching aspects +; or leave this empty to avoid sending anti-caching headers. +session.cache_limiter = nocache + +; Document expires after n minutes. +session.cache_expire = 180 + +; trans sid support is disabled by default. +; Use of trans sid may risk your users security. +; Use this option with caution. +; - User may send URL contains active session ID +; to other person via. email/irc/etc. +; - URL that contains active session ID may be stored +; in publically accessible computer. +; - User may access your site with the same session ID +; always using URL stored in browser's history or bookmarks. +session.use_trans_sid = 0 + +; Select a hash function +; 0: MD5 (128 bits) +; 1: SHA-1 (160 bits) +session.hash_function = 0 + +; Define how many bits are stored in each character when converting +; the binary hash data to something readable. +; +; 4 bits: 0-9, a-f +; 5 bits: 0-9, a-v +; 6 bits: 0-9, a-z, A-Z, "-", "," +session.hash_bits_per_character = 5 + +; The URL rewriter will look for URLs in a defined set of HTML tags. +; form/fieldset are special; if you include them here, the rewriter will +; add a hidden field with the info which is otherwise appended +; to URLs. If you want XHTML conformity, remove the form entry. +; Note that all valid entries require a "=", even if no value follows. +url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry" + +[MSSQL] +; Allow or prevent persistent links. +mssql.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +mssql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +mssql.max_links = -1 + +; Minimum error severity to display. +mssql.min_error_severity = 10 + +; Minimum message severity to display. +mssql.min_message_severity = 10 + +; Compatability mode with old versions of PHP 3.0. +mssql.compatability_mode = Off + +; Connect timeout +;mssql.connect_timeout = 5 + +; Query timeout +;mssql.timeout = 60 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textlimit = 4096 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textsize = 4096 + +; Limits the number of records in each batch. 0 = all records in one batch. +;mssql.batchsize = 0 + +; Specify how datetime and datetim4 columns are returned +; On => Returns data converted to SQL server settings +; Off => Returns values as YYYY-MM-DD hh:mm:ss +;mssql.datetimeconvert = On + +; Use NT authentication when connecting to the server +mssql.secure_connection = Off + +; Specify max number of processes. -1 = library default +; msdlib defaults to 25 +; FreeTDS defaults to 4096 +;mssql.max_procs = -1 + +; Specify client character set. +; If empty or not set the client charset from freetds.comf is used +; This is only used when compiled with FreeTDS +;mssql.charset = "ISO-8859-1" + +[Assertion] +; Assert(expr); active by default. +;assert.active = On + +; Issue a PHP warning for each failed assertion. +;assert.warning = On + +; Don't bail out by default. +;assert.bail = Off + +; User-function to be called if an assertion fails. +;assert.callback = 0 + +; Eval the expression with current error_reporting(). Set to true if you want +; error_reporting(0) around the eval(). +;assert.quiet_eval = 0 + +[Verisign Payflow Pro] +; Default Payflow Pro server. +pfpro.defaulthost = "test-payflow.verisign.com" + +; Default port to connect to. +pfpro.defaultport = 443 + +; Default timeout in seconds. +pfpro.defaulttimeout = 30 + +; Default proxy IP address (if required). +;pfpro.proxyaddress = + +; Default proxy port. +;pfpro.proxyport = + +; Default proxy logon. +;pfpro.proxylogon = + +; Default proxy password. +;pfpro.proxypassword = + +[COM] +; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs +;com.typelib_file = +; allow Distributed-COM calls +;com.allow_dcom = true +; autoregister constants of a components typlib on com_load() +;com.autoregister_typelib = true +; register constants casesensitive +;com.autoregister_casesensitive = false +; show warnings on duplicate constat registrations +;com.autoregister_verbose = true + +[mbstring] +; language for internal character representation. +;mbstring.language = Japanese + +; internal/script encoding. +; Some encoding cannot work as internal encoding. +; (e.g. SJIS, BIG5, ISO-2022-*) +;mbstring.internal_encoding = EUC-JP + +; http input encoding. +;mbstring.http_input = auto + +; http output encoding. mb_output_handler must be +; registered as output buffer to function +;mbstring.http_output = SJIS + +; enable automatic encoding translation according to +; mbstring.internal_encoding setting. Input chars are +; converted to internal encoding by setting this to On. +; Note: Do _not_ use automatic encoding translation for +; portable libs/applications. +;mbstring.encoding_translation = Off + +; automatic encoding detection order. +; auto means +;mbstring.detect_order = auto + +; substitute_character used when character cannot be converted +; one from another +;mbstring.substitute_character = none; + +; overload(replace) single byte functions by mbstring functions. +; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(), +; etc. Possible values are 0,1,2,4 or combination of them. +; For example, 7 for overload everything. +; 0: No overload +; 1: Overload mail() function +; 2: Overload str*() functions +; 4: Overload ereg*() functions +;mbstring.func_overload = 0 + +; enable strict encoding detection. +;mbstring.strict_encoding = Off + +[FrontBase] +;fbsql.allow_persistent = On +;fbsql.autocommit = On +;fbsql.default_database = +;fbsql.default_database_password = +;fbsql.default_host = +;fbsql.default_password = +;fbsql.default_user = "_SYSTEM" +;fbsql.generate_warnings = Off +;fbsql.max_connections = 128 +;fbsql.max_links = 128 +;fbsql.max_persistent = -1 +;fbsql.max_results = 128 +;fbsql.batchSize = 1000 + +[gd] +; Tell the jpeg decode to libjpeg warnings and try to create +; a gd image. The warning will then be displayed as notices +; disabled by default +;gd.jpeg_ignore_warning = 0 + +[exif] +; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. +; With mbstring support this will automatically be converted into the encoding +; given by corresponding encode setting. When empty mbstring.internal_encoding +; is used. For the decode settings you can distinguish between motorola and +; intel byte order. A decode setting cannot be empty. +;exif.encode_unicode = ISO-8859-15 +;exif.decode_unicode_motorola = UCS-2BE +;exif.decode_unicode_intel = UCS-2LE +;exif.encode_jis = +;exif.decode_jis_motorola = JIS +;exif.decode_jis_intel = JIS + +[Tidy] +; The path to a default tidy configuration file to use when using tidy +;tidy.default_config = /usr/local/lib/php/default.tcfg + +; Should tidy clean and repair output automatically? +; WARNING: Do not use this option if you are generating non-html content +; such as dynamic images +tidy.clean_output = Off + +[soap] +; Enables or disables WSDL caching feature. +soap.wsdl_cache_enabled=1 +; Sets the directory name where SOAP extension will put cache files. +soap.wsdl_cache_dir="/tmp" +; (time to live) Sets the number of second while cached file will be used +; instead of original one. +soap.wsdl_cache_ttl=86400 + +; Local Variables: +; tab-width: 4 +; End: + +<% @directives.sort_by { |key, val| key }.each do |directive, value| -%> +<%= "#{directive}=\"#{value}\"" %> +<% end -%> diff --git a/cookbooks/php/templates/ubuntu/php.ini.erb b/cookbooks/php/templates/ubuntu/php.ini.erb new file mode 100644 index 0000000..1bd0a47 --- /dev/null +++ b/cookbooks/php/templates/ubuntu/php.ini.erb @@ -0,0 +1,1857 @@ +[PHP] + +;;;;;;;;;;;;;;;;;;; +; About php.ini ; +;;;;;;;;;;;;;;;;;;; +; PHP's initialization file, generally called php.ini, is responsible for +; configuring many of the aspects of PHP's behavior. + +; PHP attempts to find and load this configuration from a number of locations. +; The following is a summary of its search order: +; 1. SAPI module specific location. +; 2. The PHPRC environment variable. (As of PHP 5.2.0) +; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0) +; 4. Current working directory (except CLI) +; 5. The web server's directory (for SAPI modules), or directory of PHP +; (otherwise in Windows) +; 6. The directory from the --with-config-file-path compile time option, or the +; Windows directory (C:\windows or C:\winnt) +; See the PHP docs for more specific information. +; http://php.net/configuration.file + +; The syntax of the file is extremely simple. Whitespace and Lines +; beginning with a semicolon are silently ignored (as you probably guessed). +; Section headers (e.g. [Foo]) are also silently ignored, even though +; they might mean something in the future. + +; Directives following the section heading [PATH=/www/mysite] only +; apply to PHP files in the /www/mysite directory. Directives +; following the section heading [HOST=www.example.com] only apply to +; PHP files served from www.example.com. Directives set in these +; special sections cannot be overridden by user-defined INI files or +; at runtime. Currently, [PATH=] and [HOST=] sections only work under +; CGI/FastCGI. +; http://php.net/ini.sections + +; Directives are specified using the following syntax: +; directive = value +; Directive names are *case sensitive* - foo=bar is different from FOO=bar. +; Directives are variables used to configure PHP or PHP extensions. +; There is no name validation. If PHP can't find an expected +; directive because it is not set or is mistyped, a default value will be used. + +; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one +; of the INI constants (On, Off, True, False, Yes, No and None) or an expression +; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a +; previously set variable or directive (e.g. ${foo}) + +; Expressions in the INI file are limited to bitwise operators and parentheses: +; | bitwise OR +; ^ bitwise XOR +; & bitwise AND +; ~ bitwise NOT +; ! boolean NOT + +; Boolean flags can be turned on using the values 1, On, True or Yes. +; They can be turned off using the values 0, Off, False or No. + +; An empty string can be denoted by simply not writing anything after the equal +; sign, or by using the None keyword: + +; foo = ; sets foo to an empty string +; foo = None ; sets foo to an empty string +; foo = "None" ; sets foo to the string 'None' + +; If you use constants in your value, and these constants belong to a +; dynamically loaded extension (either a PHP extension or a Zend extension), +; you may only use these constants *after* the line that loads the extension. + +;;;;;;;;;;;;;;;;;;; +; About this file ; +;;;;;;;;;;;;;;;;;;; +; PHP comes packaged with two INI files. One that is recommended to be used +; in production environments and one that is recommended to be used in +; development environments. + +; php.ini-production contains settings which hold security, performance and +; best practices at its core. But please be aware, these settings may break +; compatibility with older or less security conscience applications. We +; recommending using the production ini in production and testing environments. + +; php.ini-development is very similar to its production variant, except it's +; much more verbose when it comes to errors. We recommending using the +; development version only in development environments as errors shown to +; application users can inadvertently leak otherwise secure information. + +;;;;;;;;;;;;;;;;;;; +; Quick Reference ; +;;;;;;;;;;;;;;;;;;; +; The following are all the settings which are different in either the production +; or development versions of the INIs with respect to PHP's default behavior. +; Please see the actual settings later in the document for more details as to why +; we recommend these changes in PHP's behavior. + +; allow_call_time_pass_reference +; Default Value: On +; Development Value: Off +; Production Value: Off + +; display_errors +; Default Value: On +; Development Value: On +; Production Value: Off + +; display_startup_errors +; Default Value: Off +; Development Value: On +; Production Value: Off + +; error_reporting +; Default Value: E_ALL & ~E_NOTICE +; Development Value: E_ALL | E_STRICT +; Production Value: E_ALL & ~E_DEPRECATED + +; html_errors +; Default Value: On +; Development Value: On +; Production value: Off + +; log_errors +; Default Value: Off +; Development Value: On +; Production Value: On + +; magic_quotes_gpc +; Default Value: On +; Development Value: Off +; Production Value: Off + +; max_input_time +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) + +; output_buffering +; Default Value: Off +; Development Value: 4096 +; Production Value: 4096 + +; register_argc_argv +; Default Value: On +; Development Value: Off +; Production Value: Off + +; register_long_arrays +; Default Value: On +; Development Value: Off +; Production Value: Off + +; request_order +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" + +; session.bug_compat_42 +; Default Value: On +; Development Value: On +; Production Value: Off + +; session.bug_compat_warn +; Default Value: On +; Development Value: On +; Production Value: Off + +; session.gc_divisor +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 + +; session.hash_bits_per_character +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 + +; short_open_tag +; Default Value: On +; Development Value: Off +; Production Value: Off + +; track_errors +; Default Value: Off +; Development Value: On +; Production Value: Off + +; url_rewriter.tags +; Default Value: "a=href,area=href,frame=src,form=,fieldset=" +; Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry" + +; variables_order +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS" + +;;;;;;;;;;;;;;;;;;;; +; php.ini Options ; +;;;;;;;;;;;;;;;;;;;; +; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini" +;user_ini.filename = ".user.ini" + +; To disable this feature set this option to empty value +;user_ini.filename = + +; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes) +;user_ini.cache_ttl = 300 + +;;;;;;;;;;;;;;;;;;;; +; Language Options ; +;;;;;;;;;;;;;;;;;;;; + +; Enable the PHP scripting language engine under Apache. +; http://php.net/engine +engine = On + +; This directive determines whether or not PHP will recognize code between +; tags as PHP source which should be processed as such. It's been +; recommended for several years that you not use the short tag "short cut" and +; instead to use the full tag combination. With the wide spread use +; of XML and use of these tags by other languages, the server can become easily +; confused and end up parsing the wrong code in the wrong context. But because +; this short cut has been a feature for such a long time, it's currently still +; supported for backwards compatibility, but we recommend you don't use them. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/short-open-tag +short_open_tag = On + +; Allow ASP-style <% %> tags. +; http://php.net/asp-tags +asp_tags = Off + +; The number of significant digits displayed in floating point numbers. +; http://php.net/precision +precision = 14 + +; Enforce year 2000 compliance (will cause problems with non-compliant browsers) +; http://php.net/y2k-compliance +y2k_compliance = On + +; Output buffering is a mechanism for controlling how much output data +; (excluding headers and cookies) PHP should keep internally before pushing that +; data to the client. If your application's output exceeds this setting, PHP +; will send that data in chunks of roughly the size you specify. +; Turning on this setting and managing its maximum buffer size can yield some +; interesting side-effects depending on your application and web server. +; You may be able to send headers and cookies after you've already sent output +; through print or echo. You also may see performance benefits if your server is +; emitting less packets due to buffered output versus PHP streaming the output +; as it gets it. On production servers, 4096 bytes is a good setting for performance +; reasons. +; Note: Output buffering can also be controlled via Output Buffering Control +; functions. +; Possible Values: +; On = Enabled and buffer is unlimited. (Use with caution) +; Off = Disabled +; Integer = Enables the buffer and sets its maximum size in bytes. +; Note: This directive is hardcoded to Off for the CLI SAPI +; Default Value: Off +; Development Value: 4096 +; Production Value: 4096 +; http://php.net/output-buffering +output_buffering = 4096 + +; You can redirect all of the output of your scripts to a function. For +; example, if you set output_handler to "mb_output_handler", character +; encoding will be transparently converted to the specified encoding. +; Setting any output handler automatically turns on output buffering. +; Note: People who wrote portable scripts should not depend on this ini +; directive. Instead, explicitly set the output handler using ob_start(). +; Using this ini directive may cause problems unless you know what script +; is doing. +; Note: You cannot use both "mb_output_handler" with "ob_iconv_handler" +; and you cannot use both "ob_gzhandler" and "zlib.output_compression". +; Note: output_handler must be empty if this is set 'On' !!!! +; Instead you must use zlib.output_handler. +; http://php.net/output-handler +;output_handler = + +; Transparent output compression using the zlib library +; Valid values for this option are 'off', 'on', or a specific buffer size +; to be used for compression (default is 4KB) +; Note: Resulting chunk size may vary due to nature of compression. PHP +; outputs chunks that are few hundreds bytes each as a result of +; compression. If you prefer a larger chunk size for better +; performance, enable output_buffering in addition. +; Note: You need to use zlib.output_handler instead of the standard +; output_handler, or otherwise the output will be corrupted. +; http://php.net/zlib.output-compression +zlib.output_compression = Off + +; http://php.net/zlib.output-compression-level +;zlib.output_compression_level = -1 + +; You cannot specify additional output handlers if zlib.output_compression +; is activated here. This setting does the same as output_handler but in +; a different order. +; http://php.net/zlib.output-handler +;zlib.output_handler = + +; Implicit flush tells PHP to tell the output layer to flush itself +; automatically after every output block. This is equivalent to calling the +; PHP function flush() after each and every call to print() or echo() and each +; and every HTML block. Turning this option on has serious performance +; implications and is generally recommended for debugging purposes only. +; http://php.net/implicit-flush +; Note: This directive is hardcoded to On for the CLI SAPI +implicit_flush = Off + +; The unserialize callback function will be called (with the undefined class' +; name as parameter), if the unserializer finds an undefined class +; which should be instantiated. A warning appears if the specified function is +; not defined, or if the function doesn't include/implement the missing class. +; So only set this entry, if you really want to implement such a +; callback-function. +unserialize_callback_func = + +; When floats & doubles are serialized store serialize_precision significant +; digits after the floating point. The default value ensures that when floats +; are decoded with unserialize, the data will remain the same. +serialize_precision = 100 + +; This directive allows you to enable and disable warnings which PHP will issue +; if you pass a value by reference at function call time. Passing values by +; reference at function call time is a deprecated feature which will be removed +; from PHP at some point in the near future. The acceptable method for passing a +; value by reference to a function is by declaring the reference in the functions +; definition, not at call time. This directive does not disable this feature, it +; only determines whether PHP will warn you about it or not. These warnings +; should enabled in development environments only. +; Default Value: On (Suppress warnings) +; Development Value: Off (Issue warnings) +; Production Value: Off (Issue warnings) +; http://php.net/allow-call-time-pass-reference +allow_call_time_pass_reference = Off + +; Safe Mode +; http://php.net/safe-mode +safe_mode = Off + +; By default, Safe Mode does a UID compare check when +; opening files. If you want to relax this to a GID compare, +; then turn on safe_mode_gid. +; http://php.net/safe-mode-gid +safe_mode_gid = Off + +; When safe_mode is on, UID/GID checks are bypassed when +; including files from this directory and its subdirectories. +; (directory must also be in include_path or full path must +; be used when including) +; http://php.net/safe-mode-include-dir +safe_mode_include_dir = + +; When safe_mode is on, only executables located in the safe_mode_exec_dir +; will be allowed to be executed via the exec family of functions. +; http://php.net/safe-mode-exec-dir +safe_mode_exec_dir = + +; Setting certain environment variables may be a potential security breach. +; This directive contains a comma-delimited list of prefixes. In Safe Mode, +; the user may only alter environment variables whose names begin with the +; prefixes supplied here. By default, users will only be able to set +; environment variables that begin with PHP_ (e.g. PHP_FOO=BAR). +; Note: If this directive is empty, PHP will let the user modify ANY +; environment variable! +; http://php.net/safe-mode-allowed-env-vars +safe_mode_allowed_env_vars = PHP_ + +; This directive contains a comma-delimited list of environment variables that +; the end user won't be able to change using putenv(). These variables will be +; protected even if safe_mode_allowed_env_vars is set to allow to change them. +; http://php.net/safe-mode-protected-env-vars +safe_mode_protected_env_vars = LD_LIBRARY_PATH + +; open_basedir, if set, limits all file operations to the defined directory +; and below. This directive makes most sense if used in a per-directory +; or per-virtualhost web server configuration file. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/open-basedir +;open_basedir = + +; This directive allows you to disable certain functions for security reasons. +; It receives a comma-delimited list of function names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/disable-functions +disable_functions = + +; This directive allows you to disable certain classes for security reasons. +; It receives a comma-delimited list of class names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/disable-classes +disable_classes = + +; Colors for Syntax Highlighting mode. Anything that's acceptable in +; would work. +; http://php.net/syntax-highlighting +;highlight.string = #DD0000 +;highlight.comment = #FF9900 +;highlight.keyword = #007700 +;highlight.bg = #FFFFFF +;highlight.default = #0000BB +;highlight.html = #000000 + +; If enabled, the request will be allowed to complete even if the user aborts +; the request. Consider enabling it if executing long requests, which may end up +; being interrupted by the user or a browser timing out. PHP's default behavior +; is to disable this feature. +; http://php.net/ignore-user-abort +;ignore_user_abort = On + +; Determines the size of the realpath cache to be used by PHP. This value should +; be increased on systems where PHP opens many files to reflect the quantity of +; the file operations performed. +; http://php.net/realpath-cache-size +;realpath_cache_size = 16k + +; Duration of time, in seconds for which to cache realpath information for a given +; file or directory. For systems with rarely changing files, consider increasing this +; value. +; http://php.net/realpath-cache-ttl +;realpath_cache_ttl = 120 + +;;;;;;;;;;;;;;;;; +; Miscellaneous ; +;;;;;;;;;;;;;;;;; + +; Decides whether PHP may expose the fact that it is installed on the server +; (e.g. by adding its signature to the Web server header). It is no security +; threat in any way, but it makes it possible to determine whether you use PHP +; on your server or not. +; http://php.net/expose-php +expose_php = On + +;;;;;;;;;;;;;;;;;;; +; Resource Limits ; +;;;;;;;;;;;;;;;;;;; + +; Maximum execution time of each script, in seconds +; http://php.net/max-execution-time +; Note: This directive is hardcoded to 0 for the CLI SAPI +max_execution_time = 30 + +; Maximum amount of time each script may spend parsing request data. It's a good +; idea to limit this time on productions servers in order to eliminate unexpectedly +; long running scripts. +; Note: This directive is hardcoded to -1 for the CLI SAPI +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) +; http://php.net/max-input-time +max_input_time = 60 + +; Maximum input variable nesting level +; http://php.net/max-input-nesting-level +;max_input_nesting_level = 64 + +; Maximum amount of memory a script may consume (128MB) +; http://php.net/memory-limit +memory_limit = -1 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Error handling and logging ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; This directive informs PHP of which errors, warnings and notices you would like +; it to take action for. The recommended way of setting values for this +; directive is through the use of the error level constants and bitwise +; operators. The error level constants are below here for convenience as well as +; some common settings and their meanings. +; By default, PHP is set to take action on all errors, notices and warnings EXCEPT +; those related to E_NOTICE and E_STRICT, which together cover best practices and +; recommended coding standards in PHP. For performance reasons, this is the +; recommend error reporting setting. Your production server shouldn't be wasting +; resources complaining about best practices and coding standards. That's what +; development servers and development settings are for. +; Note: The php.ini-development file has this setting as E_ALL | E_STRICT. This +; means it pretty much reports everything which is exactly what you want during +; development and early testing. +; +; Error Level Constants: +; E_ALL - All errors and warnings (includes E_STRICT as of PHP 6.0.0) +; E_ERROR - fatal run-time errors +; E_RECOVERABLE_ERROR - almost fatal run-time errors +; E_WARNING - run-time warnings (non-fatal errors) +; E_PARSE - compile-time parse errors +; E_NOTICE - run-time notices (these are warnings which often result +; from a bug in your code, but it's possible that it was +; intentional (e.g., using an uninitialized variable and +; relying on the fact it's automatically initialized to an +; empty string) +; E_STRICT - run-time notices, enable to have PHP suggest changes +; to your code which will ensure the best interoperability +; and forward compatibility of your code +; E_CORE_ERROR - fatal errors that occur during PHP's initial startup +; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's +; initial startup +; E_COMPILE_ERROR - fatal compile-time errors +; E_COMPILE_WARNING - compile-time warnings (non-fatal errors) +; E_USER_ERROR - user-generated error message +; E_USER_WARNING - user-generated warning message +; E_USER_NOTICE - user-generated notice message +; E_DEPRECATED - warn about code that will not work in future versions +; of PHP +; E_USER_DEPRECATED - user-generated deprecation warnings +; +; Common Values: +; E_ALL & ~E_NOTICE (Show all errors, except for notices and coding standards warnings.) +; E_ALL & ~E_NOTICE | E_STRICT (Show all errors, except for notices) +; E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR (Show only errors) +; E_ALL | E_STRICT (Show all errors, warnings and notices including coding standards.) +; Default Value: E_ALL & ~E_NOTICE +; Development Value: E_ALL | E_STRICT +; Production Value: E_ALL & ~E_DEPRECATED +; http://php.net/error-reporting +error_reporting = E_ALL & ~E_DEPRECATED + +; This directive controls whether or not and where PHP will output errors, +; notices and warnings too. Error output is very useful during development, but +; it could be very dangerous in production environments. Depending on the code +; which is triggering the error, sensitive information could potentially leak +; out of your application such as database usernames and passwords or worse. +; It's recommended that errors be logged on production servers rather than +; having the errors sent to STDOUT. +; Possible Values: +; Off = Do not display any errors +; stderr = Display errors to STDERR (affects only CGI/CLI binaries!) +; On or stdout = Display errors to STDOUT +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/display-errors +display_errors = Off + +; The display of errors which occur during PHP's startup sequence are handled +; separately from display_errors. PHP's default behavior is to suppress those +; errors from clients. Turning the display of startup errors on can be useful in +; debugging configuration problems. But, it's strongly recommended that you +; leave this setting off on production servers. +; Default Value: Off +; Development Value: On +; Production Value: Off +; http://php.net/display-startup-errors +display_startup_errors = Off + +; Besides displaying errors, PHP can also log errors to locations such as a +; server-specific log, STDERR, or a location specified by the error_log +; directive found below. While errors should not be displayed on productions +; servers they should still be monitored and logging is a great way to do that. +; Default Value: Off +; Development Value: On +; Production Value: On +; http://php.net/log-errors +log_errors = On + +; Set maximum length of log_errors. In error_log information about the source is +; added. The default is 1024 and 0 allows to not apply any maximum length at all. +; http://php.net/log-errors-max-len +log_errors_max_len = 1024 + +; Do not log repeated messages. Repeated errors must occur in same file on same +; line unless ignore_repeated_source is set true. +; http://php.net/ignore-repeated-errors +ignore_repeated_errors = Off + +; Ignore source of message when ignoring repeated messages. When this setting +; is On you will not log errors with repeated messages from different files or +; source lines. +; http://php.net/ignore-repeated-source +ignore_repeated_source = Off + +; If this parameter is set to Off, then memory leaks will not be shown (on +; stdout or in the log). This has only effect in a debug compile, and if +; error reporting includes E_WARNING in the allowed list +; http://php.net/report-memleaks +report_memleaks = On + +; This setting is on by default. +;report_zend_debug = 0 + +; Store the last error/warning message in $php_errormsg (boolean). Setting this value +; to On can assist in debugging and is appropriate for development servers. It should +; however be disabled on production servers. +; Default Value: Off +; Development Value: On +; Production Value: Off +; http://php.net/track-errors +track_errors = Off + +; Turn off normal error reporting and emit XML-RPC error XML +; http://php.net/xmlrpc-errors +;xmlrpc_errors = 0 + +; An XML-RPC faultCode +;xmlrpc_error_number = 0 + +; When PHP displays or logs an error, it has the capability of inserting html +; links to documentation related to that error. This directive controls whether +; those HTML links appear in error messages or not. For performance and security +; reasons, it's recommended you disable this on production servers. +; Note: This directive is hardcoded to Off for the CLI SAPI +; Default Value: On +; Development Value: On +; Production value: Off +; http://php.net/html-errors +html_errors = Off + +; If html_errors is set On PHP produces clickable error messages that direct +; to a page describing the error or function causing the error in detail. +; You can download a copy of the PHP manual from http://php.net/docs +; and change docref_root to the base URL of your local copy including the +; leading '/'. You must also specify the file extension being used including +; the dot. PHP's default behavior is to leave these settings empty. +; Note: Never use this feature for production boxes. +; http://php.net/docref-root +; Examples +;docref_root = "/phpmanual/" + +; http://php.net/docref-ext +;docref_ext = .html + +; String to output before an error message. PHP's default behavior is to leave +; this setting blank. +; http://php.net/error-prepend-string +; Example: +;error_prepend_string = "" + +; String to output after an error message. PHP's default behavior is to leave +; this setting blank. +; http://php.net/error-append-string +; Example: +;error_append_string = "" + +; Log errors to specified file. PHP's default behavior is to leave this value +; empty. +; http://php.net/error-log +; Example: +;error_log = php_errors.log +; Log errors to syslog (Event Log on NT, not valid in Windows 95). +;error_log = syslog + +;;;;;;;;;;;;;;;;; +; Data Handling ; +;;;;;;;;;;;;;;;;; + +; The separator used in PHP generated URLs to separate arguments. +; PHP's default setting is "&". +; http://php.net/arg-separator.output +; Example: +;arg_separator.output = "&" + +; List of separator(s) used by PHP to parse input URLs into variables. +; PHP's default setting is "&". +; NOTE: Every character in this directive is considered as separator! +; http://php.net/arg-separator.input +; Example: +;arg_separator.input = ";&" + +; This directive determines which super global arrays are registered when PHP +; starts up. If the register_globals directive is enabled, it also determines +; what order variables are populated into the global space. G,P,C,E & S are +; abbreviations for the following respective super globals: GET, POST, COOKIE, +; ENV and SERVER. There is a performance penalty paid for the registration of +; these arrays and because ENV is not as commonly used as the others, ENV is +; is not recommended on productions servers. You can still get access to +; the environment variables through getenv() should you need to. +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS"; +; http://php.net/variables-order +variables_order = "GPCS" + +; This directive determines which super global data (G,P,C,E & S) should +; be registered into the super global array REQUEST. If so, it also determines +; the order in which that data is registered. The values for this directive are +; specified in the same manner as the variables_order directive, EXCEPT one. +; Leaving this value empty will cause PHP to use the value set in the +; variables_order directive. It does not mean it will leave the super globals +; array REQUEST empty. +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" +; http://php.net/request-order +request_order = "GP" + +; Whether or not to register the EGPCS variables as global variables. You may +; want to turn this off if you don't want to clutter your scripts' global scope +; with user data. +; You should do your best to write your scripts so that they do not require +; register_globals to be on; Using form variables as globals can easily lead +; to possible security problems, if the code is not very well thought of. +; http://php.net/register-globals +register_globals = Off + +; Determines whether the deprecated long $HTTP_*_VARS type predefined variables +; are registered by PHP or not. As they are deprecated, we obviously don't +; recommend you use them. They are on by default for compatibility reasons but +; they are not recommended on production servers. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/register-long-arrays +register_long_arrays = Off + +; This directive determines whether PHP registers $argv & $argc each time it +; runs. $argv contains an array of all the arguments passed to PHP when a script +; is invoked. $argc contains an integer representing the number of arguments +; that were passed when the script was invoked. These arrays are extremely +; useful when running scripts from the command line. When this directive is +; enabled, registering these variables consumes CPU cycles and memory each time +; a script is executed. For performance reasons, this feature should be disabled +; on production servers. +; Note: This directive is hardcoded to On for the CLI SAPI +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/register-argc-argv +register_argc_argv = Off + +; When enabled, the SERVER and ENV variables are created when they're first +; used (Just In Time) instead of when the script starts. If these variables +; are not used within a script, having this directive on will result in a +; performance gain. The PHP directives register_globals, register_long_arrays, +; and register_argc_argv must be disabled for this directive to have any affect. +; http://php.net/auto-globals-jit +auto_globals_jit = On + +; Maximum size of POST data that PHP will accept. +; http://php.net/post-max-size +post_max_size = 8M + +; Magic quotes are a preprocessing feature of PHP where PHP will attempt to +; escape any character sequences in GET, POST, COOKIE and ENV data which might +; otherwise corrupt data being placed in resources such as databases before +; making that data available to you. Because of character encoding issues and +; non-standard SQL implementations across many databases, it's not currently +; possible for this feature to be 100% accurate. PHP's default behavior is to +; enable the feature. We strongly recommend you use the escaping mechanisms +; designed specifically for the database your using instead of relying on this +; feature. Also note, this feature has been deprecated as of PHP 5.3.0 and is +; scheduled for removal in PHP 6. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/magic-quotes-gpc +magic_quotes_gpc = Off + +; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc. +; http://php.net/magic-quotes-runtime +magic_quotes_runtime = Off + +; Use Sybase-style magic quotes (escape ' with '' instead of \'). +; http://php.net/magic-quotes-sybase +magic_quotes_sybase = Off + +; Automatically add files before PHP document. +; http://php.net/auto-prepend-file +auto_prepend_file = + +; Automatically add files after PHP document. +; http://php.net/auto-append-file +auto_append_file = + +; By default, PHP will output a character encoding using +; the Content-type: header. To disable sending of the charset, simply +; set it to be empty. +; +; PHP's built-in default is text/html +; http://php.net/default-mimetype +default_mimetype = "text/html" + +; PHP's default character set is set to empty. +; http://php.net/default-charset +;default_charset = "iso-8859-1" + +; Always populate the $HTTP_RAW_POST_DATA variable. PHP's default behavior is +; to disable this feature. +; http://php.net/always-populate-raw-post-data +;always_populate_raw_post_data = On + +;;;;;;;;;;;;;;;;;;;;;;;;; +; Paths and Directories ; +;;;;;;;;;;;;;;;;;;;;;;;;; + +; UNIX: "/path1:/path2" +;include_path = ".:/usr/share/php" +; +; Windows: "\path1;\path2" +;include_path = ".;c:\php\includes" +; +; PHP's default setting for include_path is ".;/path/to/php/pear" +; http://php.net/include-path + +; The root of the PHP pages, used only if nonempty. +; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root +; if you are running php as a CGI under any web server (other than IIS) +; see documentation for security issues. The alternate is to use the +; cgi.force_redirect configuration below +; http://php.net/doc-root +doc_root = + +; The directory under which PHP opens the script using /~username used only +; if nonempty. +; http://php.net/user-dir +user_dir = + +; Directory in which the loadable extensions (modules) reside. +; http://php.net/extension-dir +; extension_dir = "./" +; On windows: +; extension_dir = "ext" + +; Whether or not to enable the dl() function. The dl() function does NOT work +; properly in multithreaded servers, such as IIS or Zeus, and is automatically +; disabled on them. +; http://php.net/enable-dl +enable_dl = Off + +; cgi.force_redirect is necessary to provide security running PHP as a CGI under +; most web servers. Left undefined, PHP turns this on by default. You can +; turn it off here AT YOUR OWN RISK +; **You CAN safely turn this off for IIS, in fact, you MUST.** +; http://php.net/cgi.force-redirect +;cgi.force_redirect = 1 + +; if cgi.nph is enabled it will force cgi to always sent Status: 200 with +; every request. PHP's default behavior is to disable this feature. +;cgi.nph = 1 + +; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape +; (iPlanet) web servers, you MAY need to set an environment variable name that PHP +; will look for to know it is OK to continue execution. Setting this variable MAY +; cause security issues, KNOW WHAT YOU ARE DOING FIRST. +; http://php.net/cgi.redirect-status-env +;cgi.redirect_status_env = ; + +; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's +; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok +; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting +; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting +; of zero causes PHP to behave as before. Default is 1. You should fix your scripts +; to use SCRIPT_FILENAME rather than PATH_TRANSLATED. +; http://php.net/cgi.fix-pathinfo +;cgi.fix_pathinfo=1 + +; FastCGI under IIS (on WINNT based OS) supports the ability to impersonate +; security tokens of the calling client. This allows IIS to define the +; security context that the request runs under. mod_fastcgi under Apache +; does not currently support this feature (03/17/2002) +; Set to 1 if running under IIS. Default is zero. +; http://php.net/fastcgi.impersonate +;fastcgi.impersonate = 1; + +; Disable logging through FastCGI connection. PHP's default behavior is to enable +; this feature. +;fastcgi.logging = 0 + +; cgi.rfc2616_headers configuration option tells PHP what type of headers to +; use when sending HTTP response code. If it's set 0 PHP sends Status: header that +; is supported by Apache. When this option is set to 1 PHP will send +; RFC2616 compliant header. +; Default is zero. +; http://php.net/cgi.rfc2616-headers +;cgi.rfc2616_headers = 0 + +;;;;;;;;;;;;;;;; +; File Uploads ; +;;;;;;;;;;;;;;;; + +; Whether to allow HTTP file uploads. +; http://php.net/file-uploads +file_uploads = On + +; Temporary directory for HTTP uploaded files (will use system default if not +; specified). +; http://php.net/upload-tmp-dir +;upload_tmp_dir = + +; Maximum allowed size for uploaded files. +; http://php.net/upload-max-filesize +upload_max_filesize = 2M + +; Maximum number of files that can be uploaded via a single request +max_file_uploads = 20 + +;;;;;;;;;;;;;;;;;; +; Fopen wrappers ; +;;;;;;;;;;;;;;;;;; + +; Whether to allow the treatment of URLs (like http:// or ftp://) as files. +; http://php.net/allow-url-fopen +allow_url_fopen = On + +; Whether to allow include/require to open URLs (like http:// or ftp://) as files. +; http://php.net/allow-url-include +allow_url_include = Off + +; Define the anonymous ftp password (your email address). PHP's default setting +; for this is empty. +; http://php.net/from +;from="john@doe.com" + +; Define the User-Agent string. PHP's default setting for this is empty. +; http://php.net/user-agent +;user_agent="PHP" + +; Default timeout for socket based streams (seconds) +; http://php.net/default-socket-timeout +default_socket_timeout = 60 + +; If your scripts have to deal with files from Macintosh systems, +; or you are running on a Mac and need to deal with files from +; unix or win32 systems, setting this flag will cause PHP to +; automatically detect the EOL character in those files so that +; fgets() and file() will work regardless of the source of the file. +; http://php.net/auto-detect-line-endings +;auto_detect_line_endings = Off + +;;;;;;;;;;;;;;;;;;;;;; +; Dynamic Extensions ; +;;;;;;;;;;;;;;;;;;;;;; + +; If you wish to have an extension loaded automatically, use the following +; syntax: +; +; extension=modulename.extension +; +; For example, on Windows: +; +; extension=msql.dll +; +; ... or under UNIX: +; +; extension=msql.so +; +; ... or with a path: +; +; extension=/path/to/extension/msql.so +; +; If you only provide the name of the extension, PHP will look for it in its +; default extension directory. +; + +;;;;;;;;;;;;;;;;;;; +; Module Settings ; +;;;;;;;;;;;;;;;;;;; + +[Date] +; Defines the default timezone used by the date functions +; http://php.net/date.timezone +;date.timezone = + +; http://php.net/date.default-latitude +;date.default_latitude = 31.7667 + +; http://php.net/date.default-longitude +;date.default_longitude = 35.2333 + +; http://php.net/date.sunrise-zenith +;date.sunrise_zenith = 90.583333 + +; http://php.net/date.sunset-zenith +;date.sunset_zenith = 90.583333 + +[filter] +; http://php.net/filter.default +;filter.default = unsafe_raw + +; http://php.net/filter.default-flags +;filter.default_flags = + +[iconv] +;iconv.input_encoding = ISO-8859-1 +;iconv.internal_encoding = ISO-8859-1 +;iconv.output_encoding = ISO-8859-1 + +[intl] +;intl.default_locale = +; This directive allows you to produce PHP errors when some error +; happens within intl functions. The value is the level of the error produced. +; Default is 0, which does not produce any errors. +;intl.error_level = E_WARNING + +[sqlite] +; http://php.net/sqlite.assoc-case +;sqlite.assoc_case = 0 + +[sqlite3] +;sqlite3.extension_dir = + +[Pcre] +;PCRE library backtracking limit. +; http://php.net/pcre.backtrack-limit +;pcre.backtrack_limit=100000 + +;PCRE library recursion limit. +;Please note that if you set this value to a high number you may consume all +;the available process stack and eventually crash PHP (due to reaching the +;stack size limit imposed by the Operating System). +; http://php.net/pcre.recursion-limit +;pcre.recursion_limit=100000 + +[Pdo] +; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off" +; http://php.net/pdo-odbc.connection-pooling +;pdo_odbc.connection_pooling=strict + +;pdo_odbc.db2_instance_name + +[Pdo_mysql] +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/pdo_mysql.cache_size +pdo_mysql.cache_size = 2000 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/pdo_mysql.default-socket +pdo_mysql.default_socket= + +[Phar] +; http://php.net/phar.readonly +;phar.readonly = On + +; http://php.net/phar.require-hash +;phar.require_hash = On + +;phar.cache_list = + +[Syslog] +; Whether or not to define the various syslog variables (e.g. $LOG_PID, +; $LOG_CRON, etc.). Turning it off is a good idea performance-wise. In +; runtime, you can define these variables by calling define_syslog_variables(). +; http://php.net/define-syslog-variables +define_syslog_variables = Off + +[mail function] +; For Win32 only. +; http://php.net/smtp +SMTP = localhost +; http://php.net/smtp-port +smtp_port = 25 + +; For Win32 only. +; http://php.net/sendmail-from +;sendmail_from = me@example.com + +; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). +; http://php.net/sendmail-path +;sendmail_path = + +; Force the addition of the specified parameters to be passed as extra parameters +; to the sendmail binary. These parameters will always replace the value of +; the 5th parameter to mail(), even in safe mode. +;mail.force_extra_parameters = + +; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename +mail.add_x_header = On + +; Log all mail() calls including the full path of the script, line #, to address and headers +;mail.log = + +[SQL] +; http://php.net/sql.safe-mode +sql.safe_mode = Off + +[ODBC] +; http://php.net/odbc.default-db +;odbc.default_db = Not yet implemented + +; http://php.net/odbc.default-user +;odbc.default_user = Not yet implemented + +; http://php.net/odbc.default-pw +;odbc.default_pw = Not yet implemented + +; Controls the ODBC cursor model. +; Default: SQL_CURSOR_STATIC (default). +;odbc.default_cursortype + +; Allow or prevent persistent links. +; http://php.net/odbc.allow-persistent +odbc.allow_persistent = On + +; Check that a connection is still valid before reuse. +; http://php.net/odbc.check-persistent +odbc.check_persistent = On + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/odbc.max-persistent +odbc.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/odbc.max-links +odbc.max_links = -1 + +; Handling of LONG fields. Returns number of bytes to variables. 0 means +; passthru. +; http://php.net/odbc.defaultlrl +odbc.defaultlrl = 4096 + +; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char. +; See the documentation on odbc_binmode and odbc_longreadlen for an explanation +; of odbc.defaultlrl and odbc.defaultbinmode +; http://php.net/odbc.defaultbinmode +odbc.defaultbinmode = 1 + +;birdstep.max_links = -1 + +[Interbase] +; Allow or prevent persistent links. +ibase.allow_persistent = 1 + +; Maximum number of persistent links. -1 means no limit. +ibase.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +ibase.max_links = -1 + +; Default database name for ibase_connect(). +;ibase.default_db = + +; Default username for ibase_connect(). +;ibase.default_user = + +; Default password for ibase_connect(). +;ibase.default_password = + +; Default charset for ibase_connect(). +;ibase.default_charset = + +; Default timestamp format. +ibase.timestampformat = "%Y-%m-%d %H:%M:%S" + +; Default date format. +ibase.dateformat = "%Y-%m-%d" + +; Default time format. +ibase.timeformat = "%H:%M:%S" + +[MySQL] +; Allow accessing, from PHP's perspective, local files with LOAD DATA statements +; http://php.net/mysql.allow_local_infile +mysql.allow_local_infile = On + +; Allow or prevent persistent links. +; http://php.net/mysql.allow-persistent +mysql.allow_persistent = On + +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/mysql.cache_size +mysql.cache_size = 2000 + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/mysql.max-persistent +mysql.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/mysql.max-links +mysql.max_links = -1 + +; Default port number for mysql_connect(). If unset, mysql_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +; http://php.net/mysql.default-port +mysql.default_port = + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/mysql.default-socket +mysql.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysql.default-host +mysql.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysql.default-user +mysql.default_user = + +; Default password for mysql_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysql.default_password") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +; http://php.net/mysql.default-password +mysql.default_password = + +; Maximum time (in seconds) for connect timeout. -1 means no limit +; http://php.net/mysql.connect-timeout +mysql.connect_timeout = 60 + +; Trace mode. When trace_mode is active (=On), warnings for table/index scans and +; SQL-Errors will be displayed. +; http://php.net/mysql.trace-mode +mysql.trace_mode = Off + +[MySQLi] + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/mysqli.max-persistent +mysqli.max_persistent = -1 + +; Allow accessing, from PHP's perspective, local files with LOAD DATA statements +; http://php.net/mysqli.allow_local_infile +;mysqli.allow_local_infile = On + +; Allow or prevent persistent links. +; http://php.net/mysqli.allow-persistent +mysqli.allow_persistent = On + +; Maximum number of links. -1 means no limit. +; http://php.net/mysqli.max-links +mysqli.max_links = -1 + +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/mysqli.cache_size +mysqli.cache_size = 2000 + +; Default port number for mysqli_connect(). If unset, mysqli_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +; http://php.net/mysqli.default-port +mysqli.default_port = 3306 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/mysqli.default-socket +mysqli.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysqli.default-host +mysqli.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysqli.default-user +mysqli.default_user = + +; Default password for mysqli_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +; http://php.net/mysqli.default-pw +mysqli.default_pw = + +; Allow or prevent reconnect +mysqli.reconnect = Off + +[mysqlnd] +; Enable / Disable collection of general statstics by mysqlnd which can be +; used to tune and monitor MySQL operations. +; http://php.net/mysqlnd.collect_statistics +mysqlnd.collect_statistics = On + +; Enable / Disable collection of memory usage statstics by mysqlnd which can be +; used to tune and monitor MySQL operations. +; http://php.net/mysqlnd.collect_memory_statistics +mysqlnd.collect_memory_statistics = Off + +; Size of a pre-allocated buffer used when sending commands to MySQL in bytes. +; http://php.net/mysqlnd.net_cmd_buffer_size +;mysqlnd.net_cmd_buffer_size = 2048 + +; Size of a pre-allocated buffer used for reading data sent by the server in +; bytes. +; http://php.net/mysqlnd.net_read_buffer_size +;mysqlnd.net_read_buffer_size = 32768 + +[OCI8] + +; Connection: Enables privileged connections using external +; credentials (OCI_SYSOPER, OCI_SYSDBA) +; http://php.net/oci8.privileged-connect +;oci8.privileged_connect = Off + +; Connection: The maximum number of persistent OCI8 connections per +; process. Using -1 means no limit. +; http://php.net/oci8.max-persistent +;oci8.max_persistent = -1 + +; Connection: The maximum number of seconds a process is allowed to +; maintain an idle persistent connection. Using -1 means idle +; persistent connections will be maintained forever. +; http://php.net/oci8.persistent-timeout +;oci8.persistent_timeout = -1 + +; Connection: The number of seconds that must pass before issuing a +; ping during oci_pconnect() to check the connection validity. When +; set to 0, each oci_pconnect() will cause a ping. Using -1 disables +; pings completely. +; http://php.net/oci8.ping-interval +;oci8.ping_interval = 60 + +; Connection: Set this to a user chosen connection class to be used +; for all pooled server requests with Oracle 11g Database Resident +; Connection Pooling (DRCP). To use DRCP, this value should be set to +; the same string for all web servers running the same application, +; the database pool must be configured, and the connection string must +; specify to use a pooled server. +;oci8.connection_class = + +; High Availability: Using On lets PHP receive Fast Application +; Notification (FAN) events generated when a database node fails. The +; database must also be configured to post FAN events. +;oci8.events = Off + +; Tuning: This option enables statement caching, and specifies how +; many statements to cache. Using 0 disables statement caching. +; http://php.net/oci8.statement-cache-size +;oci8.statement_cache_size = 20 + +; Tuning: Enables statement prefetching and sets the default number of +; rows that will be fetched automatically after statement execution. +; http://php.net/oci8.default-prefetch +;oci8.default_prefetch = 100 + +; Compatibility. Using On means oci_close() will not close +; oci_connect() and oci_new_connect() connections. +; http://php.net/oci8.old-oci-close-semantics +;oci8.old_oci_close_semantics = Off + +[PostgresSQL] +; Allow or prevent persistent links. +; http://php.net/pgsql.allow-persistent +pgsql.allow_persistent = On + +; Detect broken persistent links always with pg_pconnect(). +; Auto reset feature requires a little overheads. +; http://php.net/pgsql.auto-reset-persistent +pgsql.auto_reset_persistent = Off + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/pgsql.max-persistent +pgsql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +; http://php.net/pgsql.max-links +pgsql.max_links = -1 + +; Ignore PostgreSQL backends Notice message or not. +; Notice message logging require a little overheads. +; http://php.net/pgsql.ignore-notice +pgsql.ignore_notice = 0 + +; Log PostgreSQL backends Noitce message or not. +; Unless pgsql.ignore_notice=0, module cannot log notice message. +; http://php.net/pgsql.log-notice +pgsql.log_notice = 0 + +[Sybase-CT] +; Allow or prevent persistent links. +; http://php.net/sybct.allow-persistent +sybct.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/sybct.max-persistent +sybct.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/sybct.max-links +sybct.max_links = -1 + +; Minimum server message severity to display. +; http://php.net/sybct.min-server-severity +sybct.min_server_severity = 10 + +; Minimum client message severity to display. +; http://php.net/sybct.min-client-severity +sybct.min_client_severity = 10 + +; Set per-context timeout +; http://php.net/sybct.timeout +;sybct.timeout= + +;sybct.packet_size + +; The maximum time in seconds to wait for a connection attempt to succeed before returning failure. +; Default: one minute +;sybct.login_timeout= + +; The name of the host you claim to be connecting from, for display by sp_who. +; Default: none +;sybct.hostname= + +; Allows you to define how often deadlocks are to be retried. -1 means "forever". +; Default: 0 +;sybct.deadlock_retry_count= + +[bcmath] +; Number of decimal digits for all bcmath functions. +; http://php.net/bcmath.scale +bcmath.scale = 0 + +[browscap] +; http://php.net/browscap +;browscap = extra/browscap.ini + +[Session] +; Handler used to store/retrieve data. +; http://php.net/session.save-handler +session.save_handler = files + +; Argument passed to save_handler. In the case of files, this is the path +; where data files are stored. Note: Windows users have to change this +; variable in order to use PHP's session functions. +; +; The path can be defined as: +; +; session.save_path = "N;/path" +; +; where N is an integer. Instead of storing all the session files in +; /path, what this will do is use subdirectories N-levels deep, and +; store the session data in those directories. This is useful if you +; or your OS have problems with lots of files in one directory, and is +; a more efficient layout for servers that handle lots of sessions. +; +; NOTE 1: PHP will not create this directory structure automatically. +; You can use the script in the ext/session dir for that purpose. +; NOTE 2: See the section on garbage collection below if you choose to +; use subdirectories for session storage +; +; The file storage module creates files using mode 600 by default. +; You can change that by using +; +; session.save_path = "N;MODE;/path" +; +; where MODE is the octal representation of the mode. Note that this +; does not overwrite the process's umask. +; http://php.net/session.save-path +;session.save_path = "/tmp" + +; Whether to use cookies. +; http://php.net/session.use-cookies +session.use_cookies = 1 + +; http://php.net/session.cookie-secure +;session.cookie_secure = + +; This option forces PHP to fetch and use a cookie for storing and maintaining +; the session id. We encourage this operation as it's very helpful in combatting +; session hijacking when not specifying and managing your own session id. It is +; not the end all be all of session hijacking defense, but it's a good start. +; http://php.net/session.use-only-cookies +session.use_only_cookies = 1 + +; Name of the session (used as cookie name). +; http://php.net/session.name +session.name = PHPSESSID + +; Initialize session on request startup. +; http://php.net/session.auto-start +session.auto_start = 0 + +; Lifetime in seconds of cookie or, if 0, until browser is restarted. +; http://php.net/session.cookie-lifetime +session.cookie_lifetime = 0 + +; The path for which the cookie is valid. +; http://php.net/session.cookie-path +session.cookie_path = / + +; The domain for which the cookie is valid. +; http://php.net/session.cookie-domain +session.cookie_domain = + +; Whether or not to add the httpOnly flag to the cookie, which makes it inaccessible to browser scripting languages such as JavaScript. +; http://php.net/session.cookie-httponly +session.cookie_httponly = + +; Handler used to serialize data. php is the standard serializer of PHP. +; http://php.net/session.serialize-handler +session.serialize_handler = php + +; Defines the probability that the 'garbage collection' process is started +; on every session initialization. The probability is calculated by using +; gc_probability/gc_divisor. Where session.gc_probability is the numerator +; and gc_divisor is the denominator in the equation. Setting this value to 1 +; when the session.gc_divisor value is 100 will give you approximately a 1% chance +; the gc will run on any give request. +; Default Value: 1 +; Development Value: 1 +; Production Value: 1 +; http://php.net/session.gc-probability +session.gc_probability = 1 + +; Defines the probability that the 'garbage collection' process is started on every +; session initialization. The probability is calculated by using the following equation: +; gc_probability/gc_divisor. Where session.gc_probability is the numerator and +; session.gc_divisor is the denominator in the equation. Setting this value to 1 +; when the session.gc_divisor value is 100 will give you approximately a 1% chance +; the gc will run on any give request. Increasing this value to 1000 will give you +; a 0.1% chance the gc will run on any give request. For high volume production servers, +; this is a more efficient approach. +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 +; http://php.net/session.gc-divisor +session.gc_divisor = 1000 + +; After this number of seconds, stored data will be seen as 'garbage' and +; cleaned up by the garbage collection process. +; http://php.net/session.gc-maxlifetime +session.gc_maxlifetime = 1440 + +; NOTE: If you are using the subdirectory option for storing session files +; (see session.save_path above), then garbage collection does *not* +; happen automatically. You will need to do your own garbage +; collection through a shell script, cron entry, or some other method. +; For example, the following script would is the equivalent of +; setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): +; cd /path/to/sessions; find -cmin +24 | xargs rm + +; PHP 4.2 and less have an undocumented feature/bug that allows you to +; to initialize a session variable in the global scope, even when register_globals +; is disabled. PHP 4.3 and later will warn you, if this feature is used. +; You can disable the feature and the warning separately. At this time, +; the warning is only displayed, if bug_compat_42 is enabled. This feature +; introduces some serious security problems if not handled correctly. It's +; recommended that you do not use this feature on production servers. But you +; should enable this on development servers and enable the warning as well. If you +; do not enable the feature on development servers, you won't be warned when it's +; used and debugging errors caused by this can be difficult to track down. +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/session.bug-compat-42 +session.bug_compat_42 = Off + +; This setting controls whether or not you are warned by PHP when initializing a +; session value into the global space. session.bug_compat_42 must be enabled before +; these warnings can be issued by PHP. See the directive above for more information. +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/session.bug-compat-warn +session.bug_compat_warn = Off + +; Check HTTP Referer to invalidate externally stored URLs containing ids. +; HTTP_REFERER has to contain this substring for the session to be +; considered as valid. +; http://php.net/session.referer-check +session.referer_check = + +; How many bytes to read from the file. +; http://php.net/session.entropy-length +session.entropy_length = 0 + +; Specified here to create the session id. +; http://php.net/session.entropy-file +;session.entropy_file = /dev/urandom +session.entropy_file = + +; http://php.net/session.entropy-length +;session.entropy_length = 16 + +; Set to {nocache,private,public,} to determine HTTP caching aspects +; or leave this empty to avoid sending anti-caching headers. +; http://php.net/session.cache-limiter +session.cache_limiter = nocache + +; Document expires after n minutes. +; http://php.net/session.cache-expire +session.cache_expire = 180 + +; trans sid support is disabled by default. +; Use of trans sid may risk your users security. +; Use this option with caution. +; - User may send URL contains active session ID +; to other person via. email/irc/etc. +; - URL that contains active session ID may be stored +; in publically accessible computer. +; - User may access your site with the same session ID +; always using URL stored in browser's history or bookmarks. +; http://php.net/session.use-trans-sid +session.use_trans_sid = 0 + +; Select a hash function for use in generating session ids. +; Possible Values +; 0 (MD5 128 bits) +; 1 (SHA-1 160 bits) +; This option may also be set to the name of any hash function supported by +; the hash extension. A list of available hashes is returned by the hash_alogs() +; function. +; http://php.net/session.hash-function +session.hash_function = 0 + +; Define how many bits are stored in each character when converting +; the binary hash data to something readable. +; Possible values: +; 4 (4 bits: 0-9, a-f) +; 5 (5 bits: 0-9, a-v) +; 6 (6 bits: 0-9, a-z, A-Z, "-", ",") +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 +; http://php.net/session.hash-bits-per-character +session.hash_bits_per_character = 5 + +; The URL rewriter will look for URLs in a defined set of HTML tags. +; form/fieldset are special; if you include them here, the rewriter will +; add a hidden field with the info which is otherwise appended +; to URLs. If you want XHTML conformity, remove the form entry. +; Note that all valid entries require a "=", even if no value follows. +; Default Value: "a=href,area=href,frame=src,form=,fieldset=" +; Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; http://php.net/url-rewriter.tags +url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry" + +[MSSQL] +; Allow or prevent persistent links. +mssql.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +mssql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +mssql.max_links = -1 + +; Minimum error severity to display. +mssql.min_error_severity = 10 + +; Minimum message severity to display. +mssql.min_message_severity = 10 + +; Compatibility mode with old versions of PHP 3.0. +mssql.compatability_mode = Off + +; Connect timeout +;mssql.connect_timeout = 5 + +; Query timeout +;mssql.timeout = 60 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textlimit = 4096 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textsize = 4096 + +; Limits the number of records in each batch. 0 = all records in one batch. +;mssql.batchsize = 0 + +; Specify how datetime and datetim4 columns are returned +; On => Returns data converted to SQL server settings +; Off => Returns values as YYYY-MM-DD hh:mm:ss +;mssql.datetimeconvert = On + +; Use NT authentication when connecting to the server +mssql.secure_connection = Off + +; Specify max number of processes. -1 = library default +; msdlib defaults to 25 +; FreeTDS defaults to 4096 +;mssql.max_procs = -1 + +; Specify client character set. +; If empty or not set the client charset from freetds.comf is used +; This is only used when compiled with FreeTDS +;mssql.charset = "ISO-8859-1" + +[Assertion] +; Assert(expr); active by default. +; http://php.net/assert.active +;assert.active = On + +; Issue a PHP warning for each failed assertion. +; http://php.net/assert.warning +;assert.warning = On + +; Don't bail out by default. +; http://php.net/assert.bail +;assert.bail = Off + +; User-function to be called if an assertion fails. +; http://php.net/assert.callback +;assert.callback = 0 + +; Eval the expression with current error_reporting(). Set to true if you want +; error_reporting(0) around the eval(). +; http://php.net/assert.quiet-eval +;assert.quiet_eval = 0 + +[COM] +; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs +; http://php.net/com.typelib-file +;com.typelib_file = + +; allow Distributed-COM calls +; http://php.net/com.allow-dcom +;com.allow_dcom = true + +; autoregister constants of a components typlib on com_load() +; http://php.net/com.autoregister-typelib +;com.autoregister_typelib = true + +; register constants casesensitive +; http://php.net/com.autoregister-casesensitive +;com.autoregister_casesensitive = false + +; show warnings on duplicate constant registrations +; http://php.net/com.autoregister-verbose +;com.autoregister_verbose = true + +; The default character set code-page to use when passing strings to and from COM objects. +; Default: system ANSI code page +;com.code_page= + +[mbstring] +; language for internal character representation. +; http://php.net/mbstring.language +;mbstring.language = Japanese + +; internal/script encoding. +; Some encoding cannot work as internal encoding. +; (e.g. SJIS, BIG5, ISO-2022-*) +; http://php.net/mbstring.internal-encoding +;mbstring.internal_encoding = EUC-JP + +; http input encoding. +; http://php.net/mbstring.http-input +;mbstring.http_input = auto + +; http output encoding. mb_output_handler must be +; registered as output buffer to function +; http://php.net/mbstring.http-output +;mbstring.http_output = SJIS + +; enable automatic encoding translation according to +; mbstring.internal_encoding setting. Input chars are +; converted to internal encoding by setting this to On. +; Note: Do _not_ use automatic encoding translation for +; portable libs/applications. +; http://php.net/mbstring.encoding-translation +;mbstring.encoding_translation = Off + +; automatic encoding detection order. +; auto means +; http://php.net/mbstring.detect-order +;mbstring.detect_order = auto + +; substitute_character used when character cannot be converted +; one from another +; http://php.net/mbstring.substitute-character +;mbstring.substitute_character = none; + +; overload(replace) single byte functions by mbstring functions. +; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(), +; etc. Possible values are 0,1,2,4 or combination of them. +; For example, 7 for overload everything. +; 0: No overload +; 1: Overload mail() function +; 2: Overload str*() functions +; 4: Overload ereg*() functions +; http://php.net/mbstring.func-overload +;mbstring.func_overload = 0 + +; enable strict encoding detection. +;mbstring.strict_detection = Off + +; This directive specifies the regex pattern of content types for which mb_output_handler() +; is activated. +; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml) +;mbstring.http_output_conv_mimetype= + +; Allows to set script encoding. Only affects if PHP is compiled with --enable-zend-multibyte +; Default: "" +;mbstring.script_encoding= + +[gd] +; Tell the jpeg decode to ignore warnings and try to create +; a gd image. The warning will then be displayed as notices +; disabled by default +; http://php.net/gd.jpeg-ignore-warning +;gd.jpeg_ignore_warning = 0 + +[exif] +; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. +; With mbstring support this will automatically be converted into the encoding +; given by corresponding encode setting. When empty mbstring.internal_encoding +; is used. For the decode settings you can distinguish between motorola and +; intel byte order. A decode setting cannot be empty. +; http://php.net/exif.encode-unicode +;exif.encode_unicode = ISO-8859-15 + +; http://php.net/exif.decode-unicode-motorola +;exif.decode_unicode_motorola = UCS-2BE + +; http://php.net/exif.decode-unicode-intel +;exif.decode_unicode_intel = UCS-2LE + +; http://php.net/exif.encode-jis +;exif.encode_jis = + +; http://php.net/exif.decode-jis-motorola +;exif.decode_jis_motorola = JIS + +; http://php.net/exif.decode-jis-intel +;exif.decode_jis_intel = JIS + +[Tidy] +; The path to a default tidy configuration file to use when using tidy +; http://php.net/tidy.default-config +;tidy.default_config = /usr/local/lib/php/default.tcfg + +; Should tidy clean and repair output automatically? +; WARNING: Do not use this option if you are generating non-html content +; such as dynamic images +; http://php.net/tidy.clean-output +tidy.clean_output = Off + +[soap] +; Enables or disables WSDL caching feature. +; http://php.net/soap.wsdl-cache-enabled +soap.wsdl_cache_enabled=1 + +; Sets the directory name where SOAP extension will put cache files. +; http://php.net/soap.wsdl-cache-dir +soap.wsdl_cache_dir="/tmp" + +; (time to live) Sets the number of second while cached file will be used +; instead of original one. +; http://php.net/soap.wsdl-cache-ttl +soap.wsdl_cache_ttl=86400 + +; Sets the size of the cache limit. (Max. number of WSDL files to cache) +soap.wsdl_cache_limit = 5 + +[sysvshm] +; A default size of the shared memory segment +;sysvshm.init_mem = 10000 + +[ldap] +; Sets the maximum number of open links or -1 for unlimited. +ldap.max_links = -1 + +[mcrypt] +; For more information about mcrypt settings see http://php.net/mcrypt-module-open + +; Directory where to load mcrypt algorithms +; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt) +;mcrypt.algorithms_dir= + +; Directory where to load mcrypt modes +; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt) +;mcrypt.modes_dir= + +[dba] +;dba.default_handler= + +; Local Variables: +; tab-width: 4 +; End: + +<% @directives.sort_by { |key, val| key }.each do |directive, value| -%> +<%= "#{directive}=\"#{value}\"" %> +<% end -%> diff --git a/cookbooks/php/templates/windows/pear-options.erb b/cookbooks/php/templates/windows/pear-options.erb new file mode 100644 index 0000000..7f9405c --- /dev/null +++ b/cookbooks/php/templates/windows/pear-options.erb @@ -0,0 +1,18 @@ + +all + + + + + + + + + + + +<%= node['php']['conf_dir'].gsub("/", "\\") %> + +n + + diff --git a/cookbooks/php/templates/windows/php.ini.erb b/cookbooks/php/templates/windows/php.ini.erb new file mode 100644 index 0000000..e492f09 --- /dev/null +++ b/cookbooks/php/templates/windows/php.ini.erb @@ -0,0 +1,1935 @@ +[PHP] + +;;;;;;;;;;;;;;;;;;; +; About php.ini ; +;;;;;;;;;;;;;;;;;;; +; PHP's initialization file, generally called php.ini, is responsible for +; configuring many of the aspects of PHP's behavior. + +; PHP attempts to find and load this configuration from a number of locations. +; The following is a summary of its search order: +; 1. SAPI module specific location. +; 2. The PHPRC environment variable. (As of PHP 5.2.0) +; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0) +; 4. Current working directory (except CLI) +; 5. The web server's directory (for SAPI modules), or directory of PHP +; (otherwise in Windows) +; 6. The directory from the --with-config-file-path compile time option, or the +; Windows directory (C:\windows or C:\winnt) +; See the PHP docs for more specific information. +; http://php.net/configuration.file + +; The syntax of the file is extremely simple. Whitespace and lines +; beginning with a semicolon are silently ignored (as you probably guessed). +; Section headers (e.g. [Foo]) are also silently ignored, even though +; they might mean something in the future. + +; Directives following the section heading [PATH=/www/mysite] only +; apply to PHP files in the /www/mysite directory. Directives +; following the section heading [HOST=www.example.com] only apply to +; PHP files served from www.example.com. Directives set in these +; special sections cannot be overridden by user-defined INI files or +; at runtime. Currently, [PATH=] and [HOST=] sections only work under +; CGI/FastCGI. +; http://php.net/ini.sections + +; Directives are specified using the following syntax: +; directive = value +; Directive names are *case sensitive* - foo=bar is different from FOO=bar. +; Directives are variables used to configure PHP or PHP extensions. +; There is no name validation. If PHP can't find an expected +; directive because it is not set or is mistyped, a default value will be used. + +; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one +; of the INI constants (On, Off, True, False, Yes, No and None) or an expression +; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a +; previously set variable or directive (e.g. ${foo}) + +; Expressions in the INI file are limited to bitwise operators and parentheses: +; | bitwise OR +; ^ bitwise XOR +; & bitwise AND +; ~ bitwise NOT +; ! boolean NOT + +; Boolean flags can be turned on using the values 1, On, True or Yes. +; They can be turned off using the values 0, Off, False or No. + +; An empty string can be denoted by simply not writing anything after the equal +; sign, or by using the None keyword: + +; foo = ; sets foo to an empty string +; foo = None ; sets foo to an empty string +; foo = "None" ; sets foo to the string 'None' + +; If you use constants in your value, and these constants belong to a +; dynamically loaded extension (either a PHP extension or a Zend extension), +; you may only use these constants *after* the line that loads the extension. + +;;;;;;;;;;;;;;;;;;; +; About this file ; +;;;;;;;;;;;;;;;;;;; +; PHP comes packaged with two INI files. One that is recommended to be used +; in production environments and one that is recommended to be used in +; development environments. + +; php.ini-production contains settings which hold security, performance and +; best practices at its core. But please be aware, these settings may break +; compatibility with older or less security conscience applications. We +; recommending using the production ini in production and testing environments. + +; php.ini-development is very similar to its production variant, except it's +; much more verbose when it comes to errors. We recommending using the +; development version only in development environments as errors shown to +; application users can inadvertently leak otherwise secure information. + +;;;;;;;;;;;;;;;;;;; +; Quick Reference ; +;;;;;;;;;;;;;;;;;;; +; The following are all the settings which are different in either the production +; or development versions of the INIs with respect to PHP's default behavior. +; Please see the actual settings later in the document for more details as to why +; we recommend these changes in PHP's behavior. + +; allow_call_time_pass_reference +; Default Value: On +; Development Value: Off +; Production Value: Off + +; display_errors +; Default Value: On +; Development Value: On +; Production Value: Off + +; display_startup_errors +; Default Value: Off +; Development Value: On +; Production Value: Off + +; error_reporting +; Default Value: E_ALL & ~E_NOTICE +; Development Value: E_ALL | E_STRICT +; Production Value: E_ALL & ~E_DEPRECATED + +; html_errors +; Default Value: On +; Development Value: On +; Production value: Off + +; log_errors +; Default Value: Off +; Development Value: On +; Production Value: On + +; magic_quotes_gpc +; Default Value: On +; Development Value: Off +; Production Value: Off + +; max_input_time +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) + +; output_buffering +; Default Value: Off +; Development Value: 4096 +; Production Value: 4096 + +; register_argc_argv +; Default Value: On +; Development Value: Off +; Production Value: Off + +; register_long_arrays +; Default Value: On +; Development Value: Off +; Production Value: Off + +; request_order +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" + +; session.bug_compat_42 +; Default Value: On +; Development Value: On +; Production Value: Off + +; session.bug_compat_warn +; Default Value: On +; Development Value: On +; Production Value: Off + +; session.gc_divisor +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 + +; session.hash_bits_per_character +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 + +; short_open_tag +; Default Value: On +; Development Value: Off +; Production Value: Off + +; track_errors +; Default Value: Off +; Development Value: On +; Production Value: Off + +; url_rewriter.tags +; Default Value: "a=href,area=href,frame=src,form=,fieldset=" +; Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry" + +; variables_order +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS" + +;;;;;;;;;;;;;;;;;;;; +; php.ini Options ; +;;;;;;;;;;;;;;;;;;;; +; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini" +;user_ini.filename = ".user.ini" + +; To disable this feature set this option to empty value +;user_ini.filename = + +; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes) +;user_ini.cache_ttl = 300 + +;;;;;;;;;;;;;;;;;;;; +; Language Options ; +;;;;;;;;;;;;;;;;;;;; + +; Enable the PHP scripting language engine under Apache. +; http://php.net/engine +engine = On + +; This directive determines whether or not PHP will recognize code between +; tags as PHP source which should be processed as such. It's been +; recommended for several years that you not use the short tag "short cut" and +; instead to use the full tag combination. With the wide spread use +; of XML and use of these tags by other languages, the server can become easily +; confused and end up parsing the wrong code in the wrong context. But because +; this short cut has been a feature for such a long time, it's currently still +; supported for backwards compatibility, but we recommend you don't use them. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/short-open-tag +short_open_tag = Off + +; Allow ASP-style <%% %> tags. +; http://php.net/asp-tags +asp_tags = Off + +; The number of significant digits displayed in floating point numbers. +; http://php.net/precision +precision = 14 + +; Enforce year 2000 compliance (will cause problems with non-compliant browsers) +; http://php.net/y2k-compliance +y2k_compliance = On + +; Output buffering is a mechanism for controlling how much output data +; (excluding headers and cookies) PHP should keep internally before pushing that +; data to the client. If your application's output exceeds this setting, PHP +; will send that data in chunks of roughly the size you specify. +; Turning on this setting and managing its maximum buffer size can yield some +; interesting side-effects depending on your application and web server. +; You may be able to send headers and cookies after you've already sent output +; through print or echo. You also may see performance benefits if your server is +; emitting less packets due to buffered output versus PHP streaming the output +; as it gets it. On production servers, 4096 bytes is a good setting for performance +; reasons. +; Note: Output buffering can also be controlled via Output Buffering Control +; functions. +; Possible Values: +; On = Enabled and buffer is unlimited. (Use with caution) +; Off = Disabled +; Integer = Enables the buffer and sets its maximum size in bytes. +; Note: This directive is hardcoded to Off for the CLI SAPI +; Default Value: Off +; Development Value: 4096 +; Production Value: 4096 +; http://php.net/output-buffering +output_buffering = 4096 + +; You can redirect all of the output of your scripts to a function. For +; example, if you set output_handler to "mb_output_handler", character +; encoding will be transparently converted to the specified encoding. +; Setting any output handler automatically turns on output buffering. +; Note: People who wrote portable scripts should not depend on this ini +; directive. Instead, explicitly set the output handler using ob_start(). +; Using this ini directive may cause problems unless you know what script +; is doing. +; Note: You cannot use both "mb_output_handler" with "ob_iconv_handler" +; and you cannot use both "ob_gzhandler" and "zlib.output_compression". +; Note: output_handler must be empty if this is set 'On' !!!! +; Instead you must use zlib.output_handler. +; http://php.net/output-handler +;output_handler = + +; Transparent output compression using the zlib library +; Valid values for this option are 'off', 'on', or a specific buffer size +; to be used for compression (default is 4KB) +; Note: Resulting chunk size may vary due to nature of compression. PHP +; outputs chunks that are few hundreds bytes each as a result of +; compression. If you prefer a larger chunk size for better +; performance, enable output_buffering in addition. +; Note: You need to use zlib.output_handler instead of the standard +; output_handler, or otherwise the output will be corrupted. +; http://php.net/zlib.output-compression +zlib.output_compression = Off + +; http://php.net/zlib.output-compression-level +;zlib.output_compression_level = -1 + +; You cannot specify additional output handlers if zlib.output_compression +; is activated here. This setting does the same as output_handler but in +; a different order. +; http://php.net/zlib.output-handler +;zlib.output_handler = + +; Implicit flush tells PHP to tell the output layer to flush itself +; automatically after every output block. This is equivalent to calling the +; PHP function flush() after each and every call to print() or echo() and each +; and every HTML block. Turning this option on has serious performance +; implications and is generally recommended for debugging purposes only. +; http://php.net/implicit-flush +; Note: This directive is hardcoded to On for the CLI SAPI +implicit_flush = Off + +; The unserialize callback function will be called (with the undefined class' +; name as parameter), if the unserializer finds an undefined class +; which should be instantiated. A warning appears if the specified function is +; not defined, or if the function doesn't include/implement the missing class. +; So only set this entry, if you really want to implement such a +; callback-function. +unserialize_callback_func = + +; When floats & doubles are serialized store serialize_precision significant +; digits after the floating point. The default value ensures that when floats +; are decoded with unserialize, the data will remain the same. +serialize_precision = 17 + +; This directive allows you to enable and disable warnings which PHP will issue +; if you pass a value by reference at function call time. Passing values by +; reference at function call time is a deprecated feature which will be removed +; from PHP at some point in the near future. The acceptable method for passing a +; value by reference to a function is by declaring the reference in the functions +; definition, not at call time. This directive does not disable this feature, it +; only determines whether PHP will warn you about it or not. These warnings +; should enabled in development environments only. +; Default Value: On (Suppress warnings) +; Development Value: Off (Issue warnings) +; Production Value: Off (Issue warnings) +; http://php.net/allow-call-time-pass-reference +allow_call_time_pass_reference = Off + +; Safe Mode +; http://php.net/safe-mode +safe_mode = Off + +; By default, Safe Mode does a UID compare check when +; opening files. If you want to relax this to a GID compare, +; then turn on safe_mode_gid. +; http://php.net/safe-mode-gid +safe_mode_gid = Off + +; When safe_mode is on, UID/GID checks are bypassed when +; including files from this directory and its subdirectories. +; (directory must also be in include_path or full path must +; be used when including) +; http://php.net/safe-mode-include-dir +safe_mode_include_dir = + +; When safe_mode is on, only executables located in the safe_mode_exec_dir +; will be allowed to be executed via the exec family of functions. +; http://php.net/safe-mode-exec-dir +safe_mode_exec_dir = + +; Setting certain environment variables may be a potential security breach. +; This directive contains a comma-delimited list of prefixes. In Safe Mode, +; the user may only alter environment variables whose names begin with the +; prefixes supplied here. By default, users will only be able to set +; environment variables that begin with PHP_ (e.g. PHP_FOO=BAR). +; Note: If this directive is empty, PHP will let the user modify ANY +; environment variable! +; http://php.net/safe-mode-allowed-env-vars +safe_mode_allowed_env_vars = PHP_ + +; This directive contains a comma-delimited list of environment variables that +; the end user won't be able to change using putenv(). These variables will be +; protected even if safe_mode_allowed_env_vars is set to allow to change them. +; http://php.net/safe-mode-protected-env-vars +safe_mode_protected_env_vars = LD_LIBRARY_PATH + +; open_basedir, if set, limits all file operations to the defined directory +; and below. This directive makes most sense if used in a per-directory +; or per-virtualhost web server configuration file. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/open-basedir +;open_basedir = + +; This directive allows you to disable certain functions for security reasons. +; It receives a comma-delimited list of function names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/disable-functions +disable_functions = + +; This directive allows you to disable certain classes for security reasons. +; It receives a comma-delimited list of class names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/disable-classes +disable_classes = + +; Colors for Syntax Highlighting mode. Anything that's acceptable in +; would work. +; http://php.net/syntax-highlighting +;highlight.string = #DD0000 +;highlight.comment = #FF9900 +;highlight.keyword = #007700 +;highlight.bg = #FFFFFF +;highlight.default = #0000BB +;highlight.html = #000000 + +; If enabled, the request will be allowed to complete even if the user aborts +; the request. Consider enabling it if executing long requests, which may end up +; being interrupted by the user or a browser timing out. PHP's default behavior +; is to disable this feature. +; http://php.net/ignore-user-abort +;ignore_user_abort = On + +; Determines the size of the realpath cache to be used by PHP. This value should +; be increased on systems where PHP opens many files to reflect the quantity of +; the file operations performed. +; http://php.net/realpath-cache-size +;realpath_cache_size = 16k + +; Duration of time, in seconds for which to cache realpath information for a given +; file or directory. For systems with rarely changing files, consider increasing this +; value. +; http://php.net/realpath-cache-ttl +;realpath_cache_ttl = 120 + +; Enables or disables the circular reference collector. +; http://php.net/zend.enable-gc +zend.enable_gc = On + +;;;;;;;;;;;;;;;;; +; Miscellaneous ; +;;;;;;;;;;;;;;;;; + +; Decides whether PHP may expose the fact that it is installed on the server +; (e.g. by adding its signature to the Web server header). It is no security +; threat in any way, but it makes it possible to determine whether you use PHP +; on your server or not. +; http://php.net/expose-php +expose_php = On + +;;;;;;;;;;;;;;;;;;; +; Resource Limits ; +;;;;;;;;;;;;;;;;;;; + +; Maximum execution time of each script, in seconds +; http://php.net/max-execution-time +; Note: This directive is hardcoded to 0 for the CLI SAPI +max_execution_time =300 + +; Maximum amount of time each script may spend parsing request data. It's a good +; idea to limit this time on productions servers in order to eliminate unexpectedly +; long running scripts. +; Note: This directive is hardcoded to -1 for the CLI SAPI +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) +; http://php.net/max-input-time +max_input_time = 60 + +; Maximum input variable nesting level +; http://php.net/max-input-nesting-level +;max_input_nesting_level = 64 + +; How many GET/POST/COOKIE input variables may be accepted +; max_input_vars = 1000 + +; Maximum amount of memory a script may consume (128MB) +; http://php.net/memory-limit +memory_limit = 128M + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Error handling and logging ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; This directive informs PHP of which errors, warnings and notices you would like +; it to take action for. The recommended way of setting values for this +; directive is through the use of the error level constants and bitwise +; operators. The error level constants are below here for convenience as well as +; some common settings and their meanings. +; By default, PHP is set to take action on all errors, notices and warnings EXCEPT +; those related to E_NOTICE and E_STRICT, which together cover best practices and +; recommended coding standards in PHP. For performance reasons, this is the +; recommend error reporting setting. Your production server shouldn't be wasting +; resources complaining about best practices and coding standards. That's what +; development servers and development settings are for. +; Note: The php.ini-development file has this setting as E_ALL | E_STRICT. This +; means it pretty much reports everything which is exactly what you want during +; development and early testing. +; +; Error Level Constants: +; E_ALL - All errors and warnings (includes E_STRICT as of PHP 5.4.0) +; E_ERROR - fatal run-time errors +; E_RECOVERABLE_ERROR - almost fatal run-time errors +; E_WARNING - run-time warnings (non-fatal errors) +; E_PARSE - compile-time parse errors +; E_NOTICE - run-time notices (these are warnings which often result +; from a bug in your code, but it's possible that it was +; intentional (e.g., using an uninitialized variable and +; relying on the fact it's automatically initialized to an +; empty string) +; E_STRICT - run-time notices, enable to have PHP suggest changes +; to your code which will ensure the best interoperability +; and forward compatibility of your code +; E_CORE_ERROR - fatal errors that occur during PHP's initial startup +; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's +; initial startup +; E_COMPILE_ERROR - fatal compile-time errors +; E_COMPILE_WARNING - compile-time warnings (non-fatal errors) +; E_USER_ERROR - user-generated error message +; E_USER_WARNING - user-generated warning message +; E_USER_NOTICE - user-generated notice message +; E_DEPRECATED - warn about code that will not work in future versions +; of PHP +; E_USER_DEPRECATED - user-generated deprecation warnings +; +; Common Values: +; E_ALL & ~E_NOTICE (Show all errors, except for notices and coding standards warnings.) +; E_ALL & ~E_NOTICE | E_STRICT (Show all errors, except for notices) +; E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR (Show only errors) +; E_ALL | E_STRICT (Show all errors, warnings and notices including coding standards.) +; Default Value: E_ALL & ~E_NOTICE +; Development Value: E_ALL | E_STRICT +; Production Value: E_ALL & ~E_DEPRECATED +; http://php.net/error-reporting +error_reporting = E_ALL & ~E_DEPRECATED + +; This directive controls whether or not and where PHP will output errors, +; notices and warnings too. Error output is very useful during development, but +; it could be very dangerous in production environments. Depending on the code +; which is triggering the error, sensitive information could potentially leak +; out of your application such as database usernames and passwords or worse. +; It's recommended that errors be logged on production servers rather than +; having the errors sent to STDOUT. +; Possible Values: +; Off = Do not display any errors +; stderr = Display errors to STDERR (affects only CGI/CLI binaries!) +; On or stdout = Display errors to STDOUT +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/display-errors +display_errors = Off + +; The display of errors which occur during PHP's startup sequence are handled +; separately from display_errors. PHP's default behavior is to suppress those +; errors from clients. Turning the display of startup errors on can be useful in +; debugging configuration problems. But, it's strongly recommended that you +; leave this setting off on production servers. +; Default Value: Off +; Development Value: On +; Production Value: Off +; http://php.net/display-startup-errors +display_startup_errors = Off + +; Besides displaying errors, PHP can also log errors to locations such as a +; server-specific log, STDERR, or a location specified by the error_log +; directive found below. While errors should not be displayed on productions +; servers they should still be monitored and logging is a great way to do that. +; Default Value: Off +; Development Value: On +; Production Value: On +; http://php.net/log-errors +log_errors =On + +; Set maximum length of log_errors. In error_log information about the source is +; added. The default is 1024 and 0 allows to not apply any maximum length at all. +; http://php.net/log-errors-max-len +log_errors_max_len = 1024 + +; Do not log repeated messages. Repeated errors must occur in same file on same +; line unless ignore_repeated_source is set true. +; http://php.net/ignore-repeated-errors +ignore_repeated_errors = Off + +; Ignore source of message when ignoring repeated messages. When this setting +; is On you will not log errors with repeated messages from different files or +; source lines. +; http://php.net/ignore-repeated-source +ignore_repeated_source = Off + +; If this parameter is set to Off, then memory leaks will not be shown (on +; stdout or in the log). This has only effect in a debug compile, and if +; error reporting includes E_WARNING in the allowed list +; http://php.net/report-memleaks +report_memleaks = On + +; This setting is on by default. +;report_zend_debug = 0 + +; Store the last error/warning message in $php_errormsg (boolean). Setting this value +; to On can assist in debugging and is appropriate for development servers. It should +; however be disabled on production servers. +; Default Value: Off +; Development Value: On +; Production Value: Off +; http://php.net/track-errors +track_errors = Off + +; Turn off normal error reporting and emit XML-RPC error XML +; http://php.net/xmlrpc-errors +;xmlrpc_errors = 0 + +; An XML-RPC faultCode +;xmlrpc_error_number = 0 + +; When PHP displays or logs an error, it has the capability of inserting html +; links to documentation related to that error. This directive controls whether +; those HTML links appear in error messages or not. For performance and security +; reasons, it's recommended you disable this on production servers. +; Note: This directive is hardcoded to Off for the CLI SAPI +; Default Value: On +; Development Value: On +; Production value: Off +; http://php.net/html-errors +html_errors = Off + +; If html_errors is set On PHP produces clickable error messages that direct +; to a page describing the error or function causing the error in detail. +; You can download a copy of the PHP manual from http://php.net/docs +; and change docref_root to the base URL of your local copy including the +; leading '/'. You must also specify the file extension being used including +; the dot. PHP's default behavior is to leave these settings empty. +; Note: Never use this feature for production boxes. +; http://php.net/docref-root +; Examples +;docref_root = "/phpmanual/" + +; http://php.net/docref-ext +;docref_ext = .html + +; String to output before an error message. PHP's default behavior is to leave +; this setting blank. +; http://php.net/error-prepend-string +; Example: +;error_prepend_string = "" + +; String to output after an error message. PHP's default behavior is to leave +; this setting blank. +; http://php.net/error-append-string +; Example: +;error_append_string = "" + +; Log errors to specified file. PHP's default behavior is to leave this value +; empty. +; http://php.net/error-log +; Example: +;error_log = php_errors.log +; Log errors to syslog (Event Log on NT, not valid in Windows 95). +;error_log = syslog + +;windows.show_crt_warning +; Default value: 0 +; Development value: 0 +; Production value: 0 + +;;;;;;;;;;;;;;;;; +; Data Handling ; +;;;;;;;;;;;;;;;;; + +; The separator used in PHP generated URLs to separate arguments. +; PHP's default setting is "&". +; http://php.net/arg-separator.output +; Example: +;arg_separator.output = "&" + +; List of separator(s) used by PHP to parse input URLs into variables. +; PHP's default setting is "&". +; NOTE: Every character in this directive is considered as separator! +; http://php.net/arg-separator.input +; Example: +;arg_separator.input = ";&" + +; This directive determines which super global arrays are registered when PHP +; starts up. If the register_globals directive is enabled, it also determines +; what order variables are populated into the global space. G,P,C,E & S are +; abbreviations for the following respective super globals: GET, POST, COOKIE, +; ENV and SERVER. There is a performance penalty paid for the registration of +; these arrays and because ENV is not as commonly used as the others, ENV is +; is not recommended on productions servers. You can still get access to +; the environment variables through getenv() should you need to. +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS"; +; http://php.net/variables-order +variables_order = "GPCS" + +; This directive determines which super global data (G,P,C,E & S) should +; be registered into the super global array REQUEST. If so, it also determines +; the order in which that data is registered. The values for this directive are +; specified in the same manner as the variables_order directive, EXCEPT one. +; Leaving this value empty will cause PHP to use the value set in the +; variables_order directive. It does not mean it will leave the super globals +; array REQUEST empty. +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" +; http://php.net/request-order +request_order = "GP" + +; Whether or not to register the EGPCS variables as global variables. You may +; want to turn this off if you don't want to clutter your scripts' global scope +; with user data. +; You should do your best to write your scripts so that they do not require +; register_globals to be on; Using form variables as globals can easily lead +; to possible security problems, if the code is not very well thought of. +; http://php.net/register-globals +register_globals = Off + +; Determines whether the deprecated long $HTTP_*_VARS type predefined variables +; are registered by PHP or not. As they are deprecated, we obviously don't +; recommend you use them. They are on by default for compatibility reasons but +; they are not recommended on production servers. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/register-long-arrays +register_long_arrays = Off + +; This directive determines whether PHP registers $argv & $argc each time it +; runs. $argv contains an array of all the arguments passed to PHP when a script +; is invoked. $argc contains an integer representing the number of arguments +; that were passed when the script was invoked. These arrays are extremely +; useful when running scripts from the command line. When this directive is +; enabled, registering these variables consumes CPU cycles and memory each time +; a script is executed. For performance reasons, this feature should be disabled +; on production servers. +; Note: This directive is hardcoded to On for the CLI SAPI +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/register-argc-argv +register_argc_argv = Off + +; When enabled, the SERVER and ENV variables are created when they're first +; used (Just In Time) instead of when the script starts. If these variables +; are not used within a script, having this directive on will result in a +; performance gain. The PHP directives register_globals, register_long_arrays, +; and register_argc_argv must be disabled for this directive to have any affect. +; http://php.net/auto-globals-jit +auto_globals_jit = On + +; Maximum size of POST data that PHP will accept. +; http://php.net/post-max-size +post_max_size = 8M + +; Magic quotes are a preprocessing feature of PHP where PHP will attempt to +; escape any character sequences in GET, POST, COOKIE and ENV data which might +; otherwise corrupt data being placed in resources such as databases before +; making that data available to you. Because of character encoding issues and +; non-standard SQL implementations across many databases, it's not currently +; possible for this feature to be 100% accurate. PHP's default behavior is to +; enable the feature. We strongly recommend you use the escaping mechanisms +; designed specifically for the database your using instead of relying on this +; feature. Also note, this feature has been deprecated as of PHP 5.3.0 and is +; scheduled removed in PHP 5.4. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/magic-quotes-gpc +magic_quotes_gpc = Off + +; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc. +; http://php.net/magic-quotes-runtime +magic_quotes_runtime = Off + +; Use Sybase-style magic quotes (escape ' with '' instead of \'). +; http://php.net/magic-quotes-sybase +magic_quotes_sybase = Off + +; Automatically add files before PHP document. +; http://php.net/auto-prepend-file +auto_prepend_file = + +; Automatically add files after PHP document. +; http://php.net/auto-append-file +auto_append_file = + +; By default, PHP will output a character encoding using +; the Content-type: header. To disable sending of the charset, simply +; set it to be empty. +; +; PHP's built-in default is text/html +; http://php.net/default-mimetype +default_mimetype = "text/html" + +; PHP's default character set is set to empty. +; http://php.net/default-charset +;default_charset = "iso-8859-1" + +; Always populate the $HTTP_RAW_POST_DATA variable. PHP's default behavior is +; to disable this feature. +; http://php.net/always-populate-raw-post-data +;always_populate_raw_post_data = On + +;;;;;;;;;;;;;;;;;;;;;;;;; +; Paths and Directories ; +;;;;;;;;;;;;;;;;;;;;;;;;; + +; UNIX: "/path1:/path2" +;include_path = ".:/php/includes" +; +; Windows: "\path1;\path2" +;include_path = ".;c:\php\includes" +; +; PHP's default setting for include_path is ".;/path/to/php/pear" +; http://php.net/include-path + +; The root of the PHP pages, used only if nonempty. +; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root +; if you are running php as a CGI under any web server (other than IIS) +; see documentation for security issues. The alternate is to use the +; cgi.force_redirect configuration below +; http://php.net/doc-root +doc_root = + +; The directory under which PHP opens the script using /~username used only +; if nonempty. +; http://php.net/user-dir +user_dir = + +; Directory in which the loadable extensions (modules) reside. +; http://php.net/extension-dir +; extension_dir = "./" +; On windows: +; extension_dir = "ext" + +; Whether or not to enable the dl() function. The dl() function does NOT work +; properly in multithreaded servers, such as IIS or Zeus, and is automatically +; disabled on them. +; http://php.net/enable-dl +enable_dl = Off + +; cgi.force_redirect is necessary to provide security running PHP as a CGI under +; most web servers. Left undefined, PHP turns this on by default. You can +; turn it off here AT YOUR OWN RISK +; **You CAN safely turn this off for IIS, in fact, you MUST.** +; http://php.net/cgi.force-redirect +;cgi.force_redirect = 1 + +; if cgi.nph is enabled it will force cgi to always sent Status: 200 with +; every request. PHP's default behavior is to disable this feature. +;cgi.nph = 1 + +; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape +; (iPlanet) web servers, you MAY need to set an environment variable name that PHP +; will look for to know it is OK to continue execution. Setting this variable MAY +; cause security issues, KNOW WHAT YOU ARE DOING FIRST. +; http://php.net/cgi.redirect-status-env +;cgi.redirect_status_env = + +; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's +; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok +; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting +; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting +; of zero causes PHP to behave as before. Default is 1. You should fix your scripts +; to use SCRIPT_FILENAME rather than PATH_TRANSLATED. +; http://php.net/cgi.fix-pathinfo +;cgi.fix_pathinfo=1 + +; FastCGI under IIS (on WINNT based OS) supports the ability to impersonate +; security tokens of the calling client. This allows IIS to define the +; security context that the request runs under. mod_fastcgi under Apache +; does not currently support this feature (03/17/2002) +; Set to 1 if running under IIS. Default is zero. +; http://php.net/fastcgi.impersonate +;fastcgi.impersonate = 1 + +; Disable logging through FastCGI connection. PHP's default behavior is to enable +; this feature. +;fastcgi.logging = 0 + +; cgi.rfc2616_headers configuration option tells PHP what type of headers to +; use when sending HTTP response code. If it's set 0 PHP sends Status: header that +; is supported by Apache. When this option is set to 1 PHP will send +; RFC2616 compliant header. +; Default is zero. +; http://php.net/cgi.rfc2616-headers +;cgi.rfc2616_headers = 0 + +;;;;;;;;;;;;;;;; +; File Uploads ; +;;;;;;;;;;;;;;;; + +; Whether to allow HTTP file uploads. +; http://php.net/file-uploads +file_uploads = On + +; Temporary directory for HTTP uploaded files (will use system default if not +; specified). +; http://php.net/upload-tmp-dir +;upload_tmp_dir = + +; Maximum allowed size for uploaded files. +; http://php.net/upload-max-filesize +upload_max_filesize = 2M + +; Maximum number of files that can be uploaded via a single request +max_file_uploads = 20 + +;;;;;;;;;;;;;;;;;; +; Fopen wrappers ; +;;;;;;;;;;;;;;;;;; + +; Whether to allow the treatment of URLs (like http:// or ftp://) as files. +; http://php.net/allow-url-fopen +allow_url_fopen = On + +; Whether to allow include/require to open URLs (like http:// or ftp://) as files. +; http://php.net/allow-url-include +allow_url_include = Off + +; Define the anonymous ftp password (your email address). PHP's default setting +; for this is empty. +; http://php.net/from +;from="john@doe.com" + +; Define the User-Agent string. PHP's default setting for this is empty. +; http://php.net/user-agent +;user_agent="PHP" + +; Default timeout for socket based streams (seconds) +; http://php.net/default-socket-timeout +default_socket_timeout = 60 +upload_tmp_dir="C:\Windows\Temp" +session.save_path="C:\Windows\Temp" +error_log="C:\Windows\temp\php-errors.log" +cgi.force_redirect=0 +fastcgi.impersonate=1 +fastcgi.logging=0 +extension_dir="ext" + +; If your scripts have to deal with files from Macintosh systems, +; or you are running on a Mac and need to deal with files from +; unix or win32 systems, setting this flag will cause PHP to +; automatically detect the EOL character in those files so that +; fgets() and file() will work regardless of the source of the file. +; http://php.net/auto-detect-line-endings +;auto_detect_line_endings = Off + +;;;;;;;;;;;;;;;;;;;;;; +; Dynamic Extensions ; +;;;;;;;;;;;;;;;;;;;;;; + +; If you wish to have an extension loaded automatically, use the following +; syntax: +; +; extension=modulename.extension +; +; For example, on Windows: +; +; extension=msql.dll +; +; ... or under UNIX: +; +; extension=msql.so +; +; ... or with a path: +; +; extension=/path/to/extension/msql.so +; +; If you only provide the name of the extension, PHP will look for it in its +; default extension directory. +; +; Windows Extensions +; Note that ODBC support is built in, so no dll is needed for it. +; Note that many DLL files are located in the extensions/ (PHP 4) ext/ (PHP 5) +; extension folders as well as the separate PECL DLL download (PHP 5). +; Be sure to appropriately set the extension_dir directive. +; + +; The MIBS data available in the PHP distribution must be installed. +; See http://www.php.net/manual/en/snmp.installation.php + + +;;;;;;;;;;;;;;;;;;; +; Module Settings ; +;;;;;;;;;;;;;;;;;;; + +[Date] +; Defines the default timezone used by the date functions +; http://php.net/date.timezone +;date.timezone = + +; http://php.net/date.default-latitude +;date.default_latitude = 31.7667 + +; http://php.net/date.default-longitude +;date.default_longitude = 35.2333 + +; http://php.net/date.sunrise-zenith +;date.sunrise_zenith = 90.583333 + +; http://php.net/date.sunset-zenith +;date.sunset_zenith = 90.583333 + +[filter] +; http://php.net/filter.default +;filter.default = unsafe_raw + +; http://php.net/filter.default-flags +;filter.default_flags = + +[iconv] +;iconv.input_encoding = ISO-8859-1 +;iconv.internal_encoding = ISO-8859-1 +;iconv.output_encoding = ISO-8859-1 + +[intl] +;intl.default_locale = +; This directive allows you to produce PHP errors when some error +; happens within intl functions. The value is the level of the error produced. +; Default is 0, which does not produce any errors. +;intl.error_level = E_WARNING + +[sqlite] +; http://php.net/sqlite.assoc-case +;sqlite.assoc_case = 0 + +[sqlite3] +;sqlite3.extension_dir = + +[Pcre] +;PCRE library backtracking limit. +; http://php.net/pcre.backtrack-limit +;pcre.backtrack_limit=100000 + +;PCRE library recursion limit. +;Please note that if you set this value to a high number you may consume all +;the available process stack and eventually crash PHP (due to reaching the +;stack size limit imposed by the Operating System). +; http://php.net/pcre.recursion-limit +;pcre.recursion_limit=100000 + +[Pdo] +; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off" +; http://php.net/pdo-odbc.connection-pooling +;pdo_odbc.connection_pooling=strict + +;pdo_odbc.db2_instance_name + +[Pdo_mysql] +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/pdo_mysql.cache_size +pdo_mysql.cache_size = 2000 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/pdo_mysql.default-socket +pdo_mysql.default_socket= + +[Phar] +; http://php.net/phar.readonly +;phar.readonly = On + +; http://php.net/phar.require-hash +;phar.require_hash = On + +;phar.cache_list = + +[Syslog] +; Whether or not to define the various syslog variables (e.g. $LOG_PID, +; $LOG_CRON, etc.). Turning it off is a good idea performance-wise. In +; runtime, you can define these variables by calling define_syslog_variables(). +; http://php.net/define-syslog-variables +define_syslog_variables = Off + +[mail function] +; For Win32 only. +; http://php.net/smtp +SMTP = localhost +; http://php.net/smtp-port +smtp_port = 25 + +; For Win32 only. +; http://php.net/sendmail-from +;sendmail_from = me@example.com + +; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). +; http://php.net/sendmail-path +;sendmail_path = + +; Force the addition of the specified parameters to be passed as extra parameters +; to the sendmail binary. These parameters will always replace the value of +; the 5th parameter to mail(), even in safe mode. +;mail.force_extra_parameters = + +; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename +mail.add_x_header = On + +; The path to a log file that will log all mail() calls. Log entries include +; the full path of the script, line number, To address and headers. +;mail.log = + +[SQL] +; http://php.net/sql.safe-mode +sql.safe_mode = Off + +[ODBC] +; http://php.net/odbc.default-db +;odbc.default_db = Not yet implemented + +; http://php.net/odbc.default-user +;odbc.default_user = Not yet implemented + +; http://php.net/odbc.default-pw +;odbc.default_pw = Not yet implemented + +; Controls the ODBC cursor model. +; Default: SQL_CURSOR_STATIC (default). +;odbc.default_cursortype + +; Allow or prevent persistent links. +; http://php.net/odbc.allow-persistent +odbc.allow_persistent = On + +; Check that a connection is still valid before reuse. +; http://php.net/odbc.check-persistent +odbc.check_persistent = On + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/odbc.max-persistent +odbc.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/odbc.max-links +odbc.max_links = -1 + +; Handling of LONG fields. Returns number of bytes to variables. 0 means +; passthru. +; http://php.net/odbc.defaultlrl +odbc.defaultlrl = 4096 + +; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char. +; See the documentation on odbc_binmode and odbc_longreadlen for an explanation +; of odbc.defaultlrl and odbc.defaultbinmode +; http://php.net/odbc.defaultbinmode +odbc.defaultbinmode = 1 + +;birdstep.max_links = -1 + +[Interbase] +; Allow or prevent persistent links. +ibase.allow_persistent = 1 + +; Maximum number of persistent links. -1 means no limit. +ibase.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +ibase.max_links = -1 + +; Default database name for ibase_connect(). +;ibase.default_db = + +; Default username for ibase_connect(). +;ibase.default_user = + +; Default password for ibase_connect(). +;ibase.default_password = + +; Default charset for ibase_connect(). +;ibase.default_charset = + +; Default timestamp format. +ibase.timestampformat = "%Y-%m-%d %H:%M:%S" + +; Default date format. +ibase.dateformat = "%Y-%m-%d" + +; Default time format. +ibase.timeformat = "%H:%M:%S" + +[MySQL] +; Allow accessing, from PHP's perspective, local files with LOAD DATA statements +; http://php.net/mysql.allow_local_infile +mysql.allow_local_infile = On + +; Allow or prevent persistent links. +; http://php.net/mysql.allow-persistent +mysql.allow_persistent = On + +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/mysql.cache_size +mysql.cache_size = 2000 + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/mysql.max-persistent +mysql.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/mysql.max-links +mysql.max_links = -1 + +; Default port number for mysql_connect(). If unset, mysql_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +; http://php.net/mysql.default-port +mysql.default_port = + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/mysql.default-socket +mysql.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysql.default-host +mysql.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysql.default-user +mysql.default_user = + +; Default password for mysql_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysql.default_password") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +; http://php.net/mysql.default-password +mysql.default_password = + +; Maximum time (in seconds) for connect timeout. -1 means no limit +; http://php.net/mysql.connect-timeout +mysql.connect_timeout = 60 + +; Trace mode. When trace_mode is active (=On), warnings for table/index scans and +; SQL-Errors will be displayed. +; http://php.net/mysql.trace-mode +mysql.trace_mode = Off + +[MySQLi] + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/mysqli.max-persistent +mysqli.max_persistent = -1 + +; Allow accessing, from PHP's perspective, local files with LOAD DATA statements +; http://php.net/mysqli.allow_local_infile +;mysqli.allow_local_infile = On + +; Allow or prevent persistent links. +; http://php.net/mysqli.allow-persistent +mysqli.allow_persistent = On + +; Maximum number of links. -1 means no limit. +; http://php.net/mysqli.max-links +mysqli.max_links = -1 + +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/mysqli.cache_size +mysqli.cache_size = 2000 + +; Default port number for mysqli_connect(). If unset, mysqli_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +; http://php.net/mysqli.default-port +mysqli.default_port = 3306 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/mysqli.default-socket +mysqli.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysqli.default-host +mysqli.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysqli.default-user +mysqli.default_user = + +; Default password for mysqli_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +; http://php.net/mysqli.default-pw +mysqli.default_pw = + +; Allow or prevent reconnect +mysqli.reconnect = Off + +[mysqlnd] +; Enable / Disable collection of general statistics by mysqlnd which can be +; used to tune and monitor MySQL operations. +; http://php.net/mysqlnd.collect_statistics +mysqlnd.collect_statistics = On + +; Enable / Disable collection of memory usage statistics by mysqlnd which can be +; used to tune and monitor MySQL operations. +; http://php.net/mysqlnd.collect_memory_statistics +mysqlnd.collect_memory_statistics = Off + +; Size of a pre-allocated buffer used when sending commands to MySQL in bytes. +; http://php.net/mysqlnd.net_cmd_buffer_size +;mysqlnd.net_cmd_buffer_size = 2048 + +; Size of a pre-allocated buffer used for reading data sent by the server in +; bytes. +; http://php.net/mysqlnd.net_read_buffer_size +;mysqlnd.net_read_buffer_size = 32768 + +[OCI8] + +; Connection: Enables privileged connections using external +; credentials (OCI_SYSOPER, OCI_SYSDBA) +; http://php.net/oci8.privileged-connect +;oci8.privileged_connect = Off + +; Connection: The maximum number of persistent OCI8 connections per +; process. Using -1 means no limit. +; http://php.net/oci8.max-persistent +;oci8.max_persistent = -1 + +; Connection: The maximum number of seconds a process is allowed to +; maintain an idle persistent connection. Using -1 means idle +; persistent connections will be maintained forever. +; http://php.net/oci8.persistent-timeout +;oci8.persistent_timeout = -1 + +; Connection: The number of seconds that must pass before issuing a +; ping during oci_pconnect() to check the connection validity. When +; set to 0, each oci_pconnect() will cause a ping. Using -1 disables +; pings completely. +; http://php.net/oci8.ping-interval +;oci8.ping_interval = 60 + +; Connection: Set this to a user chosen connection class to be used +; for all pooled server requests with Oracle 11g Database Resident +; Connection Pooling (DRCP). To use DRCP, this value should be set to +; the same string for all web servers running the same application, +; the database pool must be configured, and the connection string must +; specify to use a pooled server. +;oci8.connection_class = + +; High Availability: Using On lets PHP receive Fast Application +; Notification (FAN) events generated when a database node fails. The +; database must also be configured to post FAN events. +;oci8.events = Off + +; Tuning: This option enables statement caching, and specifies how +; many statements to cache. Using 0 disables statement caching. +; http://php.net/oci8.statement-cache-size +;oci8.statement_cache_size = 20 + +; Tuning: Enables statement prefetching and sets the default number of +; rows that will be fetched automatically after statement execution. +; http://php.net/oci8.default-prefetch +;oci8.default_prefetch = 100 + +; Compatibility. Using On means oci_close() will not close +; oci_connect() and oci_new_connect() connections. +; http://php.net/oci8.old-oci-close-semantics +;oci8.old_oci_close_semantics = Off + +[PostgreSQL] +; Allow or prevent persistent links. +; http://php.net/pgsql.allow-persistent +pgsql.allow_persistent = On + +; Detect broken persistent links always with pg_pconnect(). +; Auto reset feature requires a little overheads. +; http://php.net/pgsql.auto-reset-persistent +pgsql.auto_reset_persistent = Off + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/pgsql.max-persistent +pgsql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +; http://php.net/pgsql.max-links +pgsql.max_links = -1 + +; Ignore PostgreSQL backends Notice message or not. +; Notice message logging require a little overheads. +; http://php.net/pgsql.ignore-notice +pgsql.ignore_notice = 0 + +; Log PostgreSQL backends Notice message or not. +; Unless pgsql.ignore_notice=0, module cannot log notice message. +; http://php.net/pgsql.log-notice +pgsql.log_notice = 0 + +[Sybase-CT] +; Allow or prevent persistent links. +; http://php.net/sybct.allow-persistent +sybct.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/sybct.max-persistent +sybct.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/sybct.max-links +sybct.max_links = -1 + +; Minimum server message severity to display. +; http://php.net/sybct.min-server-severity +sybct.min_server_severity = 10 + +; Minimum client message severity to display. +; http://php.net/sybct.min-client-severity +sybct.min_client_severity = 10 + +; Set per-context timeout +; http://php.net/sybct.timeout +;sybct.timeout= + +;sybct.packet_size + +; The maximum time in seconds to wait for a connection attempt to succeed before returning failure. +; Default: one minute +;sybct.login_timeout= + +; The name of the host you claim to be connecting from, for display by sp_who. +; Default: none +;sybct.hostname= + +; Allows you to define how often deadlocks are to be retried. -1 means "forever". +; Default: 0 +;sybct.deadlock_retry_count= + +[bcmath] +; Number of decimal digits for all bcmath functions. +; http://php.net/bcmath.scale +bcmath.scale = 0 + +[browscap] +; http://php.net/browscap +;browscap = extra/browscap.ini + +[Session] +; Handler used to store/retrieve data. +; http://php.net/session.save-handler +session.save_handler = files + +; Argument passed to save_handler. In the case of files, this is the path +; where data files are stored. Note: Windows users have to change this +; variable in order to use PHP's session functions. +; +; The path can be defined as: +; +; session.save_path = "N;/path" +; +; where N is an integer. Instead of storing all the session files in +; /path, what this will do is use subdirectories N-levels deep, and +; store the session data in those directories. This is useful if you +; or your OS have problems with lots of files in one directory, and is +; a more efficient layout for servers that handle lots of sessions. +; +; NOTE 1: PHP will not create this directory structure automatically. +; You can use the script in the ext/session dir for that purpose. +; NOTE 2: See the section on garbage collection below if you choose to +; use subdirectories for session storage +; +; The file storage module creates files using mode 600 by default. +; You can change that by using +; +; session.save_path = "N;MODE;/path" +; +; where MODE is the octal representation of the mode. Note that this +; does not overwrite the process's umask. +; http://php.net/session.save-path +;session.save_path = "/tmp" + +; Whether to use cookies. +; http://php.net/session.use-cookies +session.use_cookies = 1 + +; http://php.net/session.cookie-secure +;session.cookie_secure = + +; This option forces PHP to fetch and use a cookie for storing and maintaining +; the session id. We encourage this operation as it's very helpful in combating +; session hijacking when not specifying and managing your own session id. It is +; not the end all be all of session hijacking defense, but it's a good start. +; http://php.net/session.use-only-cookies +session.use_only_cookies = 1 + +; Name of the session (used as cookie name). +; http://php.net/session.name +session.name = PHPSESSID + +; Initialize session on request startup. +; http://php.net/session.auto-start +session.auto_start = 0 + +; Lifetime in seconds of cookie or, if 0, until browser is restarted. +; http://php.net/session.cookie-lifetime +session.cookie_lifetime = 0 + +; The path for which the cookie is valid. +; http://php.net/session.cookie-path +session.cookie_path = / + +; The domain for which the cookie is valid. +; http://php.net/session.cookie-domain +session.cookie_domain = + +; Whether or not to add the httpOnly flag to the cookie, which makes it inaccessible to browser scripting languages such as JavaScript. +; http://php.net/session.cookie-httponly +session.cookie_httponly = + +; Handler used to serialize data. php is the standard serializer of PHP. +; http://php.net/session.serialize-handler +session.serialize_handler = php + +; Defines the probability that the 'garbage collection' process is started +; on every session initialization. The probability is calculated by using +; gc_probability/gc_divisor. Where session.gc_probability is the numerator +; and gc_divisor is the denominator in the equation. Setting this value to 1 +; when the session.gc_divisor value is 100 will give you approximately a 1% chance +; the gc will run on any give request. +; Default Value: 1 +; Development Value: 1 +; Production Value: 1 +; http://php.net/session.gc-probability +session.gc_probability = 1 + +; Defines the probability that the 'garbage collection' process is started on every +; session initialization. The probability is calculated by using the following equation: +; gc_probability/gc_divisor. Where session.gc_probability is the numerator and +; session.gc_divisor is the denominator in the equation. Setting this value to 1 +; when the session.gc_divisor value is 100 will give you approximately a 1% chance +; the gc will run on any give request. Increasing this value to 1000 will give you +; a 0.1% chance the gc will run on any give request. For high volume production servers, +; this is a more efficient approach. +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 +; http://php.net/session.gc-divisor +session.gc_divisor = 1000 + +; After this number of seconds, stored data will be seen as 'garbage' and +; cleaned up by the garbage collection process. +; http://php.net/session.gc-maxlifetime +session.gc_maxlifetime = 1440 + +; NOTE: If you are using the subdirectory option for storing session files +; (see session.save_path above), then garbage collection does *not* +; happen automatically. You will need to do your own garbage +; collection through a shell script, cron entry, or some other method. +; For example, the following script would is the equivalent of +; setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): +; find /path/to/sessions -cmin +24 | xargs rm + +; PHP 4.2 and less have an undocumented feature/bug that allows you to +; to initialize a session variable in the global scope, even when register_globals +; is disabled. PHP 4.3 and later will warn you, if this feature is used. +; You can disable the feature and the warning separately. At this time, +; the warning is only displayed, if bug_compat_42 is enabled. This feature +; introduces some serious security problems if not handled correctly. It's +; recommended that you do not use this feature on production servers. But you +; should enable this on development servers and enable the warning as well. If you +; do not enable the feature on development servers, you won't be warned when it's +; used and debugging errors caused by this can be difficult to track down. +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/session.bug-compat-42 +session.bug_compat_42 = Off + +; This setting controls whether or not you are warned by PHP when initializing a +; session value into the global space. session.bug_compat_42 must be enabled before +; these warnings can be issued by PHP. See the directive above for more information. +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/session.bug-compat-warn +session.bug_compat_warn = Off + +; Check HTTP Referer to invalidate externally stored URLs containing ids. +; HTTP_REFERER has to contain this substring for the session to be +; considered as valid. +; http://php.net/session.referer-check +session.referer_check = + +; How many bytes to read from the file. +; http://php.net/session.entropy-length +session.entropy_length = 0 + +; Specified here to create the session id. +; http://php.net/session.entropy-file +; On systems that don't have /dev/urandom /dev/arandom can be used +; On windows, setting the entropy_length setting will activate the +; Windows random source (using the CryptoAPI) +;session.entropy_file = /dev/urandom + +; Set to {nocache,private,public,} to determine HTTP caching aspects +; or leave this empty to avoid sending anti-caching headers. +; http://php.net/session.cache-limiter +session.cache_limiter = nocache + +; Document expires after n minutes. +; http://php.net/session.cache-expire +session.cache_expire = 180 + +; trans sid support is disabled by default. +; Use of trans sid may risk your users security. +; Use this option with caution. +; - User may send URL contains active session ID +; to other person via. email/irc/etc. +; - URL that contains active session ID may be stored +; in publicly accessible computer. +; - User may access your site with the same session ID +; always using URL stored in browser's history or bookmarks. +; http://php.net/session.use-trans-sid +session.use_trans_sid = 0 + +; Select a hash function for use in generating session ids. +; Possible Values +; 0 (MD5 128 bits) +; 1 (SHA-1 160 bits) +; This option may also be set to the name of any hash function supported by +; the hash extension. A list of available hashes is returned by the hash_algos() +; function. +; http://php.net/session.hash-function +session.hash_function = 0 + +; Define how many bits are stored in each character when converting +; the binary hash data to something readable. +; Possible values: +; 4 (4 bits: 0-9, a-f) +; 5 (5 bits: 0-9, a-v) +; 6 (6 bits: 0-9, a-z, A-Z, "-", ",") +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 +; http://php.net/session.hash-bits-per-character +session.hash_bits_per_character = 5 + +; The URL rewriter will look for URLs in a defined set of HTML tags. +; form/fieldset are special; if you include them here, the rewriter will +; add a hidden field with the info which is otherwise appended +; to URLs. If you want XHTML conformity, remove the form entry. +; Note that all valid entries require a "=", even if no value follows. +; Default Value: "a=href,area=href,frame=src,form=,fieldset=" +; Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; http://php.net/url-rewriter.tags +url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry" + +[MSSQL] +; Allow or prevent persistent links. +mssql.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +mssql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +mssql.max_links = -1 + +; Minimum error severity to display. +mssql.min_error_severity = 10 + +; Minimum message severity to display. +mssql.min_message_severity = 10 + +; Compatibility mode with old versions of PHP 3.0. +mssql.compatability_mode = Off + +; Connect timeout +;mssql.connect_timeout = 5 + +; Query timeout +;mssql.timeout = 60 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textlimit = 4096 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textsize = 4096 + +; Limits the number of records in each batch. 0 = all records in one batch. +;mssql.batchsize = 0 + +; Specify how datetime and datetim4 columns are returned +; On => Returns data converted to SQL server settings +; Off => Returns values as YYYY-MM-DD hh:mm:ss +;mssql.datetimeconvert = On + +; Use NT authentication when connecting to the server +mssql.secure_connection = Off + +; Specify max number of processes. -1 = library default +; msdlib defaults to 25 +; FreeTDS defaults to 4096 +;mssql.max_procs = -1 + +; Specify client character set. +; If empty or not set the client charset from freetds.conf is used +; This is only used when compiled with FreeTDS +;mssql.charset = "ISO-8859-1" + +[Assertion] +; Assert(expr); active by default. +; http://php.net/assert.active +;assert.active = On + +; Issue a PHP warning for each failed assertion. +; http://php.net/assert.warning +;assert.warning = On + +; Don't bail out by default. +; http://php.net/assert.bail +;assert.bail = Off + +; User-function to be called if an assertion fails. +; http://php.net/assert.callback +;assert.callback = 0 + +; Eval the expression with current error_reporting(). Set to true if you want +; error_reporting(0) around the eval(). +; http://php.net/assert.quiet-eval +;assert.quiet_eval = 0 + +[COM] +; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs +; http://php.net/com.typelib-file +;com.typelib_file = + +; allow Distributed-COM calls +; http://php.net/com.allow-dcom +;com.allow_dcom = true + +; autoregister constants of a components typlib on com_load() +; http://php.net/com.autoregister-typelib +;com.autoregister_typelib = true + +; register constants casesensitive +; http://php.net/com.autoregister-casesensitive +;com.autoregister_casesensitive = false + +; show warnings on duplicate constant registrations +; http://php.net/com.autoregister-verbose +;com.autoregister_verbose = true + +; The default character set code-page to use when passing strings to and from COM objects. +; Default: system ANSI code page +;com.code_page= + +[mbstring] +; language for internal character representation. +; http://php.net/mbstring.language +;mbstring.language = Japanese + +; internal/script encoding. +; Some encoding cannot work as internal encoding. +; (e.g. SJIS, BIG5, ISO-2022-*) +; http://php.net/mbstring.internal-encoding +;mbstring.internal_encoding = EUC-JP + +; http input encoding. +; http://php.net/mbstring.http-input +;mbstring.http_input = auto + +; http output encoding. mb_output_handler must be +; registered as output buffer to function +; http://php.net/mbstring.http-output +;mbstring.http_output = SJIS + +; enable automatic encoding translation according to +; mbstring.internal_encoding setting. Input chars are +; converted to internal encoding by setting this to On. +; Note: Do _not_ use automatic encoding translation for +; portable libs/applications. +; http://php.net/mbstring.encoding-translation +;mbstring.encoding_translation = Off + +; automatic encoding detection order. +; auto means +; http://php.net/mbstring.detect-order +;mbstring.detect_order = auto + +; substitute_character used when character cannot be converted +; one from another +; http://php.net/mbstring.substitute-character +;mbstring.substitute_character = none; + +; overload(replace) single byte functions by mbstring functions. +; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(), +; etc. Possible values are 0,1,2,4 or combination of them. +; For example, 7 for overload everything. +; 0: No overload +; 1: Overload mail() function +; 2: Overload str*() functions +; 4: Overload ereg*() functions +; http://php.net/mbstring.func-overload +;mbstring.func_overload = 0 + +; enable strict encoding detection. +;mbstring.strict_detection = Off + +; This directive specifies the regex pattern of content types for which mb_output_handler() +; is activated. +; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml) +;mbstring.http_output_conv_mimetype= + +; Allows to set script encoding. Only affects if PHP is compiled with --enable-zend-multibyte +; Default: "" +;mbstring.script_encoding= + +[gd] +; Tell the jpeg decode to ignore warnings and try to create +; a gd image. The warning will then be displayed as notices +; disabled by default +; http://php.net/gd.jpeg-ignore-warning +;gd.jpeg_ignore_warning = 0 + +[exif] +; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. +; With mbstring support this will automatically be converted into the encoding +; given by corresponding encode setting. When empty mbstring.internal_encoding +; is used. For the decode settings you can distinguish between motorola and +; intel byte order. A decode setting cannot be empty. +; http://php.net/exif.encode-unicode +;exif.encode_unicode = ISO-8859-15 + +; http://php.net/exif.decode-unicode-motorola +;exif.decode_unicode_motorola = UCS-2BE + +; http://php.net/exif.decode-unicode-intel +;exif.decode_unicode_intel = UCS-2LE + +; http://php.net/exif.encode-jis +;exif.encode_jis = + +; http://php.net/exif.decode-jis-motorola +;exif.decode_jis_motorola = JIS + +; http://php.net/exif.decode-jis-intel +;exif.decode_jis_intel = JIS + +[Tidy] +; The path to a default tidy configuration file to use when using tidy +; http://php.net/tidy.default-config +;tidy.default_config = /usr/local/lib/php/default.tcfg + +; Should tidy clean and repair output automatically? +; WARNING: Do not use this option if you are generating non-html content +; such as dynamic images +; http://php.net/tidy.clean-output +tidy.clean_output = Off + +[soap] +; Enables or disables WSDL caching feature. +; http://php.net/soap.wsdl-cache-enabled +soap.wsdl_cache_enabled=1 + +; Sets the directory name where SOAP extension will put cache files. +; http://php.net/soap.wsdl-cache-dir +soap.wsdl_cache_dir="/tmp" + +; (time to live) Sets the number of second while cached file will be used +; instead of original one. +; http://php.net/soap.wsdl-cache-ttl +soap.wsdl_cache_ttl=86400 + +; Sets the size of the cache limit. (Max. number of WSDL files to cache) +soap.wsdl_cache_limit = 5 + +[sysvshm] +; A default size of the shared memory segment +;sysvshm.init_mem = 10000 + +[ldap] +; Sets the maximum number of open links or -1 for unlimited. +ldap.max_links = -1 + +[mcrypt] +; For more information about mcrypt settings see http://php.net/mcrypt-module-open + +; Directory where to load mcrypt algorithms +; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt) +;mcrypt.algorithms_dir= + +; Directory where to load mcrypt modes +; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt) +;mcrypt.modes_dir= + +[dba] +;dba.default_handler= + +[xsl] +; Write operations from within XSLT are disabled by default. +; XSL_SECPREF_CREATE_DIRECTORY | XSL_SECPREF_WRITE_NETWORK | XSL_SECPREF_WRITE_FILE = 44 +; Set it to 0 to allow all operations +;xsl.security_prefs = 44 + +; Local Variables: +; tab-width: 4 +; End: +[PHP_BZ2] +extension=php_bz2.dll +[PHP_CURL] +extension=php_curl.dll +[PHP_GD2] +extension=php_gd2.dll +[PHP_GETTEXT] +extension=php_gettext.dll +[PHP_GMP] +extension=php_gmp.dll +[PHP_IMAP] +extension=php_imap.dll +[PHP_MBSTRING] +extension=php_mbstring.dll +[PHP_MYSQL] +extension=php_mysql.dll +[PHP_MYSQLI] +extension=php_mysqli.dll +[PHP_OPENSSL] +extension=php_openssl.dll +[PHP_PDO_MYSQL] +extension=php_pdo_mysql.dll +[PHP_PDO_ODBC] +extension=php_pdo_odbc.dll +[PHP_PDO_SQLITE] +extension=php_pdo_sqlite.dll +[PHP_PGSQL] +extension=php_pgsql.dll +[PHP_SOAP] +extension=php_soap.dll +[PHP_SOCKETS] +extension=php_sockets.dll +[PHP_SQLITE3] +extension=php_sqlite3.dll +[PHP_TIDY] +extension=php_tidy.dll +[PHP_XMLRPC] +extension=php_xmlrpc.dll +[PHP_EXIF] +extension=php_exif.dll + + +include_path=".;<%= node['php']['conf_dir'].gsub('/', '\\') %>" + +<% @directives.each do |directive, value| -%> +<%= "#{directive}=\"#{value}\"" %> +<% end -%> diff --git a/cookbooks/poise/CHANGELOG.md b/cookbooks/poise/CHANGELOG.md new file mode 100644 index 0000000..ee9a7c2 --- /dev/null +++ b/cookbooks/poise/CHANGELOG.md @@ -0,0 +1,78 @@ +# Changelog + +## v2.0.1 + +* Make the ChefspecHelpers helper a no-op if chefspec is not already loaded. +* Fix for finding the correct cookbook for a file when using vendored gems. +* New flag for the OptionCollector helper, `parser`: + +```ruby +class Resource < Chef::Resource + include Poise + attribute(:options, option_collector: true, parser: proc {|val| parse(val) }) + + def parse(val) + {name: val} + end +end +``` + +* Fix for a possible infinite loop when using `ResourceProviderMixin` in a nested + module structure. + +## v2.0.0 + +Major overhaul! Poise is now a Halite gem/cookbook. New helpers: + +* ChefspecMatchers – Automatically create Chefspec matchers for Poise resources. +* DefinedIn – Track which file (and cookbook) a resource or provider is defined in. +* Fused – Experimental support for defining provider actions in the resource class. +* Inversion – Support for end-user dependency inversion with providers. + +All helpers are compatible with Chef >= 12.0. Chef 11 is now deprecated, if you +need to support Chef 11 please continue to use Poise 1. + +## v1.0.12 + +* Correctly propagate errors from inside notifying_block. + +## v1.0.10 + +* Fixes an issue with the LWRPPolyfill helper and false values. + + +## v1.0.8 + +* Delayed notifications from nested converges will still only run at the end of + the main converge. + +## v1.0.6 + +* The include_recipe helper now works correctly when used at compile time. + +## v1.0.4 + +* Redeclaring a template attribute with the same name as a parent class will + inherit its options. + +## v1.0.2 + +* New template attribute pattern. + +```ruby +attribute(:config, template: true) + +... + +resource 'name' do + config_source 'template.erb' +end + +... + +new_resource.config_content +``` + +## v1.0.0 + +* Initial release! diff --git a/cookbooks/poise/README.md b/cookbooks/poise/README.md new file mode 100644 index 0000000..6636536 --- /dev/null +++ b/cookbooks/poise/README.md @@ -0,0 +1,198 @@ +# Poise + +[![Build Status](https://img.shields.io/travis/poise/poise.svg)](https://travis-ci.org/poise/poise) +[![Gem Version](https://img.shields.io/gem/v/poise.svg)](https://rubygems.org/gems/poise) +[![Cookbook Version](https://img.shields.io/cookbook/v/poise.svg)](https://supermarket.chef.io/cookbooks/poise) +[![Coverage](https://img.shields.io/codecov/c/github/poise/poise.svg)](https://codecov.io/github/poise/poise) +[![Gemnasium](https://img.shields.io/gemnasium/poise/poise.svg)](https://gemnasium.com/poise/poise) +[![License](https://img.shields.io/badge/license-Apache_2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) + +## What is Poise? + +The poise cookbook is a set of libraries for writing reusable cookbooks. It +providers helpers for common patterns and a standard structure to make it easier to create flexible cookbooks. + +## Writing your first resource + +Rather than LWRPs, Poise promotes the idea of using normal, or "heavy weight" +resources, while including helpers to reduce much of boilerplate needed for this. Each resource goes in its own file under `libraries/` named to match +the resource, which is in turn based on the class name. This means that the file `libraries/my_app.rb` would contain `Chef::Resource::MyApp` which maps to the resource `my_app`. + +An example of a simple shell to start from: + +```ruby +class Chef + class Resource::MyApp < Resource + include Poise + + actions(:enable) + + attribute(:path, kind_of: String) + ... # Other attribute definitions + end + + class Provider::MyApp < Provider + include Poise + + def action_enable + converge_by("enable resource #{new_resource.name}") do + notifying_block do + ... # Normal Chef recipe code goes here + end + end + end + end +end +``` + +Starting from the top, first we declare the resource class, which inherits from +`Chef::Resource`. This is similar to the `resources/` file in an LWRP, and a similar DSL can be used. In order to load the helpers into the class, we +include the `Poise` mixin. Then we use the familiar DSL, though with a few additions we'll cover later. + +Then we declare the provider class, again similar to the `providers/` file in an LWRP. We include the `Poise` mixin again to get access to all the helpers. Rather than use the `action :enable do ... end` DSL from LWRPs, we just define the action method directly, and use the `converge_by` method to provide a description of what the action does. The implementation of action comes from a block of recipe code wrapped with `notifying_block` to capture changes in much the same way as `use_inline_resources`, see below for more information about all the features of `notifying_block`. + +We can then use this resource like any other Chef resource: + +```ruby +my_app 'one' do + path '/tmp' +end +``` + +## Helpers + +While not exposed as a specific method, Poise will automatically set the +`resource_name` based on the class name. + +### Notifying Block + +As mentioned above, `notifying_block` is similar to `use_inline_resources` in LWRPs. Any Chef resource created inside the block will be converged in a sub-context and if any have updated it will trigger notifications on the current resource. Unlike `use_inline_resources`, resources inside the sub-context can still see resources outside of it, with lookups propagating up sub-contexts until a match is found. Also any delayed notifications are scheduled to run at the end of the main converge cycle, instead of the end of this inner converge. + +This can be used to write action methods using the normal Chef recipe DSL, while still offering more flexibility through subclassing and other forms of code reuse. + +### Include Recipe + +In keeping with `notifying_block` to implement action methods using the Chef DSL, Poise adds an `include_recipe` helper to match the method of the same name in recipes. This will load and converge the requested recipe. + +### Resource DSL + +To make writing resource classes easier, Poise exposes a DSL similar to LWRPs for defining actions and attributes. Both `actions` and +`default_action` are just like in LWRPs, though `default_action` is rarely needed as the first action becomes the default. `attribute` is also available just like in LWRPs, but with some enhancements noted below. + +One notable difference over the standard DSL method is that Poise attributes +can take a block argument. + +#### Template Content + +A common pattern with resources is to allow passing either a template filename or raw file content to be used in a configuration file. Poise exposes a new attribute flag to help with this behavior: + +```ruby +attribute(:name, template: true) +``` + +This creates four methods on the class, `name_source`, `name_cookbook`, +`name_content`, and `name_options`. If the name is set to `''`, no prefix is applied to the function names. The content method can be set directly, but if not set and source is set, then it will render the template and return it as a string. Default values can also be set for any of these: + +```ruby +attribute(:name, template: true, default_source: 'app.cfg.erb', + default_options: {host: 'localhost'}) +``` + +As an example, you can replace this: + +```ruby +if new_resource.source + template new_resource.path do + source new_resource.source + owner 'app' + group 'app' + variables new_resource.options + end +else + file new_resource.path do + content new_resource.content + owner 'app' + group 'app' + end +end +``` + +with simply: + +```ruby +file new_resource.path do + content new_resource.content + owner 'app' + group 'app' +end +``` + +As the content method returns the rendered template as a string, this can also +be useful within other templates to build from partials. + +#### Lazy Initializers + +One issue with Poise-style resources is that when the class definition is executed, Chef hasn't loaded very far so things like the node object are not +yet available. This means setting defaults based on node attributes does not work directly: + +```ruby +attribute(:path, default: node['myapp']['path']) +... +NameError: undefined local variable or method 'node' +``` + +To work around this, Poise extends the idea of lazy initializers from Chef recipes to work with resource definitions as well: + +```ruby +attribute(:path, default: lazy { node['myapp']['path'] }) +``` + +These initializers are run in the context of the resource object, allowing +complex default logic to be moved to a method if desired: + +```ruby +attribute(:path, default: lazy { my_default_path }) + +def my_default_path + ... +end +``` + +#### Option Collector + +Another common pattern with resources is to need a set of key/value pairs for +configuration data or options. This can done with a simple Hash, but an option collector attribute can offer a nicer syntax: + +```ruby +attribute(:mydata, option_collector: true) +... + +my_app 'name' do + mydata do + key1 'value1' + key2 'value2' + end +end +``` + +This will be converted to `{key1: 'value1', key2: 'value2'}`. You can also pass a Hash to an option collector attribute just as you would with a normal attribute. + +## Sponsors + +The Poise test server infrastructure is generously sponsored by [Rackspace](https://rackspace.com/). Thanks Rackspace! + +## License + +Copyright 2013-2015, Noah Kantrowitz + +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/cookbooks/poise/files/halite_gem/poise.rb b/cookbooks/poise/files/halite_gem/poise.rb new file mode 100644 index 0000000..26a817a --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise.rb @@ -0,0 +1,71 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'chef/provider' +require 'chef/resource' + +require 'poise/utils/resource_provider_mixin' + + +module Poise + include Poise::Utils::ResourceProviderMixin + autoload :Helpers, 'poise/helpers' + autoload :Provider, 'poise/provider' + autoload :Resource, 'poise/resource' + autoload :Subcontext, 'poise/subcontext' + autoload :Utils, 'poise/utils' + autoload :VERSION, 'poise/version' +end + +# Callable form to allow passing in options: +# include Poise(ParentResource) +# include Poise(parent: ParentResource) +# include Poise(container: true) +def Poise(options={}) + # Allow passing a class as a shortcut + if options.is_a?(Class) + options = {parent: options} + end + + # Create a new anonymous module + mod = Module.new + + # Fake the name. + mod.define_singleton_method(:name) do + super() || 'Poise' + end + + mod.define_singleton_method(:included) do |klass| + super(klass) + # Pull in the main helper to cover most of the needed logic. + klass.class_exec { include Poise } + # Set the defined_in values as needed. + klass.poise_defined!(caller) + # Resource-specific options. + if klass < Chef::Resource + klass.poise_subresource(options[:parent], options[:parent_optional], options[:parent_auto]) if options[:parent] + klass.poise_subresource_container(options[:container_namespace]) if options[:container] + klass.poise_fused if options[:fused] + klass.poise_inversion(options[:inversion_options_resource]) if options[:inversion] + end + # Provider-specific options. + if klass < Chef::Provider + klass.poise_inversion(options[:inversion], options[:inversion_attribute]) if options[:inversion] + end + end + + mod +end diff --git a/cookbooks/poise/files/halite_gem/poise/error.rb b/cookbooks/poise/files/halite_gem/poise/error.rb new file mode 100644 index 0000000..e03f854 --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/error.rb @@ -0,0 +1,24 @@ +# +# Copyright 2015, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +module Poise + # Base exception class for Poise errors. + # + # @since 2.0.0 + class Error < Exception + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers.rb b/cookbooks/poise/files/halite_gem/poise/helpers.rb new file mode 100644 index 0000000..ea0057a --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers.rb @@ -0,0 +1,33 @@ +# +# Copyright 2015, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +module Poise + module Helpers + autoload :ChefspecMatchers, 'poise/helpers/chefspec_matchers' + autoload :DefinedIn, 'poise/helpers/defined_in' + autoload :Fused, 'poise/helpers/fused' + autoload :IncludeRecipe, 'poise/helpers/include_recipe' + autoload :Inversion, 'poise/helpers/inversion' + autoload :LazyDefault, 'poise/helpers/lazy_default' + autoload :LWRPPolyfill, 'poise/helpers/lwrp_polyfill' + autoload :NotifyingBlock, 'poise/helpers/notifying_block' + autoload :OptionCollector, 'poise/helpers/option_collector' + autoload :ResourceName, 'poise/helpers/resource_name' + autoload :Subresources, 'poise/helpers/subresources' + autoload :TemplateContent, 'poise/helpers/template_content' + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/chefspec_matchers.rb b/cookbooks/poise/files/halite_gem/poise/helpers/chefspec_matchers.rb new file mode 100644 index 0000000..b8337fb --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/chefspec_matchers.rb @@ -0,0 +1,88 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +# Not requiring chefspec or rspec/expectations since this code should only +# activate if they are already loaded. + +require 'poise/helpers/lwrp_polyfill' +require 'poise/helpers/resource_name' + + +module Poise + module Helpers + # A resource mixin to register ChefSpec matchers for a resource + # automatically. + # + # If you are using the provides() form for naming resources, ensure that is + # set before declaring actions. + # + # @since 2.0.0 + # @example Define a class + # class Chef::Resource::MyResource < Chef::Resource + # include Poise::Helpers::ChefspecMatchers + # actions(:run) + # end + # @example Use a matcher + # expect(chef_run).to run_my_resource('...') + module ChefspecMatchers + include Poise::Helpers::LWRPPolyfill::Resource + include Poise::Helpers::ResourceName + + # Create a matcher for a given resource type and action. This is + # idempotent so if a matcher already exists, it will not be recreated. + # + # @api private + def self.create_matcher(resource, action) + # Check that we have everything we need. + return unless defined?(ChefSpec) && defined?(RSpec::Matchers) && resource + method = :"#{action}_#{resource}" + return if RSpec::Matchers.method_defined?(method) + RSpec::Matchers.send(:define_method, method) do |resource_name| + ChefSpec::Matchers::ResourceMatcher.new(resource, action, resource_name) + end + end + + # @!classmethods + module ClassMethods + # Create a resource-level matcher for this resource. + # + # @see Resource::ResourceName.provides + def provides(name) + ChefSpec.define_matcher(name) if defined?(ChefSpec) + super + end + + # Create matchers for all declared actions. + # + # @see Resource::LWRPPolyfill.actions + def actions(*names) + super.tap do |actions| + actions.each do |action| + ChefspecMatchers.create_matcher(resource_name, action) + end + end + end + + def included(klass) + super + klass.extend ClassMethods + end + end + + extend ClassMethods + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/defined_in.rb b/cookbooks/poise/files/halite_gem/poise/helpers/defined_in.rb new file mode 100644 index 0000000..18bf9f5 --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/defined_in.rb @@ -0,0 +1,111 @@ +# +# Copyright 2015, Noah Kantrowitz +# +# 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. +# + +require 'poise/error' +require 'poise/utils' + + +module Poise + module Helpers + # A mixin to track where a resource or provider was defined. This can + # provide either the filename of the class or the cookbook it was defined in. + # + # @since 2.0.0 + # @example + # class MyProvider < Chef::provider + # include Poise::Helpers::DefinedIn + # + # def action_create + # template '...' do + # # ... + # cookbook new_resource.poise_defined_in + # end + # end + # end + module DefinedIn + # Wrapper for {.poise_defined_in_cookbook} to pass the run context for you. + # + # @see .poise_defined_in_cookbook + # @param file [String, nil] Optional file path to check instead of the path + # this class was defined in. + # @return [String] + def poise_defined_in_cookbook(file=nil) + self.class.poise_defined_in_cookbook(run_context, file) + end + + # @!classmethods + module ClassMethods + # The file this class or module was defined in, or nil if it isn't found. + # + # @return [String] + def poise_defined_in + raise Poise::Error.new("Unable to determine location of #{self.name}") unless @poise_defined_in + @poise_defined_in + end + + # The cookbook this class or module was defined in. Can pass a file to + # check that instead. + # + # @param run_context [Chef::RunContext] Run context to check cookbooks in. + # @param file [String, nil] Optional file path to check instead of the + # path this class was defined in. + # @return [String] + def poise_defined_in_cookbook(run_context, file=nil) + file ||= poise_defined_in + Chef::Log.debug("[#{self.name}] Checking cookbook name for #{file}") + Poise::Utils.find_cookbook_name(run_context, file).tap do |cookbook| + Chef::Log.debug("[#{self.name}] found cookbook #{cookbook.inspect}") + end + end + + # Record that the class/module was defined. Called automatically by Ruby + # for all normal cases. + # + # @param caller_array [Array] A strack trace returned by #caller. + # @return [void] + def poise_defined!(caller_array) + # Only try to set this once. + return if @poise_defined_in + # Path to ignore, assumes Halite transformation which I'm not thrilled + # about. + poise_libraries = File.expand_path('../..', __FILE__) + # Parse out just the filenames. + caller_array = caller_array.map {|line| line.split(/:/, 2).first } + # Find the first non-poise line. + caller_path = caller_array.find do |line| + !line.start_with?(poise_libraries) + end + Chef::Log.debug("[#{self.name}] Recording poise_defined_in as #{caller_path}") + @poise_defined_in = caller_path + end + + # @api private + def inherited(klass) + super + klass.poise_defined!(caller) + end + + def included(klass) + super + klass.extend(ClassMethods) + klass.poise_defined!(caller) + end + end + + extend ClassMethods + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/fused.rb b/cookbooks/poise/files/halite_gem/poise/helpers/fused.rb new file mode 100644 index 0000000..fa9169b --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/fused.rb @@ -0,0 +1,127 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'chef/provider' + + +module Poise + module Helpers + # Resource mixin to create "fused" resources where the resource and provider + # are implemented in the same class. + # + # @since 2.0.0 + # @example + # class Chef::Resource::MyResource < Chef::Resource + # include Poise(fused: true) + # attribute(:path, kind_of: String) + # attribute(:message, kind_of: String) + # action(:run) do + # file new_resource.path do + # content new_resource.message + # end + # end + # end + module Fused + # Hack is_a? so that the DSL will consider this a Provider for the + # purposes of attaching enclosing_provider. + # + # @api private + # @param klass [Class] + # @return [Boolean] + def is_a?(klass) + if klass == Chef::Provider + # Lies, damn lies, and Ruby code. + true + else + super + end + end + + # Hack provider_for_action so that the resource is also the provider. + # + # @api private + # @param action [Symbol] + # @return [Chef::Provider] + def provider_for_action(action) + provider(self.class.fused_provider_class) unless provider + super + end + + # @!classmethods + module ClassMethods + # Define a provider action. The block should contain the usual provider + # code. + # + # @param name [Symbol] Name of the action. + # @param block [Proc] Action implementation. + # @example + # action(:run) do + # file '/temp' do + # user 'root' + # content 'temp' + # end + # end + def action(name, &block) + fused_actions[name.to_sym] = block + # Make sure this action is allowed, also sets the default if first. + if respond_to?(:actions) + actions(name.to_sym) + end + end + + # Storage accessor for fused action blocks. Maps action name to proc. + # + # @api private + # @return [Hash] + def fused_actions + (@fused_actions ||= {}) + end + + # Create a provider class for the fused actions in this resource. + # Inherits from the fused provider class of the resource's superclass if + # present. + # + # @api private + # @return [Class] + def fused_provider_class + @fused_provider_class ||= begin + provider_superclass = begin + self.superclass.fused_provider_class + rescue NoMethodError + Chef::Provider + end + actions = fused_actions + class_name = self.name + Class.new(provider_superclass) do + include Poise + define_singleton_method(:name) { class_name + ' (fused)' } + actions.each do |action, block| + define_method(:"action_#{action}", &block) + end + end + end + end + + def included(klass) + super + klass.extend(ClassMethods) + end + end + + extend ClassMethods + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/include_recipe.rb b/cookbooks/poise/files/halite_gem/poise/helpers/include_recipe.rb new file mode 100644 index 0000000..227a134 --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/include_recipe.rb @@ -0,0 +1,62 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'poise/helpers/subcontext_block' +require 'poise/subcontext/runner' + + +module Poise + module Helpers + # A provider mixin to add #include_recipe that can be called from action + # methods. + # + # @since 2.0.0 + module IncludeRecipe + include Poise::Helpers::SubcontextBlock + + def include_recipe(*recipes) + loaded_recipes = [] + subcontext = subcontext_block do + recipes.each do |recipe| + case recipe + when String + # Process normally + Chef::Log.debug("Loading recipe #{recipe} via include_recipe (poise)") + loaded_recipes += run_context.include_recipe(recipe) + when Proc + # Pretend its a block of recipe code + fake_recipe = Chef::Recipe.new(cookbook_name, new_resource.recipe_name, run_context) + fake_recipe.instance_eval(&recipe) + loaded_recipes << fake_recipe + end + end + end + # Converge the new context. + Poise::Subcontext::Runner.new(new_resource, subcontext).converge + collection = global_resource_collection + subcontext.resource_collection.each do |r| + Chef::Log.debug("Poise::IncludeRecipe: Adding #{r} to global collection #{collection.object_id}") + # Insert the local resource into the global context + collection.insert(r) + # Skip the iterator forward so we don't double-execute the inserted resource + # If running at compile time, the iterator is nil + collection.iterator.skip_forward if collection.iterator + end + loaded_recipes + end + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/inversion.rb b/cookbooks/poise/files/halite_gem/poise/helpers/inversion.rb new file mode 100644 index 0000000..beb6e22 --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/inversion.rb @@ -0,0 +1,374 @@ +# +# Copyright 2015, Noah Kantrowitz +# +# 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. +# + +require 'chef/node' +require 'chef/node_map' +require 'chef/provider' +require 'chef/resource' + +require 'poise/helpers/defined_in' +require 'poise/error' +require 'poise/helpers/inversion/options_resource' +require 'poise/utils/resource_provider_mixin' + + +module Poise + module Helpers + # A mixin for dependency inversion in Chef. + # + # @since 2.0.0 + module Inversion + autoload :OptionsResource, 'poise/helpers/inversion/options_resource' + autoload :OptionsProvider, 'poise/helpers/inversion/options_provider' + + include Poise::Utils::ResourceProviderMixin + + # Resource implementation for {Poise::Helpers::Inversion}. + # @see Poise::Helpers::Inversion + module Resource + # @overload options(val=nil) + # Set or return provider options for all providers. + # @param val [Hash] Provider options to set. + # @return [Hash] + # @example + # my_resource 'thing_one' do + # options depends: 'thing_two' + # end + # @overload options(provider, val=nil) + # Set or return provider options for a specific provider. + # @param provider [Symbol] Provider to set for. + # @param val [Hash] Provider options to set. + # @return [Hash] + # @example + # my_resource 'thing_one' do + # options :my_provider, depends: 'thing_two' + # end + def options(provider=nil, val=nil) + key = :options + if !val && provider.is_a?(Hash) + val = provider + elsif provider + key = :"options_#{provider}" + end + set_or_return(key, val ? Mash.new(val) : val, kind_of: Hash, default: lazy { Mash.new }) + end + + # Allow setting the provider directly using the same names as the attribute + # settings. + # + # @param val [String, Symbol, Class, nil] Value to set the provider to. + # @return [Class] + # @example + # my_resource 'thing_one' do + # provider :my_provider + # end + def provider(val=nil) + if val && !val.is_a?(Class) + provider_class = Poise::Helpers::Inversion.provider_for(resource_name, node, val) + Chef::Log.debug("[#{self}] Checking for an inversion provider for #{val}: #{provider_class && provider_class.name}") + val = provider_class if provider_class + end + super + end + + # @!classmethods + module ClassMethods + # Options resource class. + attr_reader :inversion_options_resource_class + # Options provider class. + attr_reader :inversion_options_provider_class + + # @overload inversion_options_resource() + # Return the options resource mode for this class. + # @return [Boolean] + # @overload inversion_options_resource(val) + # Set the options resource mode for this class. Set to true to + # automatically create an options resource. Defaults to true. + # @param val [Boolean] Enable/disable setting. + # @return [Boolean] + def inversion_options_resource(val=nil) + @poise_inversion_options_resource = val unless val.nil? + @poise_inversion_options_resource + end + + # Create resource and provider classes for an options resource. + # + # @param name [String, Symbol] DSL name for the base resource. + # @return [void] + def create_inversion_options_resource!(name) + enclosing_class = self + options_resource_name = :"#{name}_options" + # Create the resource class. + @inversion_options_resource_class = Class.new(Chef::Resource) do + include Poise::Helpers::Inversion::OptionsResource + define_singleton_method(:name) do + "#{enclosing_class}::OptionsResource" + end + provides(options_resource_name) + inversion_resource(name) + end + # Create the provider class. + @inversion_options_provider_class = Class.new(Chef::Provider) do + include Poise::Helpers::Inversion::OptionsProvider + define_singleton_method(:name) do + "#{enclosing_class}::OptionsProvider" + end + provides(options_resource_name) + end + end + + # Wrap #provides() to create an options resource if desired. + # + # @param name [Symbol] Resource name + # return [void] + def provides(name) + create_inversion_options_resource!(name) if inversion_options_resource + super if defined?(super) + end + + def included(klass) + super + klass.extend(ClassMethods) + end + end + + extend ClassMethods + end + + # Provider implementation for {Poise::Helpers::Inversion}. + # @see Poise::Helpers::Inversion + module Provider + include DefinedIn + + # Compile all the different levels of inversion options together. + # + # @return [Hash] + # @example + # def action_run + # if options['depends'] + # # ... + # end + # end + def options + @options ||= self.class.inversion_options(node, new_resource) + end + + # @!classmethods + module ClassMethods + # @overload inversion_resource() + # Return the inversion resource name for this class. + # @return [Symbol] + # @overload inversion_resource(val) + # Set the inversion resource name for this class. You can pass either + # a symbol in DSL format or a resource class that uses Poise. This + # name is used to determine which resources the inversion provider is + # a candidate for. + # @param val [Symbol, Class] Name to set. + # @return [Symbol] + def inversion_resource(val=nil) + if val + val = val.resource_name if val.is_a?(Class) + Chef::Log.debug("[#{self.name}] Setting inversion resource to #{val}") + @poise_inversion_resource = val.to_sym + end + @poise_inversion_resource || (superclass.respond_to?(:inversion_resource) ? superclass.inversion_resource : nil) + end + + # @overload inversion_attribute() + # Return the inversion attribute name(s) for this class. + # @return [Array] + # @overload inversion_attribute(val) + # Set the inversion attribute name(s) for this class. This is + # used by {.resolve_inversion_attribute} to load configuration data + # from node attributes. To specify a nested attribute pass an array + # of strings corresponding to the keys. + # @param val [String, Array] Attribute path. + # @return [Array] + def inversion_attribute(val=nil) + if val + # Coerce to an array of strings. + val = Array(val).map {|name| name.to_s } + @poise_inversion_attribute = val + end + @poise_inversion_attribute || (superclass.respond_to?(:inversion_attribute) ? superclass.inversion_attribute : nil) + end + + # Default attribute paths to check for inversion options. Based on + # the cookbook this class and its superclasses are defined in. + # + # @param node [Chef::Node] Node to load from. + # @return [Array>] + def default_inversion_attributes(node) + klass = self + tried = [] + while klass.respond_to?(:poise_defined_in_cookbook) + cookbook = klass.poise_defined_in_cookbook(node.run_context) + if node[cookbook] + return [cookbook] + end + tried << cookbook + klass = klass.superclass + end + raise Poise::Error.new("Unable to find inversion attributes, tried: #{tried.join(', ')}") + end + + # Resolve the node attribute used as the base for inversion options + # for this class. This can be set explicitly with {.inversion_attribute} + # or the default is to use the name of the cookbook the provider is + # defined in. + # + # @param node [Chef::Node] Node to load from. + # @return [Chef::Node::Attribute] + def resolve_inversion_attribute(node) + # Default to using just the name of the cookbook. + attribute_names = inversion_attribute || default_inversion_attributes(node) + attribute_names.inject(node) do |memo, key| + memo[key] || begin + raise Poise::Error.new("Attribute #{key} not set when expanding inversion attribute for #{self.name}: #{memo}") + end + end + end + + # Compile all the different levels of inversion options together. + # + # @param node [Chef::Node] Node to load from. + # @param resource [Chef::Resource] Resource to load from. + # @return [Hash] + def inversion_options(node, resource) + Mash.new.tap do |opts| + attrs = resolve_inversion_attribute(node) + # Cast the run state to a Mash because string vs. symbol keys. I can + # at least promise poise_inversion will be a str so cut down on the + # amount of data to convert. + run_state = Mash.new(node.run_state.fetch('poise_inversion', {}).fetch(inversion_resource, {}))[resource.name] || {} + # Class-level defaults. + opts.update(default_inversion_options(node, resource)) + # Resource options for all providers. + opts.update(resource.options) + # Global provider from node attributes. + opts.update(provider: attrs['provider']) if attrs['provider'] + # Attribute options for all providers. + opts.update(attrs['options']) if attrs['options'] + # Resource options for this provider. + opts.update(resource.options(provides)) + # Attribute options for this resource name. + opts.update(attrs[resource.name]) if attrs[resource.name] + # Options resource options for all providers. + opts.update(run_state['*']) if run_state['*'] + # Options resource options for this provider. + opts.update(run_state[provides]) if run_state[provides] + end + end + + # Default options data for this provider class. + # + # @param node [Chef::Node] Node to load from. + # @param resource [Chef::Resource] Resource to load from. + # @return [Hash] + def default_inversion_options(node, resource) + {} + end + + # Resolve which provider name should be used for a resource. + # + # @param node [Chef::Node] Node to load from. + # @param resource [Chef::Resource] Resource to query. + # @return [String] + def resolve_inversion_provider(node, resource) + inversion_options(node, resource)['provider'] || 'auto' + end + + # Override the normal #provides to set the inversion provider name + # instead of adding to the normal provider map. + # + # @overload provides() + # Return the inversion provider name for the class. + # @return [Symbol] + # @overload provides(name, opts={}, &block) + # Set the inversion provider name for the class. + # @param name [Symbol] Provider name. + # @param opts [Hash] NodeMap filter options. + # @param block [Proc] NodeMap filter proc. + # @return [Symbol] + def provides(name=nil, opts={}, &block) + if name + raise Poise::Error.new("Inversion resource name not set for #{self.name}") unless inversion_resource + @poise_inversion_provider = name + Chef::Log.debug("[#{self.name}] Setting inversion provider name to #{name}") + Poise::Helpers::Inversion.provider_map(inversion_resource).set(name.to_sym, self, opts, &block) + # Set the actual Chef-level provides name for DSL dispatch. + super(inversion_resource) + end + @poise_inversion_provider + end + + # Override the default #provides? to check for our inverted providers. + # + # @api private + # @param node [Chef::Node] Node to use for attribute checks. + # @param resource [Chef::Resource] Resource instance to match. + # @return [Boolean] + def provides?(node, resource) + raise Poise::Error.new("Inversion resource name not set for #{self.name}") unless inversion_resource + return false unless resource.resource_name == inversion_resource + provider_name = resolve_inversion_provider(node, resource) + Chef::Log.debug("[#{resource}] Checking provides? on #{self.name}. Got provider_name #{provider_name.inspect}") + provider_name == provides.to_s || ( provider_name == 'auto' && provides_auto?(node, resource) ) + end + + # Subclass hook to provide auto-detection for providers. + # + # @param node [Chef::Node] Node to check against. + # @param resource [Chef::Resource] Resource to check against. + # @return [Boolean] + def provides_auto?(node, resource) + false + end + + def included(klass) + super + klass.extend(ClassMethods) + end + end + + extend ClassMethods + end + + # The provider map for a given resource type. + # + # @param resource_type [Symbol] Resource type in DSL format. + # @return [Chef::NodeMap] + # @example + # Poise::Helpers::Inversion.provider_map(:my_resource) + def self.provider_map(resource_type) + @provider_maps ||= {} + @provider_maps[resource_type.to_sym] ||= Chef::NodeMap.new + end + + # Find a specific provider class for a resource. + # + # @param resource_type [Symbol] Resource type in DSL format. + # @param node [Chef::Node] Node to use for the lookup. + # @param provider_type [Symbol] Provider type in DSL format. + # @return [Class] + # @example + # Poise::Helpers::Inversion.provider_for(:my_resource, node, :my_provider) + def self.provider_for(resource_type, node, provider_type) + provider_map(resource_type).get(node, provider_type.to_sym) + end + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/inversion/options_provider.rb b/cookbooks/poise/files/halite_gem/poise/helpers/inversion/options_provider.rb new file mode 100644 index 0000000..a18785b --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/inversion/options_provider.rb @@ -0,0 +1,41 @@ +# +# Copyright 2015, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +module Poise + module Helpers + module Inversion + # A mixin for inversion options providers. + # + # @api private + # @since 2.0.0 + # @see Poise::Helper::Inversion + module OptionsProvider + # @api private + def self.included(klass) + klass.class_exec { include Poise } + end + + # A blank run action. + # + # @return [void] + def action_run + # This space left intentionally blank. + end + end + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/inversion/options_resource.rb b/cookbooks/poise/files/halite_gem/poise/helpers/inversion/options_resource.rb new file mode 100644 index 0000000..f24489a --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/inversion/options_resource.rb @@ -0,0 +1,101 @@ +# +# Copyright 2015, Noah Kantrowitz +# +# 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. +# + +require 'chef/mash' + +require 'poise/error' + + +module Poise + module Helpers + module Inversion + # A mixin for inversion options resources. + # + # @api private + # @since 2.0.0 + # @see Poise::Helpers::Inversion + module OptionsResource + include Poise + + # Method missing delegation to allow DSL-style options. + # + # @example + # my_app_options 'app' do + # key1 'value1' + # key2 'value2' + # end + def method_missing(method_sym, *args, &block) + super(method_sym, *args, &block) + rescue NoMethodError + # First time we've seen this key and using it as an rvalue, NOPE.GIF. + raise unless !args.empty? || block || _options[method_sym] + if !args.empty? || block + _options[method_sym] = block || args.first + end + _options[method_sym] + end + + # Insert the options data in to the run state. This has to match the + # layout used in {Poise::Helpers::Inversion::Provider.inversion_options}. + # + # @api private + def after_created + raise Poise::Error.new("Inversion resource name not set for #{self.class.name}") unless self.class.inversion_resource + node.run_state['poise_inversion'] ||= {} + node.run_state['poise_inversion'][self.class.inversion_resource] ||= {} + node.run_state['poise_inversion'][self.class.inversion_resource][resource] ||= {} + node.run_state['poise_inversion'][self.class.inversion_resource][resource][for_provider] ||= {} + node.run_state['poise_inversion'][self.class.inversion_resource][resource][for_provider].update(_options) + end + + module ClassMethods + # @overload inversion_resource() + # Return the inversion resource name for this class. + # @return [Symbol] + # @overload inversion_resource(val) + # Set the inversion resource name for this class. You can pass either + # a symbol in DSL format or a resource class that uses Poise. This + # name is used to determine which resources the inversion provider is + # a candidate for. + # @param val [Symbol, Class] Name to set. + # @return [Symbol] + def inversion_resource(val=nil) + if val + val = val.resource_name if val.is_a?(Class) + Chef::Log.debug("[#{self.name}] Setting inversion resource to #{val}") + @poise_inversion_resource = val.to_sym + end + @poise_inversion_resource || (superclass.respond_to?(:inversion_resource) ? superclass.inversion_resource : nil) + end + + # @api private + def included(klass) + super + klass.extend(ClassMethods) + klass.class_exec do + actions(:run) + attribute(:resource, kind_of: String, name_attribute: true) + attribute(:for_provider, kind_of: [String, Symbol], default: '*') + attribute(:_options, kind_of: Hash, default: lazy { Mash.new }) + end + end + end + + extend ClassMethods + end + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/lazy_default.rb b/cookbooks/poise/files/halite_gem/poise/helpers/lazy_default.rb new file mode 100644 index 0000000..d35dc0c --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/lazy_default.rb @@ -0,0 +1,62 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +module Poise + module Helpers + # Resource mixin to allow lazyily-evaluated defaults in resource attributes. + # This is designed to be used with {LWRPPolyfill} or a similar #attributes + # method. + # + # @since 1.0.0 + # @example + # class MyResource < Chef::Resource + # include Poise::Helpers::LWRPPolyfill + # include Poise::Helpers::LazyDefault + # attribute(:path, default: lazy { name + '_temp' }) + # end + module LazyDefault + # Override the default set_or_return to support lazy evaluation of the + # default value. This only actually matters when it is called from a class + # level context via #attributes. + def set_or_return(symbol, arg, validation) + if validation && validation[:default].is_a?(Chef::DelayedEvaluator) + validation = validation.dup + validation[:default] = instance_eval(&validation[:default]) + end + super(symbol, arg, validation) + end + + # @!classmethods + module ClassMethods + # Create a lazyily-evaluated block. + # + # @param block [Proc] Callable to return the default value. + # @return [Chef::DelayedEvaluator] + def lazy(&block) + Chef::DelayedEvaluator.new(&block) + end + + def included(klass) + super + klass.extend(ClassMethods) + end + end + + extend ClassMethods + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/lwrp_polyfill.rb b/cookbooks/poise/files/halite_gem/poise/helpers/lwrp_polyfill.rb new file mode 100644 index 0000000..a5a815d --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/lwrp_polyfill.rb @@ -0,0 +1,96 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'poise/utils/resource_provider_mixin' + + +module Poise + module Helpers + # A resource and provider mixin to add back some compatability with Chef's + # LWRPBase classes. + # + # @since 1.0.0 + module LWRPPolyfill + include Poise::Utils::ResourceProviderMixin + + # Provide default_action and actions like LWRPBase but better equipped for subclassing. + module Resource + def initialize(*args) + super + # Try to not stomp on stuff if already set in a parent + @action = self.class.default_action if @action == :nothing + (@allowed_actions << self.class.actions).flatten!.uniq! + end + + # @!classmethods + module ClassMethods + def default_action(name=nil) + if name + @default_action = name + actions(name) + end + @default_action || ( respond_to?(:superclass) && superclass.respond_to?(:default_action) && superclass.default_action ) || actions.first || :nothing + end + + def actions(*names) + @actions ||= ( respond_to?(:superclass) && superclass.respond_to?(:actions) ? superclass.actions.dup : [] ) + (@actions << names).flatten!.uniq! + @actions + end + + def attribute(name, opts) + # Ruby 1.8 can go to hell + define_method(name) do |arg=nil, &block| + arg = block if arg.nil? # Try to allow passing either + set_or_return(name, arg, opts) + end + end + + def included(klass) + super + klass.extend(ClassMethods) + end + end + + extend ClassMethods + end + + # Helper to handle load_current_resource for direct subclasses of Provider + module Provider + # @!classmethods + module ClassMethods + def included(klass) + super + klass.extend(ClassMethods) + + # Mask Chef::Provider#load_current_resource because it throws NotImplementedError. + if klass.is_a?(Class) && klass.superclass == Chef::Provider + klass.class_exec do + def load_current_resource + end + end + end + + # Reinstate the Chef DSL, removed in Chef 12. + klass.class_exec { include Chef::DSL::Recipe } + end + end + + extend ClassMethods + end + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/notifying_block.rb b/cookbooks/poise/files/halite_gem/poise/helpers/notifying_block.rb new file mode 100644 index 0000000..38f9cbd --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/notifying_block.rb @@ -0,0 +1,78 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'poise/helpers/subcontext_block' +require 'poise/subcontext/runner' + + +module Poise + module Helpers + # A provider mixin to provide #notifying_block, a scoped form of Chef's + # use_inline_resources. + # + # @since 1.0.0 + # @example + # class MyProvider < Chef::Provider + # include Chef::Helpers::NotifyingBlock + # + # def action_run + # notifying_block do + # template '/etc/myapp.conf' do + # # ... + # end + # end + # end + # end + module NotifyingBlock + include Poise::Helpers::SubcontextBlock + + private + + # Create and converge a subcontext for the recipe DSL. This is similar to + # Chef's use_inline_resources but is scoped to a block. All DSL resources + # declared inside the block will be converged when the block returns, and + # the updated_by_last_action flag will be set if any of the inner + # resources are updated. + # + # @api public + # @param block [Proc] Block to run in the subcontext. + # @return [void] + # @example + # def action_run + # notifying_block do + # template '/etc/myapp.conf' do + # # ... + # end + # end + # end + def notifying_block(&block) + # Make sure to mark the resource as updated-by-last-action if + # any sub-run-context resources were updated (any actual + # actions taken against the system) during the + # sub-run-context convergence. + begin + subcontext = subcontext_block(&block) + # Converge the new context. + Poise::Subcontext::Runner.new(new_resource, subcontext).converge + ensure + new_resource.updated_by_last_action( + subcontext && subcontext.resource_collection.any?(&:updated?) + ) + end + end + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/option_collector.rb b/cookbooks/poise/files/halite_gem/poise/helpers/option_collector.rb new file mode 100644 index 0000000..f66900c --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/option_collector.rb @@ -0,0 +1,131 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'chef/mash' + +require 'poise/error' + + +module Poise + module Helpers + # A resource mixin to add a new kind of attribute, an option collector. + # These attributes can act as mini-DSLs for things which would otherwise be + # key/value pairs. + # + # @since 1.0.0 + # @example Defining an option collector + # class MyResource < Chef::Resource + # include Poise::Helpers::OptionCollector + # attribute(:my_options, option_collector: true) + # end + # @example Using an option collector + # my_resource 'name' do + # my_options do + # key1 'value1' + # key2 'value2' + # end + # end + module OptionCollector + # Instance context used to eval option blocks. + # @api private + class OptionEvalContext + attr_reader :_options + + def initialize(parent) + @parent = parent + @_options = {} + end + + def method_missing(method_sym, *args, &block) + @parent.send(method_sym, *args, &block) + rescue NameError + # Even though method= in the block will set a variable instead of + # calling method_missing, still try to cope in case of self.method=. + method_sym = method_sym.to_s.chomp('=').to_sym + if args.length > 0 || block + @_options[method_sym] = args.first || block + elsif !@_options.include?(method_sym) + # We haven't seen this name before, re-raise the NameError. + raise + end + @_options[method_sym] + end + end + + # @!classmethods + module ClassMethods + # Override the normal #attribute() method to support defining option + # collectors too. + def attribute(name, options={}) + # If present but false-y, make sure it is removed anyway so it + # doesn't confuse ParamsValidate. + if options.delete(:option_collector) + option_collector_attribute(name, options) + else + super + end + end + + # Define an option collector attribute. Normally used via {.attribute}. + # + # @param name [String, Symbol] Name of the attribute to define. + # @param default [Hash] Default value for the options. + # @param parser [Proc, Symbol] Optional parser method. If a symbol it is + # called as a method on self. Takes a non-hash value and returns a + # hash of its parsed representation. + def option_collector_attribute(name, default: {}, parser: nil) + raise Poise::Error.new("Parser must be a Proc or Symbol: #{parser.inspect}") if parser && !(parser.is_a?(Proc) || parser.is_a?(Symbol)) + # Unlike LWRPBase.attribute, I don't care about Ruby 1.8. Worlds tiniest violin. + define_method(name.to_sym) do |arg=nil, &block| + iv_sym = :"@#{name}" + + value = instance_variable_get(iv_sym) || begin + default = instance_eval(&default) if default.is_a?(Chef::DelayedEvaluator) # Handle lazy{} + Mash.new(default) # Wrap in a mash because fuck str vs sym. + end + if arg + if !arg.is_a?(Hash) && parser + arg = case parser + when Proc + instance_exec(arg, &parser) + when Symbol + send(parser, arg) + end + end + raise Exceptions::ValidationFailed, "Option #{name} must be a Hash" if !arg.is_a?(Hash) + # Should this and the update below be a deep merge? + value.update(arg) + end + if block + ctx = OptionEvalContext.new(self) + ctx.instance_exec(&block) + value.update(ctx._options) + end + instance_variable_set(iv_sym, value) + value + end + end + + def included(klass) + super + klass.extend(ClassMethods) + end + end + + extend ClassMethods + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/resource_name.rb b/cookbooks/poise/files/halite_gem/poise/helpers/resource_name.rb new file mode 100644 index 0000000..174ca1a --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/resource_name.rb @@ -0,0 +1,99 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'chef/mixin/convert_to_class_name' + + +module Poise + module Helpers + # A resource mixin to automatically set @resource_name. + # + # @since 1.0.0 + # @example + # class MyResource < Chef::Resource + # include Poise::Helpers::ResourceName + # provides(:my_resource) + # end + module ResourceName + def initialize(*args) + super + # If provides() was explicitly set, unconditionally set @resource_name. + # This helps when subclassing core Chef resources which set it + # themselves in #initialize. + if self.class.resource_name(false) + @resource_name = self.class.resource_name + else + @resource_name ||= self.class.resource_name + end + end + + # @!classmethods + module ClassMethods + # Set the DSL name for the the resource class. + # + # @param name [Symbol] Name of the resource. + # @return [void] + # @example + # class MyResource < Chef::Resource + # include Poise::Resource::ResourceName + # provides(:my_resource) + # end + def provides(name) + # Patch self.constantize so this can cope with anonymous classes. + # This does require that the anonymous class define self.name though. + if self.name && respond_to?(:constantize) + old_constantize = instance_method(:constantize) + define_singleton_method(:constantize) do |const_name| + ( const_name == self.name ) ? self : old_constantize.bind(self).call(const_name) + end + end + # Store the name for later. + @provides_name = name + # Call the original if present. The defined? is for old Chef. + super if defined?(super) + end + + # Retreive the DSL name for the resource class. If not set explicitly + # via {provides} this will try to auto-detect based on the class name. + # + # @param auto [Boolean] Try to auto-detect based on class name. + # @return [Symbol] + def resource_name(auto=true) + return @provides_name if @provides_name + @provides_name || if name && name.start_with?('Chef::Resource') + Chef::Mixin::ConvertToClassName.convert_to_snake_case(name, 'Chef::Resource').to_sym + elsif name + Chef::Mixin::ConvertToClassName.convert_to_snake_case(name.split('::').last).to_sym + end + end + + # Used by Resource#to_text to find the human name for the resource. + # + # @api private + def dsl_name + resource_name.to_s + end + + def included(klass) + super + klass.extend(ClassMethods) + end + end + + extend ClassMethods + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/subcontext_block.rb b/cookbooks/poise/files/halite_gem/poise/helpers/subcontext_block.rb new file mode 100644 index 0000000..b6b5bbe --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/subcontext_block.rb @@ -0,0 +1,58 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'poise/subcontext/resource_collection' + + +module Poise + module Helpers + # A provider mixin to help with creating subcontexts. Mostly for internal + # use within Poise. + # + # @since 1.0.0 + module SubcontextBlock + private + + def subcontext_block(parent_context=nil, &block) + # Setup a sub-run-context. + parent_context ||= @run_context + sub_run_context = parent_context.dup + sub_run_context.resource_collection = Poise::Subcontext::ResourceCollection.new(parent_context.resource_collection) + + # Declare sub-resources within the sub-run-context. Since they + # are declared here, they do not pollute the parent run-context. + begin + outer_run_context = @run_context + @run_context = sub_run_context + instance_eval(&block) + ensure + @run_context = outer_run_context + end + + # Return the inner context to do other things with + sub_run_context + end + + def global_resource_collection + collection = @run_context.resource_collection + while collection.respond_to?(:parent) && collection.parent + collection = collection.parent + end + collection + end + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/subresources.rb b/cookbooks/poise/files/halite_gem/poise/helpers/subresources.rb new file mode 100644 index 0000000..909a5b3 --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/subresources.rb @@ -0,0 +1,29 @@ +# +# Copyright 2015, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +module Poise + module Helpers + # Mixins and helpers for managing subresources, resources with a + # parent/child relationship. + # + # @since 2.0.0 + module Subresources + autoload :Child, 'poise/helpers/subresources/child' + autoload :Container, 'poise/helpers/subresources/container' + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/subresources/child.rb b/cookbooks/poise/files/halite_gem/poise/helpers/subresources/child.rb new file mode 100644 index 0000000..a5fc9ea --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/subresources/child.rb @@ -0,0 +1,217 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'chef/resource' + +require 'poise/error' +require 'poise/helpers/subresources/default_containers' + + +module Poise + module Helpers + module Subresources + # A resource mixin for child subresources. + # + # @since 1.0.0 + module Child + # Little class used to fix up the display of subresources in #to_text. + # Without this you get the full parent resource shown for @parent et al. + # @api private + class ParentRef + attr_accessor :resource + + def initialize(resource) + @resource = resource + end + + def to_text + if @resource.nil? + 'nil' + else + @resource.to_s + end + end + end + + # @overload parent() + # Get the parent resource for this child. This may be nil if the + # resource is set to parent_optional = true. + # @return [Chef::Resource, nil] + # @overload parent(val) + # Set the parent resource. The parent can be set as resource + # object, a string (either a bare resource name or a type[name] + # string), or a type:name hash. + # @param val [String, Hash, Chef::Resource] Parent resource to set. + # @return [Chef::Resource, nil] + def parent(val=nil) + _parent(:parent, self.class.parent_type, self.class.parent_optional, self.class.parent_auto, val) + end + + # Register ourself with parents in case this is not a nested resource. + # + # @api private + def after_created + super + self.class.parent_attributes.each_key do |name| + parent = self.send(name) + parent.register_subresource(self) if parent + end + end + + private + + # Generic form of the parent getter/setter. + # + # @since 2.0.0 + # @see #parent + def _parent(name, parent_type, parent_optional, parent_auto, val=nil) + # Allow using a DSL symbol as the parent type. + if parent_type.is_a?(Symbol) + parent_type = Chef::Resource.resource_for_node(parent_type, node) + end + # Grab the ivar for local use. + parent_ref = instance_variable_get(:"@#{name}") + if val + if val.is_a?(String) && !val.include?('[') + raise Poise::Error.new('Cannot use a string parent without defining a parent type') if parent_type == Chef::Resource + val = "#{parent_type.resource_name}[#{val}]" + end + if val.is_a?(String) || val.is_a?(Hash) + parent = @run_context.resource_collection.find(val) + else + parent = val + end + if !parent.is_a?(parent_type) + raise Poise::Error.new("Parent resource is not an instance of #{parent_type.name}: #{val.inspect}") + end + parent_ref = ParentRef.new(parent) + elsif !parent_ref + if parent_auto + # Automatic sibling lookup for sequential composition. + # Find the last instance of the parent class as the default parent. + # This is super flaky and should only be a last resort. + parent = Poise::Helpers::Subresources::DefaultContainers.find(parent_type, run_context) + end + # Can't find a valid parent, if it wasn't optional raise an error. + raise Poise::Error.new("No parent found for #{self}") unless parent || parent_optional + parent_ref = ParentRef.new(parent) + else + parent = parent_ref.resource + end + # Store the ivar back. + instance_variable_set(:"@#{name}", parent_ref) + # Return the actual resource. + parent + end + + module ClassMethods + # @overload parent_type() + # Get the class of the default parent link on this resource. + # @return [Class, Symbol] + # @overload parent_type(type) + # Set the class of the default parent link on this resource. + # @param type [Class, Symbol] Class to set. + # @return [Class, Symbol] + def parent_type(type=nil) + if type + raise Poise::Error.new("Parent type must be a class, symbol, or true, got #{type.inspect}") unless type.is_a?(Class) || type.is_a?(Symbol) || type == true + @parent_type = type + end + @parent_type || (superclass.respond_to?(:parent_type) ? superclass.parent_type : Chef::Resource) + end + + # @overload parent_optional() + # Get the optional mode for the default parent link on this resource. + # @return [Boolean] + # @overload parent_optional(val) + # Set the optional mode for the default parent link on this resource. + # @param val [Boolean] Mode to set. + # @return [Boolean] + def parent_optional(val=nil) + unless val.nil? + @parent_optional = val + end + if @parent_optional.nil? + superclass.respond_to?(:parent_optional) ? superclass.parent_optional : false + else + @parent_optional + end + end + + # @overload parent_auto() + # Get the auto-detect mode for the default parent link on this resource. + # @return [Boolean] + # @overload parent_auto(val) + # Set the auto-detect mode for the default parent link on this resource. + # @param val [Boolean] Mode to set. + # @return [Boolean] + def parent_auto(val=nil) + unless val.nil? + @parent_auto = val + end + if @parent_auto.nil? + superclass.respond_to?(:parent_auto) ? superclass.parent_auto : true + else + @parent_auto + end + end + + # Create a new kind of parent link. + # + # @since 2.0.0 + # @param name [Symbol] Name of the relationship. This becomes a method + # name on the resource instance. + # @param type [Class] Class of the parent. + # @param optional [Boolean] If the parent is optional. + # @param auto [Boolean] If the parent is auto-detected. + # @return [void] + def parent_attribute(name, type: Chef::Resource, optional: false, auto: true) + name = :"parent_#{name}" + (@parent_attributes ||= {})[name] = type + define_method(name) do |val=nil| + _parent(name, type, optional, auto, val) + end + end + + # Return the name of all parent relationships on this class. + # + # @since 2.0.0 + # @return [Hash] + def parent_attributes + {}.tap do |attrs| + # Grab superclass's attributes if possible. + attrs.update(superclass.parent_attributes) if superclass.respond_to?(:parent_attributes) + # Local default parent. + attrs[:parent] = parent_type + # Extra locally defined parents. + attrs.update(@parent_attributes) if @parent_attributes + # Remove anything with the type set to true. + attrs.reject! {|name, type| type == true } + end + end + + # @api private + def included(klass) + super + klass.extend(ClassMethods) + end + end + + extend ClassMethods + end + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/subresources/container.rb b/cookbooks/poise/files/halite_gem/poise/helpers/subresources/container.rb new file mode 100644 index 0000000..6616303 --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/subresources/container.rb @@ -0,0 +1,165 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'chef/dsl/recipe' + +require 'poise/helpers/subcontext_block' +require 'poise/helpers/subresources/default_containers' + + +module Poise + module Helpers + module Subresources + # A resource mixin for subresource containers. + # + # @since 1.0.0 + module Container + # A resource collection that has much more condensed text output. This + # is used to show the value of @subresources during Chef's error formatting. + # @api private + class NoPrintingResourceCollection < Chef::ResourceCollection + def to_text + "[#{all_resources.map(&:to_s).join(', ')}]" + end + end + + include SubcontextBlock + include Chef::DSL::Recipe + + attr_reader :subresources + + def initialize(*args) + super + @subresources = NoPrintingResourceCollection.new + end + + def after_created + super + # Register + Poise::Helpers::Subresources::DefaultContainers.register!(self, run_context) + unless @subresources.empty? + Chef::Log.debug("[#{self}] Adding subresources to collection:") + # Because after_create is run before adding the container to the resource collection + # we need to jump through some hoops to get it swapped into place. + self_ = self + order_fixer = Chef::Resource::RubyBlock.new('subresource_order_fixer', @run_context) + order_fixer.block do + collection = self_.run_context.resource_collection + # Delete the current container resource from its current position. + collection.all_resources.delete(self_) + # Replace the order fixer with the container so it runs before all + # subresources. + collection.all_resources[collection.iterator.position] = self_ + # Hack for Chef 11 to reset the resources_by_name position too. + # @todo Remove this when I drop support for Chef 11. + if resources_by_name = collection.instance_variable_get(:@resources_by_name) + resources_by_name[self_.to_s] = collection.iterator.position + end + # Step back so we re-run the "current" resource, which is now the + # container. + collection.iterator.skip_back + end + @run_context.resource_collection.insert(order_fixer) + @subresources.each do |r| + Chef::Log.debug(" * #{r}") + @run_context.resource_collection.insert(r) + end + end + end + + def declare_resource(type, name, created_at=nil, &block) + Chef::Log.debug("[#{self}] Creating subresource from #{type}(#{name})") + self_ = self + # Used to break block context, non-local return from subcontext_block. + resource = [] + # Grab the caller so we can make the subresource look like it comes from + # correct place. + created_at ||= caller[0] + # Run this inside a subcontext to avoid adding to the current resource collection. + # It will end up added later, indirected via @subresources to ensure ordering. + subcontext_block do + namespace = if self.class.container_namespace == true + # If the value is true, use the name of the container resource. + self.name + elsif self.class.container_namespace.is_a?(Proc) + instance_eval(&self.class.container_namespace) + else + self.class.container_namespace + end + sub_name = if name && !name.empty? + if namespace + "#{namespace}::#{name}" + else + name + end + else + # If you pass in nil or '', you just get the namespace or parent name. + namespace || self.name + end + resource << super(type, sub_name, created_at) do + # Apply the correct parent before anything else so it is available + # in after_created for the subresource. + parent(self_) if respond_to?(:parent) + # Run the resource block. + instance_exec(&block) if block + end + end + # Try and add to subresources. For normal subresources this is handled + # in the after_created. + register_subresource(resource.first) if resource.first + # Return whatever we have + resource.first + end + + def register_subresource(resource) + subresources.lookup(resource) + rescue Chef::Exceptions::ResourceNotFound + Chef::Log.debug("[#{self}] Adding #{resource} to subresources") + subresources.insert(resource) + end + + private + + # Thanks Array.flatten, big help you are. Specifically the + # method_missing in the recipe DSL will make a flatten on an array of + # resources fail, so make this safe. + def to_ary + nil + end + + # @!classmethods + module ClassMethods + def container_namespace(val=nil) + @container_namespace = val unless val.nil? + if @container_namespace.nil? + # Not set here, look at the superclass of true by default for backwards compat. + superclass.respond_to?(:container_namespace) ? superclass.container_namespace : true + else + @container_namespace + end + end + + def included(klass) + super + klass.extend(ClassMethods) + end + end + + extend ClassMethods + end + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/subresources/default_containers.rb b/cookbooks/poise/files/halite_gem/poise/helpers/subresources/default_containers.rb new file mode 100644 index 0000000..eb66da0 --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/subresources/default_containers.rb @@ -0,0 +1,73 @@ +# +# Copyright 2015, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +module Poise + module Helpers + module Subresources + # Helpers to track default container resources. This is used to find a + # default parent for a child with no parent set. It flat out violates + # encapsulation to allow for the use of default parents to act as + # system-level defaults even when created in a nested scope. + # + # @api private + # @since 2.0.0 + module DefaultContainers + # Mutex to sync access to the containers array. + # + # @see .containers + CONTAINER_MUTEX = Mutex.new + + # Add a resource to the array of default containers. + # + # @param resource [Chef::Resource] Resource to add. + # @param run_context [Chef::RunContext] Context of the current run. + # @return [void] + def self.register!(resource, run_context) + CONTAINER_MUTEX.synchronize do + containers(run_context) << resource + end + end + + # Find a default container for a resource class. + # + # @param klass [Class] Resource class to search for. + # @param run_context [Chef::RunContext] Context of the current run. + # @return [Chef::Resource] + def self.find(klass, run_context) + CONTAINER_MUTEX.synchronize do + containers(run_context).reverse_each do |resource| + return resource if resource.is_a?(klass) + end + # Nothing found. + nil + end + end + + private + + # Get the array of all default container resources. + # + # @note MUST BE CALLED FROM A LOCKED CONTEXT! + # @param run_context [Chef::RunContext] Context of the current run. + # @return [Array] + def self.containers(run_context) + run_context.node.run_state[:poise_default_containers] ||= [] + end + end + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/helpers/template_content.rb b/cookbooks/poise/files/halite_gem/poise/helpers/template_content.rb new file mode 100644 index 0000000..f59ad5d --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/helpers/template_content.rb @@ -0,0 +1,165 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'chef/provider/template_finder' +require 'chef/mixin/template' + +require 'poise/helpers/lazy_default' +require 'poise/helpers/lwrp_polyfill' +require 'poise/helpers/option_collector' +require 'poise/utils' + + +module Poise + module Helpers + # A resource mixin to add a new kind of attribute, template content. TODO + # + # @since 1.0.0 + module TemplateContent + include LazyDefault + include LWRPPolyfill + include OptionCollector + + # @!classmethods + module ClassMethods + def attribute(name, options={}) + if options.delete(:template) + name_prefix = name.empty? ? '' : "#{name}_" + + # If you are reading this, I'm so sorry + # This is used for computing the default cookbook below + parent_filename = caller.first.reverse.split(':', 4).last.reverse + + # If our parent class also declared a template_content attribute on the same name, inherit its options + if superclass.respond_to?("_#{name_prefix}_template_content_options") + options = superclass.send("_#{name_prefix}_template_content_options").merge(options) + end + + # Template source path if using a template + attribute("#{name_prefix}source", kind_of: String) + define_method("_#{name_prefix}source") do + send("#{name_prefix}source") || maybe_eval(options[:default_source]) + end + + # Template cookbook name if using a template + attribute("#{name_prefix}cookbook", kind_of: [String, Symbol], default: lazy do + if send("#{name_prefix}source") + cookbook_name + elsif options[:default_cookbook] + maybe_eval(options[:default_cookbook]) + else + Poise::Utils.find_cookbook_name(run_context, parent_filename) + end + end) + + # Template variables if using a template + attribute("#{name_prefix}options", option_collector: true) + + # The big one, get/set content, but if you are getting and no + # explicit content was given, try to render the template + define_method("#{name_prefix}content") do |arg=nil, no_compute=false| + ret = set_or_return("#{name_prefix}content", arg, kind_of: String) + if !ret && !arg && !no_compute + ret = send("_#{name_prefix}content") + # Cache the results for next time + set_or_return("#{name_prefix}content", ret, {}) if ret + end + ret + end + + # Validate that arguments work + define_method("_#{name_prefix}validate") do + if options[:required] && !send("_#{name_prefix}source") && !send("#{name_prefix}content", nil, true) + raise Chef::Exceptions::ValidationFailed, "#{self}: One of #{name_prefix}source or #{name_prefix}content is required" + end + if send("#{name_prefix}source") && send("#{name_prefix}content", nil, true) + raise Chef::Exceptions::ValidationFailed, "#{self}: Only one of #{name_prefix}source or #{name_prefix}content can be specified" + end + end + + # Monkey patch #after_create to run best-effort validation. Arguments + # could be changed after creation, but this gives nicer errors for + # most cases. + unless options[:no_validate_on_create] + old_after_created = instance_method(:after_created) + define_method(:after_created) do + old_after_created.bind(self).call + send("_#{name_prefix}validate") + end + end + + # Compile the needed content + define_method("_#{name_prefix}content") do + # Run validation again + send("_#{name_prefix}validate") + # Get all the relevant parameters + content = send("#{name_prefix}content", nil, true) + source = send("_#{name_prefix}source") + if content + content # I don't think it can ever hit this branch + elsif source + cookbook = send("#{name_prefix}cookbook") + template_options = send("#{name_prefix}options") + send("_#{name_prefix}render_template", source, cookbook, template_options) + else + maybe_eval(options[:default]) + end + end + + # Actually render a template + define_method("_#{name_prefix}render_template") do |source, cookbook, template_options| + all_template_options = {} + all_template_options.update(maybe_eval(options[:default_options])) if options[:default_options] + all_template_options.update(template_options) + all_template_options[:new_resource] = self + finder = Chef::Provider::TemplateFinder.new(run_context, cookbook, node) + context = Chef::Mixin::Template::TemplateContext.new(all_template_options) + context[:node] = node + context[:template_finder] = finder + context.render_template(finder.find(source)) + end + + # Used to check if a parent class already defined a template_content thing here + define_singleton_method("_#{name_prefix}_template_content_options") do + options + end + else + super if defined?(super) + end + end + + def included(klass) + super + klass.extend(ClassMethods) + end + end + + extend ClassMethods + + private + + # Evaluate lazy blocks if needed + def maybe_eval(val) + if val.is_a?(Chef::DelayedEvaluator) + instance_eval(&val) + else + val + end + end + + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/provider.rb b/cookbooks/poise/files/halite_gem/poise/provider.rb new file mode 100644 index 0000000..275870f --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/provider.rb @@ -0,0 +1,55 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'poise/helpers' + + +module Poise + # Master provider mixin for Poise-based providers. + # + # @since 1.0.0 + # @example Default helpers. + # class MyProvider < Chef::Provider + # include Poise::Provider + # end + # @example With optional helpers. + # class MyProvider < Chef::Provider + # include Poise::Provider + # poise_inversion(MyResource) + # end + module Provider + include Poise::Helpers::DefinedIn + include Poise::Helpers::IncludeRecipe + include Poise::Helpers::LWRPPolyfill + include Poise::Helpers::NotifyingBlock + + # @!classmethods + module ClassMethods + def poise_inversion(resource, attribute=nil) + include Poise::Helpers::Inversion + inversion_resource(resource) + inversion_attribute(attribute) if attribute + end + + def included(klass) + super + klass.extend(ClassMethods) + end + end + + extend ClassMethods + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/resource.rb b/cookbooks/poise/files/halite_gem/poise/resource.rb new file mode 100644 index 0000000..e11558b --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/resource.rb @@ -0,0 +1,75 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'poise/helpers' + + +module Poise + # Master resource mixin for Poise-based resources. + # + # @since 1.0.0 + # @example Default helpers. + # class MyResource < Chef::Resource + # include Poise::Resource + # end + # @example With optional helpers. + # class MyResource < Chef::Resource + # include Poise::Resource + # poise_subresource(MyParent) + # poise_fused + # end + module Resource + include Poise::Helpers::ChefspecMatchers + include Poise::Helpers::DefinedIn + include Poise::Helpers::LazyDefault + include Poise::Helpers::LWRPPolyfill + include Poise::Helpers::OptionCollector + include Poise::Helpers::ResourceName + include Poise::Helpers::TemplateContent + + # @!classmethods + module ClassMethods + def poise_subresource_container(namespace=nil) + include Poise::Helpers::Subresources::Container + # false is a valid value. + container_namespace(namespace) unless namespace.nil? + end + + def poise_subresource(parent_type=nil, parent_optional=nil, parent_auto=nil) + include Poise::Helpers::Subresources::Child + parent_type(parent_type) if parent_type + parent_optional(parent_optional) unless parent_optional.nil? + parent_auto(parent_auto) unless parent_auto.nil? + end + + def poise_fused + include Poise::Helpers::Fused + end + + def poise_inversion(options_resource=nil) + include Poise::Helpers::Inversion + inversion_options_resource(true) unless options_resource == false + end + + def included(klass) + super + klass.extend(ClassMethods) + end + end + + extend ClassMethods + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/subcontext.rb b/cookbooks/poise/files/halite_gem/poise/subcontext.rb new file mode 100644 index 0000000..424a9b3 --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/subcontext.rb @@ -0,0 +1,27 @@ +# +# Copyright 2015, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +module Poise + # Helpers and whatnot for dealing with subcontexts. + # + # @api private + # @since 2.0.0 + module Subcontext + autoload :ResourceCollection, 'poise/subcontext/resource_collection' + autoload :Runner, 'poise/subcontext/runner' + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/subcontext/resource_collection.rb b/cookbooks/poise/files/halite_gem/poise/subcontext/resource_collection.rb new file mode 100644 index 0000000..4c25653 --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/subcontext/resource_collection.rb @@ -0,0 +1,56 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'chef/resource_collection' + + +module Poise + module Subcontext + # A subclass of the normal Chef ResourceCollection that creates a partially + # isolated set of resources. Notifications and other resources lookups can + # propagate out to parent contexts but not back in. This is used to allow + # black-box resources that are still aware of things in upper contexts. + # + # @api private + # @since 1.0.0 + class ResourceCollection < Chef::ResourceCollection + attr_accessor :parent + + def initialize(parent) + @parent = parent + super() + end + + def lookup(resource) + super + rescue Chef::Exceptions::ResourceNotFound + @parent.lookup(resource) + end + + # Iterate and expand all nested contexts + def recursive_each(&block) + if @parent + if @parent.respond_to?(:recursive_each) + @parent.recursive_each(&block) + else + @parent.each(&block) + end + end + each(&block) + end + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/subcontext/runner.rb b/cookbooks/poise/files/halite_gem/poise/subcontext/runner.rb new file mode 100644 index 0000000..5020a41 --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/subcontext/runner.rb @@ -0,0 +1,50 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +require 'chef/runner' + + +module Poise + module Subcontext + # A subclass of the normal Chef Runner that migrates delayed notifications + # to the enclosing run_context instead of running them at the end of the + # subcontext convergence. + # + # @api private + # @since 1.0.0 + class Runner < Chef::Runner + def initialize(resource, *args) + super(*args) + @resource = resource + end + + def run_delayed_notifications(error=nil) + # If there is an error, just do the normal thing. The return shouldn't + # ever fire because the superclass re-raises if there is an error. + return super if error + delayed_actions.each do |notification| + notifications = run_context.delayed_notifications(@resource) + if run_context.delayed_notifications(@resource).any? { |existing_notification| existing_notification.duplicates?(notification) } + Chef::Log.info( "#{@resource} not queuing delayed action #{notification.action} on #{notification.resource}"\ + " (delayed), as it's already been queued") + else + notifications << notification + end + end + end + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/utils.rb b/cookbooks/poise/files/halite_gem/poise/utils.rb new file mode 100644 index 0000000..5cf4cbf --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/utils.rb @@ -0,0 +1,70 @@ +# +# Copyright 2015, Noah Kantrowitz +# +# 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. +# + +require 'poise/error' + + +module Poise + module Utils + autoload :ResourceProviderMixin, 'poise/utils/resource_provider_mixin' + + extend self + + # Find the cookbook name for a given filename. The can used to find the + # cookbook that corresponds to a caller of a file. + # + # @param run_context [Chef::RunContext] Context to check. + # @param filename [String] Absolute filename to check for. + # @return [String] + # @example + # def my_thing + # caller_filename = caller.first.split(':').first + # cookbook = Poise::Utils.find_cookbook_name(run_context, caller_filename) + # # ... + # end + def find_cookbook_name(run_context, filename) + possibles = {} + Chef::Log.debug("[Poise] Checking cookbook for #{filename.inspect}") + run_context.cookbook_collection.each do |name, ver| + # This special method is added by Halite::Gem#as_cookbook_version. + if ver.respond_to?(:halite_root) + # The join is there because ../poise-ruby/lib starts with ../poise so + # we want a trailing /. + Chef::Log.debug("") + if filename.start_with?(File.join(ver.halite_root, '')) + Chef::Log.debug("[Poise] Found matching halite_root in #{name}: #{ver.halite_root.inspect}") + possibles[ver.halite_root] = name + end + else + Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |seg| + ver.segment_filenames(seg).each do |file| + # Put this behind an environment variable because it is verbose + # even for normal debugging-level output. + Chef::Log.debug("[Poise] Checking #{seg} in #{name}: #{file.inspect}") if ENV['POISE_DEBUG'] + if file == filename + Chef::Log.debug("[Poise] Found matching #{seg} in #{name}: #{file.inspect}") + possibles[file] = name + end + end + end + end + end + raise Poise::Error.new("Unable to find cookbook for file #{filename.inspect}") if possibles.empty? + # Sort the items by matching path length, pick the name attached to the longest. + possibles.sort_by{|key, value| key.length }.last[1] + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/utils/resource_provider_mixin.rb b/cookbooks/poise/files/halite_gem/poise/utils/resource_provider_mixin.rb new file mode 100644 index 0000000..908dd30 --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/utils/resource_provider_mixin.rb @@ -0,0 +1,65 @@ +# +# Copyright 2015, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +module Poise + module Utils + # A mixin to dispatch other mixins with resource and provider + # implementations. The module this is included in must have Resource and + # Provider sub-modules. + # + # @since 2.0.0 + # @example + # module MyHelper + # include Poise::Utils::ResourceProviderMixin + # module Resource + # # ... + # end + # + # module Provider + # # ... + # end + # end + module ResourceProviderMixin + def self.included(klass) + # Warning here be dragons. + # Create a new anonymous module, klass will be the module that + # actually included ResourceProviderMixin. We want to keep a reference + # to that locked down so that we can close over it and use it in the + # "real" .included defined below to find the original relative consts. + mod = Module.new do + # Use define_method instead of def so we can close over klass and mod. + define_method(:included) do |inner_klass| + # Has to be explicit because super inside define_method. + super(inner_klass) + # Cargo this .included to things which include us. + inner_klass.extend(mod) + # Dispatch to submodules, inner_klass is the most recent includer. + if inner_klass < Chef::Resource + # Use klass::Resource to look up relative to the original module. + inner_klass.class_exec { include klass::Resource } + elsif inner_klass < Chef::Provider + # As above, klass::Provider. + inner_klass.class_exec { include klass::Provider } + end + end + end + # Add our .included to the original includer. + klass.extend(mod) + end + end + end +end diff --git a/cookbooks/poise/files/halite_gem/poise/version.rb b/cookbooks/poise/files/halite_gem/poise/version.rb new file mode 100644 index 0000000..e7d3576 --- /dev/null +++ b/cookbooks/poise/files/halite_gem/poise/version.rb @@ -0,0 +1,20 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +module Poise + VERSION = '2.0.1' +end diff --git a/cookbooks/poise/libraries/default.rb b/cookbooks/poise/libraries/default.rb new file mode 100644 index 0000000..bb69a2e --- /dev/null +++ b/cookbooks/poise/libraries/default.rb @@ -0,0 +1,18 @@ +# +# Copyright 2013-2015, Noah Kantrowitz +# +# 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. +# + +raise 'Halite is not compatible with no_lazy_load false, please set no_lazy_load true in your Chef configuration file.' unless Chef::Config[:no_lazy_load] +$LOAD_PATH << File.expand_path('../../files/halite_gem', __FILE__) diff --git a/cookbooks/poise/metadata.json b/cookbooks/poise/metadata.json new file mode 100644 index 0000000..fa7cd1a --- /dev/null +++ b/cookbooks/poise/metadata.json @@ -0,0 +1 @@ +{"name":"poise","version":"2.0.1","description":"Helpers for writing extensible Chef cookbooks.","long_description":"# Poise\n\n[![Build Status](https://img.shields.io/travis/poise/poise.svg)](https://travis-ci.org/poise/poise)\n[![Gem Version](https://img.shields.io/gem/v/poise.svg)](https://rubygems.org/gems/poise)\n[![Cookbook Version](https://img.shields.io/cookbook/v/poise.svg)](https://supermarket.chef.io/cookbooks/poise)\n[![Coverage](https://img.shields.io/codecov/c/github/poise/poise.svg)](https://codecov.io/github/poise/poise)\n[![Gemnasium](https://img.shields.io/gemnasium/poise/poise.svg)](https://gemnasium.com/poise/poise)\n[![License](https://img.shields.io/badge/license-Apache_2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)\n\n## What is Poise?\n\nThe poise cookbook is a set of libraries for writing reusable cookbooks. It\nproviders helpers for common patterns and a standard structure to make it easier to create flexible cookbooks.\n\n## Writing your first resource\n\nRather than LWRPs, Poise promotes the idea of using normal, or \"heavy weight\"\nresources, while including helpers to reduce much of boilerplate needed for this. Each resource goes in its own file under `libraries/` named to match\nthe resource, which is in turn based on the class name. This means that the file `libraries/my_app.rb` would contain `Chef::Resource::MyApp` which maps to the resource `my_app`.\n\nAn example of a simple shell to start from:\n\n```ruby\nclass Chef\n class Resource::MyApp < Resource\n include Poise\n\n actions(:enable)\n\n attribute(:path, kind_of: String)\n ... # Other attribute definitions\n end\n\n class Provider::MyApp < Provider\n include Poise\n\n def action_enable\n converge_by(\"enable resource #{new_resource.name}\") do\n notifying_block do\n ... # Normal Chef recipe code goes here\n end\n end\n end\n end\nend\n```\n\nStarting from the top, first we declare the resource class, which inherits from\n`Chef::Resource`. This is similar to the `resources/` file in an LWRP, and a similar DSL can be used. In order to load the helpers into the class, we\ninclude the `Poise` mixin. Then we use the familiar DSL, though with a few additions we'll cover later.\n\nThen we declare the provider class, again similar to the `providers/` file in an LWRP. We include the `Poise` mixin again to get access to all the helpers. Rather than use the `action :enable do ... end` DSL from LWRPs, we just define the action method directly, and use the `converge_by` method to provide a description of what the action does. The implementation of action comes from a block of recipe code wrapped with `notifying_block` to capture changes in much the same way as `use_inline_resources`, see below for more information about all the features of `notifying_block`.\n\nWe can then use this resource like any other Chef resource:\n\n```ruby\nmy_app 'one' do\n path '/tmp'\nend\n```\n\n## Helpers\n\nWhile not exposed as a specific method, Poise will automatically set the\n`resource_name` based on the class name.\n\n### Notifying Block\n\nAs mentioned above, `notifying_block` is similar to `use_inline_resources` in LWRPs. Any Chef resource created inside the block will be converged in a sub-context and if any have updated it will trigger notifications on the current resource. Unlike `use_inline_resources`, resources inside the sub-context can still see resources outside of it, with lookups propagating up sub-contexts until a match is found. Also any delayed notifications are scheduled to run at the end of the main converge cycle, instead of the end of this inner converge.\n\nThis can be used to write action methods using the normal Chef recipe DSL, while still offering more flexibility through subclassing and other forms of code reuse.\n\n### Include Recipe\n\nIn keeping with `notifying_block` to implement action methods using the Chef DSL, Poise adds an `include_recipe` helper to match the method of the same name in recipes. This will load and converge the requested recipe.\n\n### Resource DSL\n\nTo make writing resource classes easier, Poise exposes a DSL similar to LWRPs for defining actions and attributes. Both `actions` and\n`default_action` are just like in LWRPs, though `default_action` is rarely needed as the first action becomes the default. `attribute` is also available just like in LWRPs, but with some enhancements noted below.\n\nOne notable difference over the standard DSL method is that Poise attributes\ncan take a block argument.\n\n#### Template Content\n\nA common pattern with resources is to allow passing either a template filename or raw file content to be used in a configuration file. Poise exposes a new attribute flag to help with this behavior:\n\n```ruby\nattribute(:name, template: true)\n```\n\nThis creates four methods on the class, `name_source`, `name_cookbook`,\n`name_content`, and `name_options`. If the name is set to `''`, no prefix is applied to the function names. The content method can be set directly, but if not set and source is set, then it will render the template and return it as a string. Default values can also be set for any of these:\n\n```ruby\nattribute(:name, template: true, default_source: 'app.cfg.erb',\n default_options: {host: 'localhost'})\n```\n\nAs an example, you can replace this:\n\n```ruby\nif new_resource.source\n template new_resource.path do\n source new_resource.source\n owner 'app'\n group 'app'\n variables new_resource.options\n end\nelse\n file new_resource.path do\n content new_resource.content\n owner 'app'\n group 'app'\n end\nend\n```\n\nwith simply:\n\n```ruby\nfile new_resource.path do\n content new_resource.content\n owner 'app'\n group 'app'\nend\n```\n\nAs the content method returns the rendered template as a string, this can also\nbe useful within other templates to build from partials.\n\n#### Lazy Initializers\n\nOne issue with Poise-style resources is that when the class definition is executed, Chef hasn't loaded very far so things like the node object are not\nyet available. This means setting defaults based on node attributes does not work directly:\n\n```ruby\nattribute(:path, default: node['myapp']['path'])\n...\nNameError: undefined local variable or method 'node'\n```\n\nTo work around this, Poise extends the idea of lazy initializers from Chef recipes to work with resource definitions as well:\n\n```ruby\nattribute(:path, default: lazy { node['myapp']['path'] })\n```\n\nThese initializers are run in the context of the resource object, allowing\ncomplex default logic to be moved to a method if desired:\n\n```ruby\nattribute(:path, default: lazy { my_default_path })\n\ndef my_default_path\n ...\nend\n```\n\n#### Option Collector\n\nAnother common pattern with resources is to need a set of key/value pairs for\nconfiguration data or options. This can done with a simple Hash, but an option collector attribute can offer a nicer syntax:\n\n```ruby\nattribute(:mydata, option_collector: true)\n...\n\nmy_app 'name' do\n mydata do\n key1 'value1'\n key2 'value2'\n end\nend\n```\n\nThis will be converted to `{key1: 'value1', key2: 'value2'}`. You can also pass a Hash to an option collector attribute just as you would with a normal attribute.\n\n## Sponsors\n\nThe Poise test server infrastructure is generously sponsored by [Rackspace](https://rackspace.com/). Thanks Rackspace!\n\n## License\n\nCopyright 2013-2015, Noah Kantrowitz\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","maintainer":"YOUR_COMPANY_NAME","maintainer_email":"YOUR_EMAIL","license":"none","platforms":{},"dependencies":{},"recommendations":{},"suggestions":{},"conflicting":{},"providing":{},"replacing":{},"attributes":{},"groupings":{},"recipes":{}} \ No newline at end of file diff --git a/cookbooks/postfix/CHANGELOG.md b/cookbooks/postfix/CHANGELOG.md new file mode 100644 index 0000000..173cbad --- /dev/null +++ b/cookbooks/postfix/CHANGELOG.md @@ -0,0 +1,167 @@ +postfix Cookbook CHANGELOG +========================== +This file is used to list changes made in each version of the postfix cookbook. + +v3.6.2 (2014-10-31) +------------------- +- Fix FreeBSDisms + +v3.6.1 (2014-10-28) +------------------- +- Fix documentation around node['postfix']['main']['relayhost'] attribute +- Fix logic around include_recipe 'postfix::virtual_aliases_domains' + +v3.6.0 (2014-08-25) +------------------- +- restart postfix after updating virtual alias templates #86 +- fixing typo for alias_db location in omnios +- moving conditional attributes to a recipe so they can be modified + via other cookbook attributes + +v3.5.0 (2014-08-25) +------------------- +Adding virtual_domains functionality + +v3.4.1 (2014-08-20) +------------------- +Removing unused parameters from main.cf + +v3.4.0 (2014-07-25) +------------------- +Refactoring to fix some logic issues + +v3.3.1 (2014-06-11) +------------------- +Reverting #37 - [COOK-3418] Virtual Domain Support PR - duplicate of #55 + + +v3.3.0 (2014-06-11) +------------------- +- #37 - [COOK-3418] - Virtual Domain Support +- #44 - Fix minor formatting issue in attributes +- #55 - Add support for virtual aliases +- #57 - Fixing attributes bug in README +- #64 - add smtp_generic maps configuration option +- #66 - [COOK-3652] Add support for transport mappings +- #67 - [COOK-4662] Added support for access control +- #68 - Properly handle binding to loopback on mixed IPV4/IPV6 systems + + +v3.2.0 (2014-05-09) +------------------- +- [COOK-4619] - no way to unset recipient_delimiter + + +v3.1.8 (2014-03-27) +------------------- +- [COOK-4410] - Fix sender_canonical configuration by adding template + and postmap execution + + +v3.1.6 (2014-03-19) +------------------- +- [COOK-4423] - use platform_family, find cert.pem on rhel + + +v3.1.4 (2014-02-27) +------------------- +[COOK-4329] Migrate minitest PITs to latest test-kitchen + serverspec + + +v3.1.2 (2014-02-19) +------------------- +### Bug +- **[COOK-4357](https://tickets.opscode.com/browse/COOK-4357)** - postfix::sasl_auth recipe fails to converge + + +v3.1.0 (2014-02-19) +------------------- +### Bug +- **[COOK-4322](https://tickets.opscode.com/browse/COOK-4322)** - Postfix cookbook has incorrect default path for sasl_passwd + +### New Feature +- **[COOK-4086](https://tickets.opscode.com/browse/COOK-4086)** - use conf_dir attribute for sasl recipe, and add omnios support +- **[COOK-2551](https://tickets.opscode.com/browse/COOK-2551)** - Support creating the sender_canonical map file + + +v3.0.4 +------ +### Bug +- **[COOK-3824](https://tickets.opscode.com/browse/COOK-3824)** - main.cf.erb mishandles lists + +### Improvement +- **[COOK-3822](https://tickets.opscode.com/browse/COOK-3822)** - postfix cookbook readme has an incorrect example +- Got rubocop errors down to 32 + +### New Feature +- **[COOK-2551](https://tickets.opscode.com/browse/COOK-2551)** - Support creating the sender_canonical map file + + +v3.0.2 +------ +### Bug +- **[COOK-3617](https://tickets.opscode.com/browse/COOK-3617)** - Fix error when no there is no FQDN +- **[COOK-3530](https://tickets.opscode.com/browse/COOK-3530)** - Update `client.rb` after 3.0.0 refactor +- **[COOK-2499](https://tickets.opscode.com/browse/COOK-2499)** - Do not use resource cloning + +### Improvement +- **[COOK-3116](https://tickets.opscode.com/browse/COOK-3116)** - Add SmartOS support + + +v3.0.0 +------ +### Improvement +- **[COOK-3328](https://tickets.opscode.com/browse/COOK-3328)** - Postfix main/master and attributes refactor + +**Breaking changes**: +- Attributes are namespaced as `node['postfix']`, `node['postfix']['main']`, and `node['postfix']['master']`. + +v2.1.6 +------ +### Bug +- [COOK-2501]: Reference to `['postfix']['domain']` should be `['postfix']['mydomain']` +- [COOK-2715]: master.cf uses old name for `smtp_fallback_relay` (`fallback_relay`) parameter in master.cf + +v2.1.4 +------ +- [COOK-2281] - postfix aliases uses require_recipe statement + +v2.1.2 +------ +- [COOK-2010] - postfix sasl_auth does not include the sasl plain package + +v2.1.0 +------ +- [COOK-1233] - optional configuration for canonical maps +- [COOK-1660] - allow comma separated arrays in aliases +- [COOK-1662] - allow inet_interfaces configuration via attribute + +v2.0.0 +------ +This version uses platform_family attribute, making the cookbook incompatible with older versions of Chef/Ohai, hence the major version bump. + +- [COOK-1535] - `smtpd_cache` should be in `data_directory`, not `queue_directory` +- [COOK-1790] - /etc/aliases template is only in ubuntu directory +- [COOK-1792] - add minitest-chef tests to postfix cookbook + +v1.2.2 +------ +- [COOK-1442] - Missing ['postfix']['domain'] Attribute causes initial installation failure +- [COOK-1520] - Add support for procmail delivery +- [COOK-1528] - Make aliasses template less specific +- [COOK-1538] - Add iptables_rule template +- [COOK-1540] - Add smtpd_milters and non_smtpd_milters parameters to main.cf + +v1.2.0 +------ +- [COOK-880] - add client/server roles for search-based discovery of relayhost + +v1.0.0 +------ +- [COOK-668] - RHEL/CentOS/Scientific/Amazon platform support +- [COOK-733] - postfix::aliases recipe to manage /etc/aliases +- [COOK-821] - add README.md :) + +v0.8.4 +------ +- Current public release. diff --git a/cookbooks/postfix/README.md b/cookbooks/postfix/README.md new file mode 100644 index 0000000..bce9a32 --- /dev/null +++ b/cookbooks/postfix/README.md @@ -0,0 +1,289 @@ +postfix Cookbook +================ +Installs and configures postfix for client or outbound relayhost, or to do SASL authentication. + +On RHEL-family systems, sendmail will be replaced with postfix. + + +Requirements +------------ +### Platforms +- Ubuntu 10.04+ +- Debian 6.0+ +- RHEL/CentOS/Scientific 5.7+, 6.2+ +- Amazon Linux (as of AMIs created after 4/9/2012) + +May work on other platforms with or without modification. + + +Attributes +---------- +See `attributes/default.rb` for default values. + +### Generic cookbook attributes +* `node['postfix']['mail_type']` - Sets the kind of mail configuration. `master` will set up a server (relayhost). +* `node['postfix']['relayhost_role']` - name of a role used for search in the client recipe. +* `node['postfix']['multi_environment_relay']` - set to true if nodes should not constrain search for the relayhost in their own environment. +* `node['postfix']['use_procmail']` - set to true if nodes should use procmail as the delivery agent. +* `node['postfix']['use_alias_maps']` - set to true if you want the cookbook to use/configure alias maps +* `node['postfix']['use_transport_maps']` - set to true if you want the cookbook to use/configure transport maps +* `node['postfix']['use_access_maps']` - set to true if you want the cookbook to use/configure access maps +* `node['postfix']['use_virtual_aliases']` - set to true if you want the cookbook to use/configure virtual alias maps +* `node['postfix']['aliases']` - hash of aliases to create with `recipe[postfix::aliases]`, see below under __Recipes__ for more information. +* `node['postfix']['transports']` - hash of transports to create with `recipe[postfix::transports]`, see below under __Recipes__ for more information. +* `node['postfix']['access']` - hash of access to create with `recipe[postfix::access]`, see below under __Recipes__ for more information. +* `node['postfix']['virtual_aliases']` - hash of virtual_aliases to create with `recipe[postfix::virtual_aliases]`, see below under __Recipes__ for more information. +* `node['postfix']['main_template_source']` - Cookbook source for main.cf template. Default 'postfix' +* `node['postfix']['master_template_source']` - Cookbook source for master.cf template. Default 'postfix' + +### main.cf and sasl\_passwd template attributes +The main.cf template has been simplified to include any attributes in the `node['postfix']['main']` data structure. The following attributes are still included with this cookbook to maintain some semblance of backwards compatibility. + +This change in namespace to `node['postfix']['main']` should allow for greater flexibility, given the large number of configuration variables for the postfix daemon. All of these cookbook attributes correspond to the option of the same name in `/etc/postfix/main.cf`. + +* `node['postfix']['main']['biff']` - (yes/no); default no +* `node['postfix']['main']['append_dot_mydomain']` - (yes/no); default no +* `node['postfix']['main']['myhostname']` - defaults to fqdn from Ohai +* `node['postfix']['main']['mydomain']` - defaults to domain from Ohai +* `node['postfix']['main']['myorigin']` - defaults to $myhostname +* `node['postfix']['main']['mynetworks']` - default is nil, which forces Postfix to default to loopback addresses. +* `node['postfix']['main']['inet_interfaces']` - set to `loopback-only`, or `all` for server recipe +* `node['postfix']['main']['alias_maps']` - set to `hash:/etc/aliases` +* `node['postfix']['main']['mailbox_size_limit']` - set to `0` (disabled) +* `node['postfix']['main']['mydestination']` - default fqdn, hostname, localhost.localdomain, localhost +* `node['postfix']['main']['smtpd_use_tls']` - (yes/no); default yes. See conditional cert/key attributes. + - `node['postfix']['main']['smtpd_tls_cert_file']` - conditional attribute, set to full path of server's x509 certificate. + - `node['postfix']['main']['smtpd_tls_key_file']` - conditional attribute, set to full path of server's private key + - `node['postfix']['main']['smtpd_tls_CAfile']` - set to platform specific CA bundle + - `node['postfix']['main']['smtpd_tls_session_cache_database']` - set to `btree:${data_directory}/smtpd_scache` +* `node['postfix']['main']['smtp_use_tls']` - (yes/no); default yes. See following conditional attributes. + - `node['postfix']['main']['smtp_tls_CAfile']` - set to platform specific CA bundle + - `node['postfix']['main']['smtp_tls_session_cache_database']` - set to `btree:${data_directory}/smtpd_scache` +* `node['postfix']['main']['smtp_sasl_auth_enable']` - (yes/no); default no. If enabled, see following conditional attributes. + - `node['postfix']['main']['smtp_sasl_password_maps']` - Set to `hash:/etc/postfix/sasl_passwd` template file + - `node['postfix']['main']['smtp_sasl_security_options']` - Set to noanonymous + - `node['postfix']['main']['relayhost']` - Set to empty string + - `node['postfix']['sasl']['smtp_sasl_user_name']` - SASL user to authenticate as. Default empty + - `node['postfix']['sasl']['smtp_sasl_passwd']` - SASL password to use. Default empty. +* `node['postfix']['sender_canonical_map_entries']` - (hash with key value pairs); default not configured. Setup generic canonical maps. See `man 5 canonical`. If has at least one value, then will be enabled in config. +* `node['postfix']['smtp_generic_map_entries']` - (hash with key value pairs); default not configured. Setup generic postfix maps. See `man 5 generic`. If has at least one value, then will be enabled in config. + +Example of json role config, for setup *_map_entries: + +`postfix : {` + +`...` + +`"smtp_generic_map_entries" : { "root@youinternaldomain.local" : "admin@example.com", "admin@youinternaldomain.local" : "admin@example.com" }` + +`}` + +### master.cf template attributes +* `node['postfix']['master']['submission'] - Whether to use submission (TCP 587) daemon. (true/false); default false + + +Recipes +------- +### default +Installs the postfix package and manages the service and the main configuration files (`/etc/postfix/main.cf` and `/etc/postfix/master.cf`). See __Usage__ and __Examples__ to see how to affect behavior of this recipe through configuration. Depending on the `node['postfix']['use_alias_maps']`, `node['postfix']['use_transport_maps']`, `node['postfix']['use_access_maps']` and `node['postfix']['use_virtual_aliases']` attributes the default recipe can call additional recipes to manage additional postfix configuration files + +For a more dynamic approach to discovery for the relayhost, see the `client` and `server` recipes below. + +### client +Use this recipe to have nodes automatically search for the mail relay based which node has the `node['postfix']['relayhost_role']` role. Sets the `node['postfix']['main']['relayhost']` attribute to the first result from the search. + +Includes the default recipe to install, configure and start postfix. + +Does not work with `chef-solo`. + +### sasl\_auth +Sets up the system to authenticate with a remote mail relay using SASL authentication. + +### server +To use Chef Server search to automatically detect a node that is the relayhost, use this recipe in a role that will be relayhost. By default, the role should be "relayhost" but you can change the attribute `node['postfix']['relayhost_role']` to modify this. + +**Note** This recipe will set the `node['postfix']['mail_type']` to "master" with an override attribute. + +### aliases +Manage `/etc/aliases` with this recipe. Currently only Ubuntu 10.04 platform has a template for the aliases file. Add your aliases template to the `templates/default` or to the appropriate platform+version directory per the File Specificity rules for templates. Then specify a hash of aliases for the `node['postfix']['aliases']` attribute. + +Arrays are supported as alias values, since postfix supports comma separated values per alias, simply specify your alias as an array to use this handy feature. + +### aliases +Manage `/etc/aliases` with this recipe. + +### transports +Manage `/etc/postfix/transport` with this recipe. + +### access +Manage `/etc/postfix/access` with this recipe. + +### virtual_aliases +Manage `/etc/postfix/virtual` with this recipe. + + +http://wiki.opscode.com/display/chef/Templates#Templates-TemplateLocationSpecificity + + +Usage +----- +On systems that should simply send mail directly to a relay, or out to the internet, use `recipe[postfix]` and modify the `node['postfix']['main']['relayhost']` attribute via a role. + +On systems that should be the MX for a domain, set the attributes accordingly and make sure the `node['postfix']['mail_type']` attribute is `master`. See __Examples__ for information on how to use `recipe[postfix::server]` to do this automatically. + +If you need to use SASL authentication to send mail through your ISP (such as on a home network), use `postfix::sasl_auth` and set the appropriate attributes. + +For each of these implementations, see __Examples__ for role usage. + + +### Examples +The example roles below only have the relevant postfix usage. You may have other contents depending on what you're configuring on your systems. + +The `base` role is applied to all nodes in the environment. + +```ruby +name "base" +run_list("recipe[postfix]") +override_attributes( + "postfix" => { + "mail_type" => "client", + "main" => { + "mydomain" => "example.com", + "myorigin" => "example.com", + "relayhost" => "[smtp.example.com]", + "smtp_use_tls" => "no" + } + } +) +``` + +The `relayhost` role is applied to the nodes that are relayhosts. Often this is 2 systems using a CNAME of `smtp.example.com`. + +```ruby +name "relayhost" +run_list("recipe[postfix::server]") +override_attributes( + "postfix" => { + "mail_type" => "master", + "main" => { + "mynetworks" => [ "10.3.3.0/24", "127.0.0.0/8" ], + "inet-interfaces" => "all", + "mydomain" => "example.com", + "myorigin" => "example.com" + } +) +``` + +The `sasl_relayhost` role is applied to the nodes that are relayhosts and require authenticating with SASL. For example this might be on a household network with an ISP that otherwise blocks direct internet access to SMTP. + +```ruby +name "sasl_relayhost" +run_list("recipe[postfix], recipe[postfix::sasl_auth]") +override_attributes( + "postfix" => { + "mail_type" => "master", + "main" => { + "mynetworks" => "10.3.3.0/24", + "mydomain" => "example.com", + "myorigin" => "example.com", + "relayhost" => "[smtp.comcast.net]:587", + "smtp_sasl_auth_enable" => "yes" + }, + "sasl" => { + "smtp_sasl_passwd" => "your_password", + "smtp_sasl_user_name" => "your_username" + } + } +) +``` + +For an example of using encrypted data bags to encrypt the SASL password, see the following blog post: + +* http://jtimberman.github.com/blog/2011/08/06/encrypted-data-bag-for-postfix-sasl-authentication/ + +#### Examples using the client & server recipes +If you'd like to use the more dynamic search based approach for discovery, use the server and client recipes. First, create a relayhost role. + +```ruby +name "relayhost" +run_list("recipe[postfix::server]") +override_attributes( + "postfix" => { + "main" => { + "mynetworks" => "10.3.3.0/24", + "mydomain" => "example.com", + "myorigin" => "example.com" + } + } +) +``` + +Then, add the `postfix::client` recipe to the run list of your `base` role or equivalent role for postfix clients. + +```ruby +name "base" +run_list("recipe[postfix::client]") +override_attributes( + "postfix" => { + "mail_type" => "client", + "main" => { + "mydomain" => "example.com", + "myorigin" => "example.com" + } + } +) +``` + +If you wish to use a different role name for the relayhost, then also set the attribute in the `base` role. For example, `postfix_master` as the role name: + +```ruby +name "postfix_master" +description "a role for postfix master that isn't relayhost" +run_list("recipe[postfix::server]") +override_attributes( + "postfix" => { + "main" => { + "mynetworks" => "10.3.3.0/24", + "mydomain" => "example.com", + "myorigin" => "example.com" + } + } +) +``` + +The base role would look something like this: + +```ruby +name "base" +run_list("recipe[postfix::client]") +override_attributes( + "postfix" => { + "relayhost_role" => "postfix_master", + "mail_type" => "client", + "main" => { + "mydomain" => "example.com", + "myorigin" => "example.com" + } + } +) +``` + +License & Authors +----------------- +- Author:: Joshua Timberman + +```text +Copyright:: 2009-2014, Chef Software, Inc + +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/cookbooks/postfix/attributes/default.rb b/cookbooks/postfix/attributes/default.rb new file mode 100644 index 0000000..e826932 --- /dev/null +++ b/cookbooks/postfix/attributes/default.rb @@ -0,0 +1,137 @@ +# encoding: utf-8 +# Author:: Joshua Timberman +# Copyright:: Copyright 2009-2014, Chef Software, Inc. +# 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. + +# Generic cookbook attributes +default['postfix']['mail_type'] = 'client' +default['postfix']['relayhost_role'] = 'relayhost' +default['postfix']['multi_environment_relay'] = false +default['postfix']['use_procmail'] = false +default['postfix']['use_alias_maps'] = (node['platform'] == 'freebsd') +default['postfix']['use_transport_maps'] = false +default['postfix']['use_access_maps'] = false +default['postfix']['use_virtual_aliases'] = false +default['postfix']['use_virtual_aliases_domains'] = false +default['postfix']['transports'] = {} +default['postfix']['access'] = {} +default['postfix']['virtual_aliases'] = {} +default['postfix']['virtual_aliases_domains'] = {} +default['postfix']['main_template_source'] = 'postfix' +default['postfix']['master_template_source'] = 'postfix' +default['postfix']['sender_canonical_map_entries'] = {} +default['postfix']['smtp_generic_map_entries'] = {} +default['postfix']['access_db_type'] = 'hash' +default['postfix']['aliases_db_type'] = 'hash' +default['postfix']['transport_db_type'] = 'hash' +default['postfix']['virtual_alias_db_type'] = 'hash' +default['postfix']['virtual_alias_domains_db_type'] = 'hash' + +case node['platform'] +when 'smartos' + default['postfix']['conf_dir'] = '/opt/local/etc/postfix' + default['postfix']['aliases_db'] = '/opt/local/etc/postfix/aliases' + default['postfix']['transport_db'] = '/opt/local/etc/postfix/transport' + default['postfix']['access_db'] = '/opt/local/etc/postfix/access' + default['postfix']['virtual_alias_db'] = '/opt/local/etc/postfix/virtual' + default['postfix']['virtual_alias_domains_db'] = '/opt/local/etc/postfix/virtual_domains' +when 'freebsd' + default['postfix']['conf_dir'] = '/usr/local/etc/postfix' + default['postfix']['aliases_db'] = '/etc/aliases' + default['postfix']['transport_db'] = '/usr/local/etc/postfix/transport' + default['postfix']['access_db'] = '/usr/local/etc/postfix/access' + default['postfix']['virtual_alias_db'] = '/usr/local/etc/postfix/virtual' + default['postfix']['virtual_alias_domains_db'] = '/usr/local/etc/postfix/virtual_domains' +when 'omnios' + default['postfix']['conf_dir'] = '/opt/omni/etc/postfix' + default['postfix']['aliases_db'] = '/opt/omni/etc/postfix/aliases' + default['postfix']['transport_db'] = '/opt/omni/etc/postfix/transport' + default['postfix']['access_db'] = '/opt/omni/etc/postfix/access' + default['postfix']['virtual_alias_db'] = '/etc/omni/etc/postfix/virtual' + default['postfix']['virtual_alias_domains_db'] = '/etc/omni/etc/postfix/virtual_domains' + default['postfix']['uid'] = 11 +else + default['postfix']['conf_dir'] = '/etc/postfix' + default['postfix']['aliases_db'] = '/etc/aliases' + default['postfix']['transport_db'] = '/etc/postfix/transport' + default['postfix']['access_db'] = '/etc/postfix/access' + default['postfix']['virtual_alias_db'] = '/etc/postfix/virtual' + default['postfix']['virtual_alias_domains_db'] = '/etc/postfix/virtual_domains' +end + +# Non-default main.cf attributes +default['postfix']['main']['biff'] = 'no' +default['postfix']['main']['append_dot_mydomain'] = 'no' +default['postfix']['main']['myhostname'] = (node['fqdn'] || node['hostname']).to_s.chomp('.') +default['postfix']['main']['mydomain'] = (node['domain'] || node['hostname']).to_s.chomp('.') +default['postfix']['main']['myorigin'] = '$myhostname' +default['postfix']['main']['mydestination'] = [node['postfix']['main']['myhostname'], node['hostname'], 'localhost.localdomain', 'localhost'].compact +default['postfix']['main']['smtpd_use_tls'] = 'yes' +default['postfix']['main']['smtp_use_tls'] = 'yes' +default['postfix']['main']['smtp_sasl_auth_enable'] = 'no' +default['postfix']['main']['mailbox_size_limit'] = 0 +default['postfix']['main']['mynetworks'] = nil +default['postfix']['main']['inet_interfaces'] = 'loopback-only' + +# Conditional attributes, also reference _attributes recipe +case node['platform_family'] +when 'smartos' + default['postfix']['main']['smtpd_use_tls'] = 'no' + default['postfix']['main']['smtp_use_tls'] = 'no' + default['postfix']['cafile'] = '/opt/local/etc/postfix/cacert.pem' +when 'rhel' + default['postfix']['cafile'] = '/etc/pki/tls/cert.pem' +else + default['postfix']['cafile'] = "#{node['postfix']['conf_dir']}/cacert.pem" +end + +# # Default main.cf attributes according to `postconf -d` +# default['postfix']['main']['relayhost'] = '' +# default['postfix']['main']['milter_default_action'] = 'tempfail' +# default['postfix']['main']['milter_protocol'] = '6' +# default['postfix']['main']['smtpd_milters'] = '' +# default['postfix']['main']['non_smtpd_milters'] = '' +# default['postfix']['main']['sender_canonical_classes'] = nil +# default['postfix']['main']['recipient_canonical_classes'] = nil +# default['postfix']['main']['canonical_classes'] = nil +# default['postfix']['main']['sender_canonical_maps'] = nil +# default['postfix']['main']['recipient_canonical_maps'] = nil +# default['postfix']['main']['canonical_maps'] = nil + +# Master.cf attributes +default['postfix']['master']['submission'] = false + + +# OS Aliases +case node['platform'] +when 'freebsd' + default['postfix']['aliases'] = { + 'MAILER-DAEMON' => 'postmaster', + 'bin' => 'root', + 'daemon' => 'root', + 'named' => 'root', + 'nobody' => 'root', + 'uucp' => 'root', + 'www' => 'root', + 'ftp-bugs' => 'root', + 'postfix' => 'root', + 'manager' => 'root', + 'dumper' => 'root', + 'operator' => 'root', + 'abuse' => 'postmaster' + } +else + default['postfix']['aliases'] = {} +end diff --git a/cookbooks/postfix/files/default/tests/minitest/support/helpers.rb b/cookbooks/postfix/files/default/tests/minitest/support/helpers.rb new file mode 100644 index 0000000..f306f2e --- /dev/null +++ b/cookbooks/postfix/files/default/tests/minitest/support/helpers.rb @@ -0,0 +1,25 @@ +# encoding: utf-8 +# Copyright 2012-2014, Chef Software, Inc. +# +# 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. +# + +# helpers +module Helpers + # postfix + module Postfix + include MiniTest::Chef::Assertions + include MiniTest::Chef::Context + include MiniTest::Chef::Resources + end +end diff --git a/cookbooks/postfix/metadata.json b/cookbooks/postfix/metadata.json new file mode 100644 index 0000000..256f842 --- /dev/null +++ b/cookbooks/postfix/metadata.json @@ -0,0 +1,89 @@ +{ + "name": "postfix", + "version": "3.6.2", + "description": "Installs and configures postfix for client or outbound relayhost, or to do SASL auth", + "long_description": "", + "maintainer": "Chef Software, Inc.", + "maintainer_email": "cookbooks@getchef.com", + "license": "Apache 2.0", + "platforms": { + "ubuntu": ">= 0.0.0", + "debian": ">= 0.0.0", + "redhat": ">= 0.0.0", + "centos": ">= 0.0.0", + "amazon": ">= 0.0.0", + "scientific": ">= 0.0.0", + "smartos": ">= 0.0.0" + }, + "dependencies": { + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + "postfix/main": { + "display_name": "postfix/main", + "description": "Hash of Postfix main.cf attributes", + "type": "hash" + }, + "postfix/aliases": { + "display_name": "Postfix Aliases", + "description": "Hash of Postfix aliases mapping a name to a value. Example 'root' => 'operator@example.com'. See aliases man page for details.", + "type": "hash" + }, + "postfix/transports": { + "display_name": "Postfix Transports", + "description": "Hash of Postfix transports mapping a destination to a smtp server. Example 'my.domain' => 'smtp:outbound-relay.my.domain'. See transport man page for details.", + "type": "hash" + }, + "postfix/access": { + "display_name": "Postfix Access Table", + "description": "Hash of Postfix accesses mapping a pattern to a action. Example 'domain.tld' => 'OK'. See access man page for details.", + "type": "hash" + }, + "postfix/mail_type": { + "display_name": "Postfix Mail Type", + "description": "Is this node a client or server?", + "default": "client" + }, + "postfix/smtp_sasl_user_name": { + "display_name": "Postfix SMTP SASL Username", + "description": "User to auth SMTP via SASL", + "default": "" + }, + "postfix/smtp_sasl_passwd": { + "display_name": "Postfix SMTP SASL Password", + "description": "Password for smtp_sasl_user_name", + "default": "" + }, + "postfix/relayhost_role": { + "display_name": "Postfix Relayhost's role", + "description": "String containing the role name", + "default": "relayhost" + }, + "postfix/use_procmail": { + "display_name": "Postfix Use procmail?", + "description": "Whether procmail should be used as the local delivery agent for a server", + "default": "no" + } + }, + "groupings": { + }, + "recipes": { + "postfix": "Installs and configures postfix", + "postfix::sasl_auth": "Set up postfix to auth to a server with sasl", + "postfix::aliases": "Manages /etc/aliases", + "postfix::transports": "Manages /etc/postfix/transport", + "postfix::access": "Manages /etc/postfix/access", + "postfix::virtual_aliases": "Manages /etc/postfix/virtual", + "postfix::client": "Searches for the relayhost based on an attribute", + "postfix::server": "Sets the mail_type attribute to master" + } +} \ No newline at end of file diff --git a/cookbooks/postfix/metadata.rb b/cookbooks/postfix/metadata.rb new file mode 100644 index 0000000..f47c338 --- /dev/null +++ b/cookbooks/postfix/metadata.rb @@ -0,0 +1,64 @@ +# encoding: utf-8 +name 'postfix' +description 'Installs and configures postfix for client or outbound relayhost, or to do SASL auth' +maintainer 'Chef Software, Inc.' +maintainer_email 'cookbooks@getchef.com' +license 'Apache 2.0' +version '3.6.2' +recipe 'postfix', 'Installs and configures postfix' +recipe 'postfix::sasl_auth', 'Set up postfix to auth to a server with sasl' +recipe 'postfix::aliases', 'Manages /etc/aliases' +recipe 'postfix::transports', 'Manages /etc/postfix/transport' +recipe 'postfix::access', 'Manages /etc/postfix/access' +recipe 'postfix::virtual_aliases', 'Manages /etc/postfix/virtual' +recipe 'postfix::client', 'Searches for the relayhost based on an attribute' +recipe 'postfix::server', 'Sets the mail_type attribute to master' + +%w(ubuntu debian redhat centos amazon scientific smartos).each do |os| + supports os +end + +attribute 'postfix/main', + display_name: 'postfix/main', + description: 'Hash of Postfix main.cf attributes', + type: 'hash' + +attribute 'postfix/aliases', + display_name: 'Postfix Aliases', + description: "Hash of Postfix aliases mapping a name to a value. Example 'root' => 'operator@example.com'. See aliases man page for details.", + type: 'hash' + +attribute 'postfix/transports', + display_name: 'Postfix Transports', + description: "Hash of Postfix transports mapping a destination to a smtp server. Example 'my.domain' => 'smtp:outbound-relay.my.domain'. See transport man page for details.", + type: 'hash' + +attribute 'postfix/access', + display_name: 'Postfix Access Table', + description: "Hash of Postfix accesses mapping a pattern to a action. Example 'domain.tld' => 'OK'. See access man page for details.", + type: 'hash' + +attribute 'postfix/mail_type', + display_name: 'Postfix Mail Type', + description: 'Is this node a client or server?', + default: 'client' + +attribute 'postfix/smtp_sasl_user_name', + display_name: 'Postfix SMTP SASL Username', + description: 'User to auth SMTP via SASL', + default: '' + +attribute 'postfix/smtp_sasl_passwd', + display_name: 'Postfix SMTP SASL Password', + description: 'Password for smtp_sasl_user_name', + default: '' + +attribute 'postfix/relayhost_role', + display_name: "Postfix Relayhost's role", + description: 'String containing the role name', + default: 'relayhost' + +attribute 'postfix/use_procmail', + display_name: 'Postfix Use procmail?', + description: 'Whether procmail should be used as the local delivery agent for a server', + default: 'no' diff --git a/cookbooks/postfix/recipes/_attributes.rb b/cookbooks/postfix/recipes/_attributes.rb new file mode 100644 index 0000000..0a84eea --- /dev/null +++ b/cookbooks/postfix/recipes/_attributes.rb @@ -0,0 +1,60 @@ +# encoding: utf-8 +# Copyright:: Copyright 2012-2014, Chef Software, Inc. +# +# 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. +# + +if node['postfix']['use_procmail'] + node.default['postfix']['main']['mailbox_command'] = '/usr/bin/procmail -a "$EXTENSION"' +end + +if node['postfix']['main']['smtpd_use_tls'] == 'yes' + node.default['postfix']['main']['smtpd_tls_cert_file'] = '/etc/ssl/certs/ssl-cert-snakeoil.pem' + node.default['postfix']['main']['smtpd_tls_key_file'] = '/etc/ssl/private/ssl-cert-snakeoil.key' + node.default['postfix']['main']['smtpd_tls_CAfile'] = node['postfix']['cafile'] + node.default['postfix']['main']['smtpd_tls_session_cache_database'] = 'btree:${data_directory}/smtpd_scache' +end + +if node['postfix']['main']['smtp_use_tls'] == 'yes' + node.default['postfix']['main']['smtp_tls_CAfile'] = node['postfix']['cafile'] + node.default['postfix']['main']['smtp_tls_session_cache_database'] = 'btree:${data_directory}/smtp_scache' +end + +if node['postfix']['main']['smtp_sasl_auth_enable'] == 'yes' + node.default['postfix']['sasl_password_file'] = "#{node['postfix']['conf_dir']}/sasl_passwd" + node.default['postfix']['main']['smtp_sasl_password_maps'] = "hash:#{node['postfix']['sasl_password_file']}" + node.default['postfix']['main']['smtp_sasl_security_options'] = 'noanonymous' + node.default['postfix']['sasl']['smtp_sasl_user_name'] = '' + node.default['postfix']['sasl']['smtp_sasl_passwd'] = '' + node.default['postfix']['main']['relayhost'] = '' +end + +if node['postfix']['use_alias_maps'] + node.default['postfix']['main']['alias_maps'] = ["hash:#{node['postfix']['aliases_db']}"] +end + +if node['postfix']['use_transport_maps'] + node.default['postfix']['main']['transport_maps'] = ["hash:#{node['postfix']['transport_db']}"] +end + +if node['postfix']['use_access_maps'] + node.default['postfix']['main']['access_maps'] = ["hash:#{node['postfix']['access_db']}"] +end + +if node['postfix']['use_virtual_aliases'] + node.default['postfix']['main']['virtual_alias_maps'] = ["#{node['postfix']['virtual_alias_db_type']}:#{node['postfix']['virtual_alias_db']}"] +end + +if node['postfix']['use_virtual_aliases_domains'] + node.default['postfix']['main']['virtual_alias_domains'] = ["#{node['postfix']['virtual_alias_domains_db_type']}:#{node['postfix']['virtual_alias_domains_db']}"] +end diff --git a/cookbooks/postfix/recipes/_common.rb b/cookbooks/postfix/recipes/_common.rb new file mode 100644 index 0000000..c91483a --- /dev/null +++ b/cookbooks/postfix/recipes/_common.rb @@ -0,0 +1,128 @@ +# encoding: utf-8 +# Author:: Joshua Timberman() +# Cookbook Name:: common +# Recipe:: default +# +# Copyright 2009-2014, Chef Software, Inc. +# +# 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. +# + +include_recipe 'postfix::_attributes' + +package 'postfix' + +package 'procmail' if node['postfix']['use_procmail'] + +case node['platform_family'] +when 'rhel', 'fedora' + service 'sendmail' do + action :nothing + end + + execute 'switch_mailer_to_postfix' do + command '/usr/sbin/alternatives --set mta /usr/sbin/sendmail.postfix' + notifies :stop, 'service[sendmail]' + notifies :start, 'service[postfix]' + not_if '/usr/bin/test /etc/alternatives/mta -ef /usr/sbin/sendmail.postfix' + end +when 'omnios' + manifest_path = ::File.join(Chef::Config[:file_cache_path], 'manifest-postfix.xml') + + # we need to manage the postfix group and user + # and then subscribe to the package install because it creates a + # postdrop group and adds postfix user to it. + group 'postfix' do + append true + end + + user 'postfix' do + uid node['postfix']['uid'] + gid 'postfix' + home '/var/spool/postfix' + subscribes :manage, 'package[postfix]' + notifies :run, 'execute[/opt/omni/sbin/postfix set-permissions]', :immediately + end + + # we don't guard this because if the user creation was successful (or happened out of band), then this won't get executed when the action is :nothing. + execute '/opt/omni/sbin/postfix set-permissions' + + template manifest_path do + source 'manifest-postfix.xml.erb' + owner 'root' + group node['root_group'] + mode '0644' + notifies :run, 'execute[load postfix manifest]', :immediately + end + + execute 'load postfix manifest' do + action :nothing + command "svccfg import #{manifest_path}" + notifies :restart, 'service[postfix]' + end +end + +execute 'update-postfix-sender_canonical' do + command "postmap #{node['postfix']['conf_dir']}/sender_canonical" + action :nothing +end + +unless node['postfix']['sender_canonical_map_entries'].empty? + template "#{node['postfix']['conf_dir']}/sender_canonical" do + owner 'root' + group node['root_group'] + mode '0644' + notifies :run, 'execute[update-postfix-sender_canonical]' + notifies :reload, 'service[postfix]' + end + + unless node['postfix']['main'].key?('sender_canonical_maps') + node.set['postfix']['main']['sender_canonical_maps'] = "hash:#{node['postfix']['conf_dir']}/sender_canonical" + end +end + +execute 'update-postfix-smtp_generic' do + command "postmap #{node['postfix']['conf_dir']}/smtp_generic" + action :nothing +end + +unless node['postfix']['smtp_generic_map_entries'].empty? + template "#{node['postfix']['conf_dir']}/smtp_generic" do + owner 'root' + group node['root_group'] + mode '0644' + notifies :run, 'execute[update-postfix-smtp_generic]' + notifies :reload, 'service[postfix]' + end + + unless node['postfix']['main'].key?('smtp_generic_maps') + node.set['postfix']['main']['smtp_generic_maps'] = "hash:#{node['postfix']['conf_dir']}/smtp_generic" + end +end + +%w{main master}.each do |cfg| + template "#{node['postfix']['conf_dir']}/#{cfg}.cf" do + source "#{cfg}.cf.erb" + owner 'root' + group node['root_group'] + mode '0644' + notifies :restart, 'service[postfix]' + variables(settings: node['postfix'][cfg]) + cookbook node['postfix']["#{cfg}_template_source"] + end +end + +service 'postfix' do + supports status: true, restart: true, reload: true + action :enable +end diff --git a/cookbooks/postfix/recipes/access.rb b/cookbooks/postfix/recipes/access.rb new file mode 100644 index 0000000..eb75bb6 --- /dev/null +++ b/cookbooks/postfix/recipes/access.rb @@ -0,0 +1,28 @@ +# encoding: utf-8 +# Copyright:: Copyright (c) 2012, Opscode, Inc. +# +# 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. +# + +include_recipe 'postfix::_common' + +execute 'update-postfix-access' do + command "postmap #{node['postfix']['access_db']}" + environment PATH: "#{ENV['PATH']}:/opt/omni/bin:/opt/omni/sbin" if platform_family?('omnios') + action :nothing +end + +template node['postfix']['access_db'] do + source 'access.erb' + notifies :run, 'execute[update-postfix-access]' +end diff --git a/cookbooks/postfix/recipes/aliases.rb b/cookbooks/postfix/recipes/aliases.rb new file mode 100644 index 0000000..cd2eb3c --- /dev/null +++ b/cookbooks/postfix/recipes/aliases.rb @@ -0,0 +1,30 @@ +# encoding: utf-8 +# Copyright:: Copyright 2012-2014, Chef Software, Inc. +# +# 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. +# + +include_recipe 'postfix::_common' + +execute 'update-postfix-aliases' do + command 'newaliases' + environment PATH: "#{ENV['PATH']}:/opt/omni/bin:/opt/omni/sbin" if platform_family?('omnios') + # On FreeBSD, /usr/sbin/newaliases is the sendmail command, and it's in the path before postfix's /usr/local/bin/newaliases + environment ({ 'PATH' => "/usr/local/bin:#{ENV['PATH']}" }) if platform_family?('freebsd') + action :nothing +end + +template node['postfix']['aliases_db'] do + source 'aliases.erb' + notifies :run, 'execute[update-postfix-aliases]' +end diff --git a/cookbooks/postfix/recipes/client.rb b/cookbooks/postfix/recipes/client.rb new file mode 100644 index 0000000..c6817ff --- /dev/null +++ b/cookbooks/postfix/recipes/client.rb @@ -0,0 +1,42 @@ +# encoding: utf-8 +# Author:: Joshua Timberman() +# Cookbook Name:: postfix +# Recipe:: client +# +# Copyright 2009-2014, Chef Software, Inc. +# +# 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. +# + +if Chef::Config[:solo] + Chef::Log.info("#{cookbook_name}::#{recipe_name} is intended for use with Chef Server, use #{cookbook_name}::default with Chef Solo.") + return +end + +query = "role:#{node['postfix']['relayhost_role']}" +relayhost = '' +# results = [] + +if node.run_list.roles.include?(node['postfix']['relayhost_role']) + relayhost << node['ipaddress'] +elsif node['postfix']['multi_environment_relay'] + results = search(:node, query) + relayhost = results.map { |n| n['ipaddress'] }.first +else + results = search(:node, "#{query} AND chef_environment:#{node.chef_environment}") + relayhost = results.map { |n| n['ipaddress'] }.first +end + +node.set['postfix']['main']['relayhost'] = "[#{relayhost}]" + +include_recipe 'postfix' diff --git a/cookbooks/postfix/recipes/default.rb b/cookbooks/postfix/recipes/default.rb new file mode 100644 index 0000000..1aecbd3 --- /dev/null +++ b/cookbooks/postfix/recipes/default.rb @@ -0,0 +1,45 @@ +# encoding: utf-8 +# Author:: Joshua Timberman() +# Cookbook Name:: postfix +# Recipe:: default +# +# Copyright 2009-2014, Chef Software, Inc. +# +# 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. +# + +include_recipe 'postfix::_common' + +if node['postfix']['main']['smtp_sasl_auth_enable'] == 'yes' + include_recipe 'postfix::sasl_auth' +end + +if node['postfix']['use_alias_maps'] + include_recipe 'postfix::aliases' +end + +if node['postfix']['use_transport_maps'] + include_recipe 'postfix::transports' +end + +if node['postfix']['use_access_maps'] + include_recipe 'postfix::access' +end + +if node['postfix']['use_virtual_aliases'] + include_recipe 'postfix::virtual_aliases' +end + +if node['postfix']['use_virtual_aliases_domains'] + include_recipe 'postfix::virtual_aliases_domains' +end diff --git a/cookbooks/postfix/recipes/sasl_auth.rb b/cookbooks/postfix/recipes/sasl_auth.rb new file mode 100644 index 0000000..bf47568 --- /dev/null +++ b/cookbooks/postfix/recipes/sasl_auth.rb @@ -0,0 +1,59 @@ +# encoding: utf-8 +# +# Author:: Joshua Timberman() +# Cookbook Name:: postfix +# Recipe:: sasl_auth +# +# Copyright 2009-2014, Chef Software, Inc. +# +# 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. +# + +include_recipe 'postfix::_common' + +sasl_pkgs = [] + +# We use case instead of value_for_platform_family because we need +# version specifics for RHEL. +case node['platform_family'] +when 'debian' + sasl_pkgs = %w(libsasl2-2 libsasl2-modules ca-certificates) +when 'rhel' + if node['platform_version'].to_i < 6 + sasl_pkgs = %w(cyrus-sasl cyrus-sasl-plain openssl) + else + sasl_pkgs = %w(cyrus-sasl cyrus-sasl-plain ca-certificates) + end +when 'fedora' + sasl_pkgs = %w(cyrus-sasl cyrus-sasl-plain ca-certificates) +end + +sasl_pkgs.each do |pkg| + package pkg +end + +execute 'postmap-sasl_passwd' do + command "postmap #{node['postfix']['sasl_password_file']}" + environment 'PATH' => "#{ENV['PATH']}:/opt/omni/bin:/opt/omni/sbin" if platform_family?('omnios') + action :nothing +end + +template node['postfix']['sasl_password_file'] do + source 'sasl_passwd.erb' + owner 'root' + group node['root_group'] + mode 0400 + notifies :run, 'execute[postmap-sasl_passwd]', :immediately + notifies :restart, 'service[postfix]' + variables(settings: node['postfix']['sasl']) +end diff --git a/cookbooks/postfix/recipes/server.rb b/cookbooks/postfix/recipes/server.rb new file mode 100644 index 0000000..3f26a52 --- /dev/null +++ b/cookbooks/postfix/recipes/server.rb @@ -0,0 +1,25 @@ +# encoding: utf-8 +# +# Author:: Joshua Timberman() +# Cookbook Name:: postfix +# Recipe:: server +# +# Copyright 2009-2014, Chef Software, Inc. +# +# 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. +# + +node.override['postfix']['mail_type'] = 'master' +node.override['postfix']['main']['inet_interfaces'] = 'all' + +include_recipe 'postfix' diff --git a/cookbooks/postfix/recipes/transports.rb b/cookbooks/postfix/recipes/transports.rb new file mode 100644 index 0000000..709d0d4 --- /dev/null +++ b/cookbooks/postfix/recipes/transports.rb @@ -0,0 +1,28 @@ +# encoding: utf-8 +# Copyright:: Copyright (c) 2012, Opscode, Inc. +# +# 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. +# + +include_recipe 'postfix::_common' + +execute 'update-postfix-transport' do + command "postmap #{node['postfix']['transport_db']}" + environment PATH: "#{ENV['PATH']}:/opt/omni/bin:/opt/omni/sbin" if platform_family?('omnios') + action :nothing +end + +template node['postfix']['transport_db'] do + source 'transport.erb' + notifies :run, 'execute[update-postfix-transport]' +end diff --git a/cookbooks/postfix/recipes/virtual_aliases.rb b/cookbooks/postfix/recipes/virtual_aliases.rb new file mode 100644 index 0000000..c2c3acf --- /dev/null +++ b/cookbooks/postfix/recipes/virtual_aliases.rb @@ -0,0 +1,29 @@ +# encoding: utf-8 +# Copyright:: Copyright (c) 2012, Opscode, Inc. +# +# 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. +# + +include_recipe 'postfix::_common' + +execute 'update-postfix-virtual-alias' do + command "postmap #{node['postfix']['virtual_alias_db']}" + environment PATH: "#{ENV['PATH']}:/opt/omni/bin:/opt/omni/sbin" if platform_family?('omnios') + action :nothing +end + +template node['postfix']['virtual_alias_db'] do + source 'virtual_aliases.erb' + notifies :run, 'execute[update-postfix-virtual-alias]' + notifies :restart, 'service[postfix]' +end diff --git a/cookbooks/postfix/recipes/virtual_aliases_domains.rb b/cookbooks/postfix/recipes/virtual_aliases_domains.rb new file mode 100644 index 0000000..3e91e99 --- /dev/null +++ b/cookbooks/postfix/recipes/virtual_aliases_domains.rb @@ -0,0 +1,29 @@ +# encoding: utf-8 +# Copyright:: Copyright (c) 2012, Opscode, Inc. +# +# 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. +# + +include_recipe 'postfix::_common' + +execute 'update-postfix-virtual-alias-domains' do + command "postmap #{node['postfix']['virtual_alias_domains_db']}" + environment PATH: "#{ENV['PATH']}:/opt/omni/bin:/opt/omni/sbin" if platform_family?('omnios') + action :nothing +end + +template node['postfix']['virtual_alias_domains_db'] do + source 'virtual_aliases_domains.erb' + notifies :run, 'execute[update-postfix-virtual-alias-domains]' + notifies :restart, 'service[postfix]' +end diff --git a/cookbooks/postfix/templates/default/access.erb b/cookbooks/postfix/templates/default/access.erb new file mode 100644 index 0000000..022f7c5 --- /dev/null +++ b/cookbooks/postfix/templates/default/access.erb @@ -0,0 +1,10 @@ +# +# This file is generated by Chef for <%= node['fqdn'] %> +# +# Local changes will be overwritten +# +# See man 5 access for format + +<% node['postfix']['access'].each do |name, value| %> +<%= name %> <%= value %> +<% end unless node['postfix']['access'].nil? %> diff --git a/cookbooks/postfix/templates/default/aliases.erb b/cookbooks/postfix/templates/default/aliases.erb new file mode 100644 index 0000000..182cbae --- /dev/null +++ b/cookbooks/postfix/templates/default/aliases.erb @@ -0,0 +1,11 @@ +# +# This file is generated by Chef for <%= node['fqdn'] %> +# +# Local changes will be overwritten +# +# See man 5 aliases for format +postmaster: root + +<% node['postfix']['aliases'].each do |name, value| %> +<%= name %>: <%= [value].flatten.map{|x| %Q("#{x}")}.join(', ') %> +<% end unless node['postfix']['aliases'].nil? %> diff --git a/cookbooks/postfix/templates/default/main.cf.erb b/cookbooks/postfix/templates/default/main.cf.erb new file mode 100644 index 0000000..d4b7ae4 --- /dev/null +++ b/cookbooks/postfix/templates/default/main.cf.erb @@ -0,0 +1,13 @@ +### +# Generated by Chef for <%= node['fqdn'] %> +# Configured as <%= node['postfix']['mail_type'] %> +### + +<% @settings.sort.map do |key, value| -%> +<% next if value.nil? -%> +<% if value.kind_of? Array -%> +<%= "#{key} = #{value.join(', ')}"%> +<% else -%> +<%= "#{key} = #{value}"%> +<% end -%> +<% end -%> diff --git a/cookbooks/postfix/templates/default/manifest-postfix.xml.erb b/cookbooks/postfix/templates/default/manifest-postfix.xml.erb new file mode 100644 index 0000000..8a1a77a --- /dev/null +++ b/cookbooks/postfix/templates/default/manifest-postfix.xml.erb @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cookbooks/postfix/templates/default/master.cf.erb b/cookbooks/postfix/templates/default/master.cf.erb new file mode 100644 index 0000000..ffa2aa5 --- /dev/null +++ b/cookbooks/postfix/templates/default/master.cf.erb @@ -0,0 +1,81 @@ +# +# 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 inet n - n - - smtpd +<% if @settings['submission'] -%> +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 -%> +#smtps inet n - n - - smtpd +# -o smtpd_tls_wrappermode=yes +# -o smtpd_sasl_auth_enable=yes +# -o smtpd_client_restrictions=permit_sasl_authenticated,reject +#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 - 500 smtp +# When relaying mail as backup MX, disable fallback_relay to avoid MX loops +relay unix - - n - - smtp + -o smtp_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 diff --git a/cookbooks/postfix/templates/default/port_smtp.erb b/cookbooks/postfix/templates/default/port_smtp.erb new file mode 100644 index 0000000..eb6aa80 --- /dev/null +++ b/cookbooks/postfix/templates/default/port_smtp.erb @@ -0,0 +1,2 @@ +# SMTP +-A FWR -p tcp -m tcp --dport 25 -j ACCEPT diff --git a/cookbooks/postfix/templates/default/sasl_passwd.erb b/cookbooks/postfix/templates/default/sasl_passwd.erb new file mode 100644 index 0000000..f8b1507 --- /dev/null +++ b/cookbooks/postfix/templates/default/sasl_passwd.erb @@ -0,0 +1,2 @@ +# This file is generated by Chef for <%= node['fqdn'] %> +<%= node['postfix']['main']['relayhost'] %> <%= @settings['smtp_sasl_user_name'] %>:<%= @settings['smtp_sasl_passwd'] %> diff --git a/cookbooks/postfix/templates/default/sender_canonical.erb b/cookbooks/postfix/templates/default/sender_canonical.erb new file mode 100644 index 0000000..785a1c3 --- /dev/null +++ b/cookbooks/postfix/templates/default/sender_canonical.erb @@ -0,0 +1,10 @@ +# +# This file is generated by Chef for <%= node['fqdn'] %> +# +# Local changes will be overwritten +# +# See man 5 canonical for format + +<% node['postfix']['sender_canonical_map_entries'].each do |name, value| %> +<%= name %> <%= value %> +<% end unless node['postfix']['sender_canonical_map_entries'].nil? %> diff --git a/cookbooks/postfix/templates/default/smtp_generic.erb b/cookbooks/postfix/templates/default/smtp_generic.erb new file mode 100644 index 0000000..7aa5d64 --- /dev/null +++ b/cookbooks/postfix/templates/default/smtp_generic.erb @@ -0,0 +1,10 @@ +# +# This file is generated by Chef for <%= node['fqdn'] %> +# +# Local changes will be overwritten +# +# See man 5 generic for format + +<% node['postfix']['smtp_generic_map_entries'].each do |name, value| %> +<%= name %> <%= value %> +<% end unless node['postfix']['smtp_generic_map_entries'].nil? %> diff --git a/cookbooks/postfix/templates/default/transport.erb b/cookbooks/postfix/templates/default/transport.erb new file mode 100644 index 0000000..4596240 --- /dev/null +++ b/cookbooks/postfix/templates/default/transport.erb @@ -0,0 +1,10 @@ +# +# This file is generated by Chef for <%= node['fqdn'] %> +# +# Local changes will be overwritten +# +# See man 5 transport for format + +<% node['postfix']['transports'].each do |name, value| %> +<%= name %> <%= value %> +<% end unless node['postfix']['transports'].nil? %> diff --git a/cookbooks/postfix/templates/default/virtual_aliases.erb b/cookbooks/postfix/templates/default/virtual_aliases.erb new file mode 100644 index 0000000..fc7e9e1 --- /dev/null +++ b/cookbooks/postfix/templates/default/virtual_aliases.erb @@ -0,0 +1,10 @@ +# +# This file is generated by Chef for <%= node['fqdn'] %> +# +# Local changes will be overwritten +# +# See man 5 virtual for format + +<% node['postfix']['virtual_aliases'].each do |key, value| %> +<%= key %> <%= value %> +<% end unless node['postfix']['virtual_aliases'].nil? %> diff --git a/cookbooks/postfix/templates/default/virtual_aliases_domains.erb b/cookbooks/postfix/templates/default/virtual_aliases_domains.erb new file mode 100644 index 0000000..a02a35a --- /dev/null +++ b/cookbooks/postfix/templates/default/virtual_aliases_domains.erb @@ -0,0 +1,10 @@ +# +# This file is generated by Chef for <%= node['fqdn'] %> +# +# Local changes will be overwritten +# +# See man 5 virtual for format + +<% node['postfix']['virtual_aliases_domains'].each do |key, value| %> +<%= key %> <%= value %> +<% end unless node['postfix']['virtual_aliases_domains'].nil? %> diff --git a/cookbooks/postgresql/CHANGELOG.md b/cookbooks/postgresql/CHANGELOG.md new file mode 100644 index 0000000..2307305 --- /dev/null +++ b/cookbooks/postgresql/CHANGELOG.md @@ -0,0 +1,220 @@ +postgresql Cookbook CHANGELOG +============================= +This file is used to list changes made in each version of the postgresql cookbook. + +v3.4.20 +------- +- Revert [#251](https://github.com/hw-cookbooks/postgresql/pull/251), a change which caused the postgresql service to restart every Chef run. + +v3.4.19 [YANKED] +------- +- node.save could better not be run on every chef run since it causes node.default attributes stored to the node objects to differ during a chef run and when +- Missing attribute in docs for yum_pgdg_postgresql +- restart postgres service immediately on config change +- Run restart command right away on the postgresql service. +- Add kitchen test for shared_preload_libraries & extension setup. +- Fix install order of contrib packages to fix pg_stat_statements issues. +- Add Debian Jessie to whitelist for apt.postgresql.org repo +- Install version 9.4 on Debian Jessie +- add amazon 2015 +- add rhel7 support + +v3.4.18 +------ +- Revert changes from #201 with the intention of revisiting these changes as part of the next major version release. +- Specify version constraint on openssl cookbook due to an upstream release mishap + +v3.4.16 +------ +- Changed hard coded value to attribute #219 +- Correction for directory creation under debian, etc. #222 +- Fedora 20 yum support #223 +- Define version-sensitive attributes in a recipe #201 + +v3.4.14 +------ +- Support apt repository for Ubuntu Utopic 14.10 +- Do not try and set password on standby hosts + +v3.4.12 +------ +- Create configuration templates at the appropriate time +- If template is updated restart service changed to default of :delayed +- Fix SSL for PostgreSQL versions < 9.2 + +v3.4.10 +------- +- correct conditional error created in 3.4.8. + +v3.4.8 +------ +- Correct scenario where work_mem could be set to 0 if con is greater than mem Issue #185 +- Add Centos7 suites to kitchen configuration + +v3.4.6 +------ +- Don't include the pgdg recipes on the wrong machine types +- Add missing dir /etc/sysconfig/pgsl for centos7 +- CentOS 7 package support + +v3.4.4 +------ +- fix packages on SLES11SP2 and higher +- [COOK-4737] Add flag to control database user password behavior +- add amazon platform rpm info +- Fix issues with the server_redhat recipe on Fedora 16 and later +- attribute typo correction +- correctly check and set max_connections to an integer + +v3.4.2 +------ +- Changed the Gem::Installer::ExtensionBuildError to a Mixlib::ShellOut::ShellCommandFailed + +v3.4.1 +------ +- Added support for Ubuntu 14.04 and Postgresql 9.3 +- Fix [COOK-3490] https://tickets.opscode.com/browse/COOK-3490 + +v3.4.0 +------ +Updated CONTRIBUTING document. +Refreshed test kitchen configuration. +Merged Pull Requests: 122, 116, 104, 102, 99, 96, 93, 90. + +v3.3.4 +------ +Testing + + +v3.3.2 +------ +- Testing maintainer transfer to Heavywater with Opscode as collaborator + + +v3.3.0 +------ +### Bug +- **[COOK-3851](https://tickets.opscode.com/browse/COOK-3851)** - Postgresql: reload after config change does not pick up certain configuration changes +- **[COOK-3611](https://tickets.opscode.com/browse/COOK-3611)** - unix_socket_directory does not exists in 9.3 +- **[COOK-2954](https://tickets.opscode.com/browse/COOK-2954)** - PostgreSQL installation ignores version attribute on CentOS >= 6 + + +v3.2.0 +------ +- [COOK-3717] Pgdg repositories improvements +- [COOK-3756] Change postgresql.conf mode from 0600 to 0644 + + +v3.1.0 +------ +### Improvement +- **[COOK-3685](https://tickets.opscode.com/browse/COOK-3685)** - Upgrade Repo Attributes for Postgresql 9.3 +- **[COOK-3597](https://tickets.opscode.com/browse/COOK-3597)** - Fix implementation of `initdb_locale` attribute for RHEL +- **[COOK-3566](https://tickets.opscode.com/browse/COOK-3566)** - Give the user's rules more priority than the default ones in pg_hba +- **[COOK-3553](https://tickets.opscode.com/browse/COOK-3553)** - Remove automatic `apt-get update` + +### Bug +- **[COOK-3611](https://tickets.opscode.com/browse/COOK-3611)** - Remove `unix_socket_directory` (it does not exists in 9.3) +- **[COOK-3599](https://tickets.opscode.com/browse/COOK-3599)** - Automatically add PGDG apt repo dependency on PostgreSQL version +- **[COOK-3555](https://tickets.opscode.com/browse/COOK-3555)** - Documentation Fix +- **[COOK-2383](https://tickets.opscode.com/browse/COOK-2383)** - Update Postgres version in attributes + + +v3.0.4 +------ +### Bug +- **[COOK-3173](https://tickets.opscode.com/browse/COOK-3173)** - Use :reload instead of :restart on conf changes +- **[COOK-2939](https://tickets.opscode.com/browse/COOK-2939)** - Fix RedHat support + +v3.0.2 +------ +### Bug +- [COOK-3076]: postgresql::ruby recipe error when using pgdg repositories + +v3.0.0 +------ +This is a backwards-incompatible release because the Pitti PPA is deprecated and the recipe removed, replaced with the PGDG apt repository. + +### Bug +- [COOK-2571]: Create helper library for pg extension detection +- [COOK-2797]: Contrib extension contianing '-' fails to load. + +### Improvement +- [COOK-2387]: Pitti Postgresql PPA is deprecated + +### Task +- [COOK-3022]: update baseboxes in .kitchen.yml + +v2.4.0 +------ +- [COOK-2163] - Dangerous "assign-postgres-password" in "recipes/server.rb" -- Can lock out dbadmin access +- [COOK-2390] - Recipes to auto-generate many postgresql.conf settings, following "initdb" and "pgtune" +- [COOK-2435] - Foodcritic fixes for postgresql cookbook +- [COOK-2476] - Installation into database of any contrib module extensions listed in a node attribute + +v2.2.2 +------ +- [COOK-2232] -Provide PGDG yum repo to install postgresql 9.x on + redhat-derived distributions + +v2.2.0 +------ +- [COOK-2230] - Careful about Debian minor version numbers +- [COOK-2231] - Fix support for postgresql 9.x in server_redhat recipe +- [COOK-2238] - Postgresql recipe error in password check +- [COOK-2176] - PostgreSQL cookbook in Solo mode can cause "NoMethodError: undefined method `[]' for nil:NilClass" +- [COOK-2233] - Provide postgresql::contrib recipe to install useful server administration tools + +v2.1.0 +------ +- [COOK-1872] - Allow latest PostgreSQL deb packages to be installed +- [COOK-1961] - Postgresql config file changes with every Chef run +- [COOK-2041] - Postgres cookbook no longer installs on OpenSuSE 11.4 + +v2.0.2 +------ +- [COOK-1406] - pg gem compile is unable to find libpq under Chef full stack (omnibus) installation + +v2.0.0 +------ +This version is backwards incompatible with previous versions of the cookbook due to use of `platform_family`, and the refactored configuration files using node attributes. See README.md for details on how to modify configuration of PostgreSQL. + +- [COOK-1508] - fix mixlib shellout error on SUSE +- [COOK-1744] - Add service enable & start +- [COOK-1779] - Don't run apt-get update and others in ruby recipe if pg is installed +- [COOK-1871] - Attribute driven configuration files for PostgreSQL +- [COOK-1900] - don't assume ssl on all postgresql 8.4+ installs +- [COOK-1901] - fail a chef-solo run when the postgres password + attribute is not set + +v1.0.0 +------ +**Important note for this release** + +This version no longer installs Ruby bindings in the client recipe by default. Use the ruby recipe if you'd like the RubyGem. If you'd like packages for your distribution, use them in your application's specific cookbook/recipe, or modify the client packages attribute. + +This resolves the following tickets. + +- COOK-1011 +- COOK-1534 + +The following issues are also resolved with this release. + +- [COOK-1011] - Don't install postgresql packages during compile phase and remove pg gem installation +- [COOK-1224] - fix undefined variable on Debian +- [COOK-1462] - Add attribute for specifying listen address + +v0.99.4 +------ +- [COOK-421] - config template is malformed +- [COOK-956] - add make package on ubuntu/debian + +v0.99.2 +------ +- [COOK-916] - use < (with float) for version comparison. + +v0.99.0 +------ +- Better support for Red Hat-family platforms +- Integration with database cookbook +- Make sure the postgres role is updated with a (secure) password diff --git a/cookbooks/postgresql/README.md b/cookbooks/postgresql/README.md new file mode 100644 index 0000000..ba7f14b --- /dev/null +++ b/cookbooks/postgresql/README.md @@ -0,0 +1,464 @@ +Description +=========== + +Installs and configures PostgreSQL as a client or a server. + +Requirements +============ + +## Platforms + +* Debian, Ubuntu +* Red Hat/CentOS/Scientific (6.0+ required) - "EL6-family" +* Fedora +* SUSE + +Tested on: + +* Ubuntu 10.04, 11.10, 12.04, 14.04, 14.10 +* Red Hat 6.1, Scientific 6.1, CentOS 6.3 + +## Cookbooks + +Requires Opscode's `openssl` cookbook for secure password generation. + +Requires a C compiler and development headers in order to build the +`pg` RubyGem to provide Ruby bindings in the `ruby` recipe. + +Opscode's `build-essential` cookbook provides this functionality on +Debian, Ubuntu, and EL6-family. + +While not required, Opscode's `database` cookbook contains resources +and providers that can interact with a PostgreSQL database. This +cookbook is a dependency of database. + +Attributes +========== + +The following attributes are set based on the platform, see the +`attributes/default.rb` file for default values. + +* `node['postgresql']['version']` - version of postgresql to manage +* `node['postgresql']['dir']` - home directory of where postgresql + data and configuration lives. + +* `node['postgresql']['client']['packages']` - An array of package names + that should be installed on "client" systems. +* `node['postgresql']['server']['packages']` - An array of package names + that should be installed on "server" systems. +* `node['postgresql']['server']['config_change_notify']` - Type of + notification triggered when a config file changes. +* `node['postgresql']['contrib']['packages']` - An array of package names + that could be installed on "server" systems for useful sysadmin tools. + +* `node['postgresql']['enable_pgdg_apt']` - Whether to enable the apt repo + by the PostgreSQL Global Development Group, which contains newer versions + of PostgreSQL. + +* `node['postgresql']['enable_pgdg_yum']` - Whether to enable the yum repo + by the PostgreSQL Global Development Group, which contains newer versions + of PostgreSQL. + +* `node['postgresql']['initdb_locale']` - Sets the default locale for the + database cluster. If this attribute is not specified, the locale is + inherited from the environment that initdb runs in. Sometimes you must + have a system locale that is not what you want for your database cluster, + and this attribute addresses that scenario. Valid only for EL-family + distros (RedHat/Centos/etc.). + +The following attributes are generated in +`recipe[postgresql::server]`. + +* `node['postgresql']['password']['postgres']` - randomly generated + password by the `openssl` cookbook's library. + (TODO: This is broken, as it disables the password.) + +Configuration +------------- + +The `postgresql.conf` and `pg_hba.conf` files are dynamically +generated from attributes. Each key in `node['postgresql']['config']` +is a postgresql configuration directive, and will be rendered in the +config file. For example, the attribute: + + node['postgresql']['config']['listen_addresses'] = 'localhost' + +Will result in the following line in the `postgresql.conf` file: + + listen_addresses = 'localhost' + +The attributes file contains default values for Debian and RHEL +platform families (per the `node['platform_family']`). These defaults +have disparity between the platforms because they were originally +extracted from the postgresql.conf files in the previous version of +this cookbook, which differed in their default config. The resulting +configuration files will be the same as before, but the content will +be dynamically rendered from the attributes. The helpful commentary +will no longer be present. You should consult the PostgreSQL +documentation for specific configuration details. + +See __Recipes__ `config_initdb` and `config_pgtune` below to +auto-generate many postgresql.conf settings. + +For values that are "on" or "off", they should be specified as literal +`true` or `false`. String values will be used with single quotes. Any +configuration option set to the literal `nil` will be skipped +entirely. All other values (e.g., numeric literals) will be used as +is. So for example: + + node.default['postgresql']['config']['logging_collector'] = true + node.default['postgresql']['config']['datestyle'] = 'iso, mdy' + node.default['postgresql']['config']['ident_file'] = nil + node.default['postgresql']['config']['port'] = 5432 + +Will result in the following config lines: + + logging_collector = 'on' + datestyle = 'iso,mdy' + port = 5432 + +(no line printed for `ident_file` as it is `nil`) + +Note that the `unix_socket_directory` configuration was renamed to +`unix_socket_directories` in Postgres 9.3 so make sure to use the +`node['postgresql']['unix_socket_directories']` attribute instead of +`node['postgresql']['unix_socket_directory']`. + +The `pg_hba.conf` file is dynamically generated from the +`node['postgresql']['pg_hba']` attribute. This attribute must be an +array of hashes, each hash containing the authorization data. As it is +an array, you can append to it in your own recipes. The hash keys in +the array must be symbols. Each hash will be written as a line in +`pg_hba.conf`. For example, this entry from +`node['postgresql']['pg_hba']`: + + [{:comment => '# Optional comment', + :type => 'local', :db => 'all', :user => 'postgres', :addr => nil, :method => 'md5'}] + +Will result in the following line in `pg_hba.conf`: + + # Optional comment + local all postgres md5 + +Use `nil` if the CIDR-ADDRESS should be empty (as above). +Don't provide a comment if none is desired in the `pg_hba.conf` file. + +Note that the following authorization rule is supplied automatically by +the cookbook template. The cookbook needs this to execute SQL in the +PostgreSQL server without supplying the clear-text password (which isn't +known by the cookbook). Therefore, your `node['postgresql']['pg_hba']` +attributes don't need to specify this authorization rule: + + # "local" is for Unix domain socket connections only + local all all ident + +(By the way, the template uses `peer` instead of `ident` for PostgreSQL-9.1 +and above, which has the same effect.) + +Recipes +======= + +default +------- + +Includes the client recipe. + +client +------ + +Installs the packages defined in the +`node['postgresql']['client']['packages']` attribute. + +ruby +---- + +**NOTE** This recipe may not currently work when installing Chef with + the + ["Omnibus" full stack installer](http://opscode.com/chef/install) on + some platforms due to an incompatibility with OpenSSL. See + [COOK-1406](http://tickets.opscode.com/browse/COOK-1406). You can + build from source into the Chef omnibus installation to work around + this issue. + +Install the `pg` gem under Chef's Ruby environment so it can be used +in other recipes. The build-essential packages and postgresql client +packages will be installed during the compile phase, so that the +native extensions of `pg` can be compiled. + +server +------ + +Includes the `server_debian` or `server_redhat` recipe to get the +appropriate server packages installed and service managed. Also +manages the configuration for the server: + +* generates a strong default password (via `openssl`) for `postgres` + (TODO: This is broken, as it disables the password.) +* sets the password for postgres +* manages the `postgresql.conf` file. +* manages the `pg_hba.conf` file. + +server\_debian +-------------- + +Installs the postgresql server packages and sets up the service. You +should include the `postgresql::server` recipe, which will include +this on Debian platforms. + +server\_redhat +-------------- + +Manages the postgres user and group (with UID/GID 26, per RHEL package +conventions), installs the postgresql server packages, initializes the +database, and manages the postgresql service. You should include the +`postgresql::server` recipe, which will include this on RHEL/Fedora +platforms. + +config\_initdb +-------------- + +Takes locale and timezone settings from the system configuration. +This recipe creates `node.default['postgresql']['config']` attributes +that conform to the system's locale and timezone. In addition, this +recipe creates the same error reporting and logging settings that +`initdb` provided: a rotation of 7 days of log files named +postgresql-Mon.log, etc. + +The default attributes created by this recipe are easy to override with +normal attributes because of Chef attribute precedence. For example, +suppose a DBA wanted to keep log files indefinitely, rolling over daily +or when growing to 10MB. The Chef installation could include the +`postgresql::config_initdb` recipe for the locale and timezone settings, +but customize the logging settings with these node JSON attributes: + + "postgresql": { + "config": { + "log_rotation_age": "1d", + "log_rotation_size": "10MB", + "log_filename": "postgresql-%Y-%m-%d_%H%M%S.log" + } + } + +Credits: This `postgresql::config_initdb` recipe is based on algorithms +in the [source code](http://doxygen.postgresql.org/initdb_8c_source.html) +for the PostgreSQL `initdb` utility. + +config\_pgtune +-------------- + +Performance tuning. +Takes the wimpy default postgresql.conf and expands the database server +to be as powerful as the hardware it's being deployed on. This recipe +creates a baseline configuration of `node.default['postgresql']['config']` +attributes in the right general range for a dedicated Postgresql system. +Most installations won't need additional performance tuning. + +The only decision you need to make is to choose a `db_type` from the +following database workloads. (See the recipe code comments for more +detailed descriptions.) + + * "dw" -- Data Warehouse + * "oltp" -- Online Transaction Processing + * "web" -- Web Application + * "mixed" -- Mixed DW and OLTP characteristics + * "desktop" -- Not a dedicated database + +This recipe uses a performance model with three input parameters. +These node attributes are completely optional, but it is obviously +important to choose the `db_type` correctly: + + * `node['postgresql']['config_pgtune']['db_type']` -- + Specifies database type from the list of five choices above. + If not specified, the default is "mixed". + + * `node['postgresql']['config_pgtune']['max_connections']` -- + Specifies maximum number of connections expected. + If not specified, it depends on database type: + "web":200, "oltp":300, "dw":20, "mixed":80, "desktop":5 + + * `node['postgresql']['config_pgtune']['total_memory']` -- + Specifies total system memory in kB. (E.g., "49416564kB".) + If not specified, it will be taken from Ohai automatic attributes. + This could be used to tune a system that isn't a dedicated database. + +The default attributes created by this recipe are easy to override with +normal attributes because of Chef attribute precedence. For example, if +you are running application benchmarks to try different buffer cache +sizes, you would experiment with this node JSON attribute: + + "postgresql": { + "config": { + "shared_buffers": "3GB" + } + } + +Note that the recipe uses `max_connections` in its computations. If +you want to override that setting, you should specify +`node['postgresql']['config_pgtune']['max_connections']` instead of +`node['postgresql']['config']['max_connections']`. + +Credits: This `postgresql::config_pgtune` recipe is based on the +[pgtune python script](https://github.com/gregs1104/pgtune) +developed by +[Greg Smith](http://notemagnet.blogspot.com/2008/11/automating-initial-postgresqlconf.html) +and +[other pgsql-hackers](http://www.postgresql.org/message-id/491C6CDC.8090506@agliodbs.com). + +contrib +------- + +Installs the packages defined in the +`node['postgresql']['contrib']['packages']` attribute. The contrib +directory of the PostgreSQL distribution includes porting tools, +analysis utilities, and plug-in features that database engineers often +require. Some (like `pgbench`) are executable. Others (like +`pg_buffercache`) would need to be installed into the database. + +Also installs any contrib module extensions defined in the +`node['postgresql']['contrib']['extensions']` attribute. These will be +available in any subsequently created databases in the cluster, because +they will be installed into the `template1` database using the +`CREATE EXTENSION` command. For example, it is often necessary/helpful +for problem troubleshooting and maintenance planning to install the +views and functions in these [standard instrumentation extensions] +(http://www.postgresql.org/message-id/flat/4DC32600.6080900@pgexperts.com#4DD3D6C6.5060006@2ndquadrant.com): + + node['postgresql']['contrib']['extensions'] = [ + "pageinspect", + "pg_buffercache", + "pg_freespacemap", + "pgrowlocks", + "pg_stat_statements", + "pgstattuple" + ] + +Note that the `pg_stat_statements` view only works if `postgresql.conf` +loads its shared library, which can be done with this node attribute: + + node['postgresql']['config']['shared_preload_libraries'] = 'pg_stat_statements' + +If using `shared_preload_libraries` in combination with the `contrib` recipe, +make sure that the `contrib` recipe is called before the `server` recipe (to +ensure the dependencies are installed and setup in order). + +apt\_pgdg\_postgresql +---------------------- + +Enables the PostgreSQL Global Development Group yum repository +maintained by Devrim Gündüz for updated PostgreSQL packages. +(The PGDG is the groups that develops PostgreSQL.) +Automatically included if the `node['postgresql']['enable_pgdg_apt']` +attribute is true. Also set the +`node['postgresql']['client']['packages']` and +`node['postgresql']['server]['packages']` to the list of packages to +use from this repository, and set the `node['postgresql']['version']` +attribute to the version to use (e.g., "9.2"). + +yum\_pgdg\_postgresql +--------------------- + +Enables the PostgreSQL Global Development Group yum repository +maintained by Devrim Gündüz for updated PostgreSQL packages. +(The PGDG is the groups that develops PostgreSQL.) +Automatically included if the `node['postgresql']['enable_pgdg_yum']` +attribute is true. Also use `override_attributes` to set a number of +values that will need to have embedded version numbers. For example: + + node['postgresql']['enable_pgdg_yum'] = true + node['postgresql']['version'] = "9.2" + node['postgresql']['dir'] = "/var/lib/pgsql/9.2/data" + node['postgresql']['config']['data_directory'] = node['postgresql']['dir'] + node['postgresql']['client']['packages'] = ["postgresql92", "postgresql92-devel"] + node['postgresql']['server']['packages'] = ["postgresql92-server"] + node['postgresql']['server']['service_name'] = "postgresql-9.2" + node['postgresql']['contrib']['packages'] = ["postgresql92-contrib"] + +You may set `node['postgresql']['pgdg']['repo_rpm_url']` attributes +to pick up recent [PGDG repo packages](http://yum.postgresql.org/repopackages.php). + +Resources/Providers +=================== + +See the [database](http://community.opscode.com/cookbooks/database) +for resources and providers that can be used for managing PostgreSQL +users and databases. + +Usage +===== + +On systems that need to connect to a PostgreSQL database, add to a run +list `recipe[postgresql]` or `recipe[postgresql::client]`. + +On systems that should be PostgreSQL servers, use +`recipe[postgresql::server]` on a run list. This recipe does set a +password for the `postgres` user. +If you're using `chef server`, if the attribute +`node['postgresql']['password']['postgres']` is not found, +the recipe generates a random password and performs a node.save. +(TODO: This is broken, as it disables the password.) +If you're using `chef-solo`, you'll need +to set the attribute `node['postgresql']['password']['postgres']` in +your node's `json_attribs` file or in a role. + +On Debian family systems, SSL will be enabled, as the packages on +Debian/Ubuntu also generate the SSL certificates. If you use another +platform and wish to use SSL in postgresql, then generate your SSL +certificates and distribute them in your own cookbook, and set the +`node['postgresql']['config']['ssl']` attribute to true in your +role/cookboook/node. + +On server systems, the postgres server is restarted when a configuration +file changes. This can be changed to reload only by setting the +following attribute: + + node['postgresql']['server']['config_change_notify'] = :reload + +Chef Solo Note +============== + +The following node attribute is stored on the Chef Server when using +`chef-client`. Because `chef-solo` does not connect to a server or +save the node object at all, to have the password persist across +`chef-solo` runs, you must specify them in the `json_attribs` file +used. For Example: + + { + "postgresql": { + "password": { + "postgres": "iloverandompasswordsbutthiswilldo" + } + }, + "run_list": ["recipe[postgresql::server]"] + } + +That should actually be the "encrypted password" instead of cleartext, +so you should generate it as an md5 hash using the PostgreSQL algorithm. + +* You could copy the md5-hashed password from an existing postgres +database if you have `postgres` access and want to use the same password:
    +`select * from pg_shadow where usename='postgres';` +* You can run this from any postgres database session to use a new password:
    +`select 'md5'||md5('iloverandompasswordsbutthiswilldo'||'postgres');` +* You can run this from a linux commandline:
    +`echo -n 'iloverandompasswordsbutthiswilldo''postgres' | openssl md5 | sed -e 's/.* /md5/'` + +License and Author +================== + +- Author:: Joshua Timberman () +- Author:: Lamont Granquist () +- Author:: Chris Roberts () +- Author:: David Crane () +- Author:: Aaron Baer () + +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/cookbooks/postgresql/attributes/default.rb b/cookbooks/postgresql/attributes/default.rb new file mode 100644 index 0000000..8c568a7 --- /dev/null +++ b/cookbooks/postgresql/attributes/default.rb @@ -0,0 +1,549 @@ +# +# Cookbook Name:: postgresql +# Attributes:: postgresql +# +# 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. +# + +default['postgresql']['enable_pgdg_apt'] = false +default['postgresql']['server']['config_change_notify'] = :restart +default['postgresql']['assign_postgres_password'] = true + +# Establish default database name +default['postgresql']['database_name'] = 'template1' + +case node['platform'] +when "debian" + + case + when node['platform_version'].to_f < 6.0 # All 5.X + default['postgresql']['version'] = "8.3" + when node['platform_version'].to_f < 7.0 # All 6.X + default['postgresql']['version'] = "8.4" + when node['platform_version'].to_f < 8.0 # All 7.X + default['postgresql']['version'] = "9.1" + else + default['postgresql']['version'] = "9.4" + end + + default['postgresql']['dir'] = "/etc/postgresql/#{node['postgresql']['version']}/main" + case + when node['platform_version'].to_f < 6.0 # All 5.X + default['postgresql']['server']['service_name'] = "postgresql-#{node['postgresql']['version']}" + else + default['postgresql']['server']['service_name'] = "postgresql" + end + + default['postgresql']['client']['packages'] = ["postgresql-client-#{node['postgresql']['version']}","libpq-dev"] + default['postgresql']['server']['packages'] = ["postgresql-#{node['postgresql']['version']}"] + default['postgresql']['contrib']['packages'] = ["postgresql-contrib-#{node['postgresql']['version']}"] + +when "ubuntu" + + case + when node['platform_version'].to_f <= 9.04 + default['postgresql']['version'] = "8.3" + when node['platform_version'].to_f <= 11.04 + default['postgresql']['version'] = "8.4" + when node['platform_version'].to_f <= 13.10 + default['postgresql']['version'] = "9.1" + else + default['postgresql']['version'] = "9.3" + end + + default['postgresql']['dir'] = "/etc/postgresql/#{node['postgresql']['version']}/main" + case + when (node['platform_version'].to_f <= 10.04) && (! node['postgresql']['enable_pgdg_apt']) + default['postgresql']['server']['service_name'] = "postgresql-#{node['postgresql']['version']}" + else + default['postgresql']['server']['service_name'] = "postgresql" + end + + default['postgresql']['client']['packages'] = ["postgresql-client-#{node['postgresql']['version']}","libpq-dev"] + default['postgresql']['server']['packages'] = ["postgresql-#{node['postgresql']['version']}"] + default['postgresql']['contrib']['packages'] = ["postgresql-contrib-#{node['postgresql']['version']}"] + +when "fedora" + + if node['platform_version'].to_f <= 12 + default['postgresql']['version'] = "8.3" + else + default['postgresql']['version'] = "8.4" + end + + default['postgresql']['dir'] = "/var/lib/pgsql/data" + default['postgresql']['client']['packages'] = %w{postgresql-devel} + default['postgresql']['server']['packages'] = %w{postgresql-server} + default['postgresql']['contrib']['packages'] = %w{postgresql-contrib} + default['postgresql']['server']['service_name'] = "postgresql" + +when "amazon" + + if node['platform_version'].to_f >= 2012.03 + default['postgresql']['version'] = "9.0" + default['postgresql']['dir'] = "/var/lib/pgsql9/data" + else + default['postgresql']['version'] = "8.4" + default['postgresql']['dir'] = "/var/lib/pgsql/data" + end + + default['postgresql']['client']['packages'] = %w{postgresql-devel} + default['postgresql']['server']['packages'] = %w{postgresql-server} + default['postgresql']['contrib']['packages'] = %w{postgresql-contrib} + default['postgresql']['server']['service_name'] = "postgresql" + +when "redhat", "centos", "scientific", "oracle" + + default['postgresql']['version'] = "8.4" + default['postgresql']['dir'] = "/var/lib/pgsql/data" + + if node['platform_version'].to_f >= 6.0 && node['postgresql']['version'] == '8.4' + default['postgresql']['client']['packages'] = %w{postgresql-devel} + default['postgresql']['server']['packages'] = %w{postgresql-server} + default['postgresql']['contrib']['packages'] = %w{postgresql-contrib} + else + default['postgresql']['client']['packages'] = ["postgresql#{node['postgresql']['version'].split('.').join}-devel"] + default['postgresql']['server']['packages'] = ["postgresql#{node['postgresql']['version'].split('.').join}-server"] + default['postgresql']['contrib']['packages'] = ["postgresql#{node['postgresql']['version'].split('.').join}-contrib"] + end + + if node['platform_version'].to_f >= 6.0 && node['postgresql']['version'] != '8.4' + default['postgresql']['dir'] = "/var/lib/pgsql/#{node['postgresql']['version']}/data" + default['postgresql']['server']['service_name'] = "postgresql-#{node['postgresql']['version']}" + else + default['postgresql']['dir'] = "/var/lib/pgsql/data" + default['postgresql']['server']['service_name'] = "postgresql" + end + +when "suse" + + if node['platform_version'].to_f <= 11.1 + default['postgresql']['version'] = "8.3" + default['postgresql']['client']['packages'] = ['postgresql', 'rubygem-pg'] + default['postgresql']['server']['packages'] = ['postgresql-server'] + default['postgresql']['contrib']['packages'] = ['postgresql-contrib'] + else + default['postgresql']['version'] = "9.1" + default['postgresql']['client']['packages'] = ['postgresql91', 'rubygem-pg'] + default['postgresql']['server']['packages'] = ['postgresql91-server'] + default['postgresql']['contrib']['packages'] = ['postgresql91-contrib'] + end + + default['postgresql']['dir'] = "/var/lib/pgsql/data" + default['postgresql']['server']['service_name'] = "postgresql" + +else + default['postgresql']['version'] = "8.4" + default['postgresql']['dir'] = "/etc/postgresql/#{node['postgresql']['version']}/main" + default['postgresql']['client']['packages'] = ["postgresql"] + default['postgresql']['server']['packages'] = ["postgresql"] + default['postgresql']['contrib']['packages'] = ["postgresql"] + default['postgresql']['server']['service_name'] = "postgresql" +end + +# These defaults have disparity between which postgresql configuration +# settings are used because they were extracted from the original +# configuration files that are now removed in favor of dynamic +# generation. +# +# While the configuration ends up being the same as the default +# in previous versions of the cookbook, the content of the rendered +# template will change, and this will result in service notification +# if you upgrade the cookbook on existing systems. +# +# The ssl config attribute is generated in the recipe to avoid awkward +# merge/precedence order during the Chef run. +case node['platform_family'] +when 'debian' + default['postgresql']['config']['data_directory'] = "/var/lib/postgresql/#{node['postgresql']['version']}/main" + default['postgresql']['config']['hba_file'] = "/etc/postgresql/#{node['postgresql']['version']}/main/pg_hba.conf" + default['postgresql']['config']['ident_file'] = "/etc/postgresql/#{node['postgresql']['version']}/main/pg_ident.conf" + default['postgresql']['config']['external_pid_file'] = "/var/run/postgresql/#{node['postgresql']['version']}-main.pid" + default['postgresql']['config']['listen_addresses'] = 'localhost' + default['postgresql']['config']['port'] = 5432 + default['postgresql']['config']['max_connections'] = 100 + default['postgresql']['config']['unix_socket_directory'] = '/var/run/postgresql' if node['postgresql']['version'].to_f < 9.3 + default['postgresql']['config']['unix_socket_directories'] = '/var/run/postgresql' if node['postgresql']['version'].to_f >= 9.3 + default['postgresql']['config']['shared_buffers'] = '24MB' + default['postgresql']['config']['max_fsm_pages'] = 153600 if node['postgresql']['version'].to_f < 8.4 + default['postgresql']['config']['log_line_prefix'] = '%t ' + default['postgresql']['config']['datestyle'] = 'iso, mdy' + default['postgresql']['config']['default_text_search_config'] = 'pg_catalog.english' + default['postgresql']['config']['ssl'] = true + default['postgresql']['config']['ssl_cert_file'] = '/etc/ssl/certs/ssl-cert-snakeoil.pem' if node['postgresql']['version'].to_f >= 9.2 + default['postgresql']['config']['ssl_key_file'] = '/etc/ssl/private/ssl-cert-snakeoil.key'if node['postgresql']['version'].to_f >= 9.2 +when 'rhel', 'fedora', 'suse' + default['postgresql']['config']['data_directory'] = node['postgresql']['dir'] + default['postgresql']['config']['listen_addresses'] = 'localhost' + default['postgresql']['config']['port'] = 5432 + default['postgresql']['config']['max_connections'] = 100 + default['postgresql']['config']['shared_buffers'] = '32MB' + default['postgresql']['config']['logging_collector'] = true + default['postgresql']['config']['log_directory'] = 'pg_log' + default['postgresql']['config']['log_filename'] = 'postgresql-%a.log' + default['postgresql']['config']['log_truncate_on_rotation'] = true + default['postgresql']['config']['log_rotation_age'] = '1d' + default['postgresql']['config']['log_rotation_size'] = 0 + default['postgresql']['config']['datestyle'] = 'iso, mdy' + default['postgresql']['config']['lc_messages'] = 'en_US.UTF-8' + default['postgresql']['config']['lc_monetary'] = 'en_US.UTF-8' + default['postgresql']['config']['lc_numeric'] = 'en_US.UTF-8' + default['postgresql']['config']['lc_time'] = 'en_US.UTF-8' + default['postgresql']['config']['default_text_search_config'] = 'pg_catalog.english' +end + +default['postgresql']['pg_hba'] = [ + {:type => 'local', :db => 'all', :user => 'postgres', :addr => nil, :method => 'ident'}, + {:type => 'local', :db => 'all', :user => 'all', :addr => nil, :method => 'ident'}, + {:type => 'host', :db => 'all', :user => 'all', :addr => '127.0.0.1/32', :method => 'md5'}, + {:type => 'host', :db => 'all', :user => 'all', :addr => '::1/128', :method => 'md5'} +] + +default['postgresql']['password'] = Hash.new + +case node['platform_family'] +when 'debian' + default['postgresql']['pgdg']['release_apt_codename'] = node['lsb']['codename'] +end + +default['postgresql']['enable_pgdg_yum'] = false + +default['postgresql']['initdb_locale'] = nil + +# The PostgreSQL RPM Building Project built repository RPMs for easy +# access to the PGDG yum repositories. Links to RPMs for installation +# on the supported version/platform combinations are listed at +# http://yum.postgresql.org/repopackages.php, and the links for +# PostgreSQL 8.4, 9.0, 9.1, 9.2 and 9.3 are captured below. +# +# The correct RPM for installing /etc/yum.repos.d is based on: +# * the attribute configuring the desired Postgres Software: +# node['postgresql']['version'] e.g., "9.1" +# * the chef ohai description of the target Operating System: +# node['platform'] e.g., "centos" +# node['platform_version'] e.g., "5.7", truncated as "5" +# node['kernel']['machine'] e.g., "i386" or "x86_64" +default['postgresql']['pgdg']['repo_rpm_url'] = { + "9.4" => { + "redhat" => { + "7" => { + "x86_64" => "http://yum.postgresql.org/9.4/redhat/rhel-7-x86_64/pgdg-redhat94-9.4-1.noarch.rpm" + } + } + }, + "9.3" => { + "amazon" => { + "2015" => { + "i386" => "http://yum.postgresql.org/9.3/redhat/rhel-6-i386/pgdg-redhat93-9.3-1.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-redhat93-9.3-1.noarch.rpm" + }, + "2014" => { + "i386" => "http://yum.postgresql.org/9.3/redhat/rhel-6-i386/pgdg-redhat93-9.3-1.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-redhat93-9.3-1.noarch.rpm" + }, + "2013" => { + "i386" => "http://yum.postgresql.org/9.3/redhat/rhel-6-i386/pgdg-redhat93-9.3-1.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-redhat93-9.3-1.noarch.rpm" + } + }, + "centos" => { + "7" => { + "x86_64" => "http://yum.postgresql.org/9.3/redhat/rhel-7-x86_64/pgdg-centos93-9.3-1.noarch.rpm" + }, + "6" => { + "i386" => "http://yum.postgresql.org/9.3/redhat/rhel-6-i386/pgdg-centos93-9.3-1.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-centos93-9.3-1.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.3/redhat/rhel-5-i386/pgdg-centos93-9.3-1.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.3/redhat/rhel-5-x86_64/pgdg-centos93-9.3-1.noarch.rpm" + } + }, + "redhat" => { + "7" => { + "x86_64" => "http://yum.postgresql.org/9.3/redhat/rhel-7-x86_64/pgdg-redhat93-9.3-1.noarch.rpm" + }, + "6" => { + "i386" => "http://yum.postgresql.org/9.3/redhat/rhel-6-i386/pgdg-redhat93-9.3-1.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-redhat93-9.3-1.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.3/redhat/rhel-5-i386/pgdg-redhat93-9.3-1.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.3/redhat/rhel-5-x86_64/pgdg-redhat93-9.3-1.noarch.rpm" + } + }, + "oracle" => { + "6" => { + "i386" => "http://yum.postgresql.org/9.3/redhat/rhel-6-i386/pgdg-redhat93-9.3-1.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-redhat93-9.3-1.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.3/redhat/rhel-5-i386/pgdg-redhat93-9.3-1.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.3/redhat/rhel-5-x86_64/pgdg-redhat93-9.3-1.noarch.rpm" + } + }, + "scientific" => { + "6" => { + "i386" => "http://yum.postgresql.org/9.3/redhat/rhel-6-i386/pgdg-sl93-9.3-1.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-sl93-9.3-1.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.3/redhat/rhel-5-i386/pgdg-sl93-9.3-1.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.3/redhat/rhel-5-x86_64/pgdg-sl93-9.3-1.noarch.rpm" + } + }, + "fedora" => { + "20" => { + "x86_64" => "http://yum.postgresql.org/9.3/fedora/fedora-20-x86_64/pgdg-fedora93-9.3-1.noarch.rpm" + }, + "19" => { + "x86_64" => "http://yum.postgresql.org/9.3/fedora/fedora-19-x86_64/pgdg-fedora93-9.3-1.noarch.rpm" + }, + "18" => { + "i386" => "http://yum.postgresql.org/9.3/fedora/fedora-18-i386/pgdg-fedora93-9.3-1.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.3/fedora/fedora-18-x86_64/pgdg-fedora93-9.3-1.noarch.rpm" + }, + "17" => { + "i386" => "http://yum.postgresql.org/9.3/fedora/fedora-17-i386/pgdg-fedora93-9.3-1.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.3/fedora/fedora-17-x86_64/pgdg-fedora93-9.3-1.noarch.rpm" + } + } + }, + "9.2" => { + "centos" => { + "6" => { + "i386" => "http://yum.postgresql.org/9.2/redhat/rhel-6-i386/pgdg-centos92-9.2-6.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.2/redhat/rhel-6-x86_64/pgdg-centos92-9.2-6.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.2/redhat/rhel-5-i386/pgdg-centos92-9.2-6.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.2/redhat/rhel-5-x86_64/pgdg-centos92-9.2-6.noarch.rpm" + } + }, + "redhat" => { + "6" => { + "i386" => "http://yum.postgresql.org/9.2/redhat/rhel-6-i386/pgdg-redhat92-9.2-7.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.2/redhat/rhel-6-x86_64/pgdg-redhat92-9.2-7.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.2/redhat/rhel-5-i386/pgdg-redhat92-9.2-7.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.2/redhat/rhel-5-x86_64/pgdg-redhat92-9.2-7.noarch.rpm" + } + }, + "oracle" => { + "6" => { + "i386" => "http://yum.postgresql.org/9.2/redhat/rhel-6-i386/pgdg-redhat92-9.2-7.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.2/redhat/rhel-6-x86_64/pgdg-redhat92-9.2-7.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.2/redhat/rhel-5-i386/pgdg-redhat92-9.2-7.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.2/redhat/rhel-5-x86_64/pgdg-redhat92-9.2-7.noarch.rpm" + } + }, + "scientific" => { + "6" => { + "i386" => "http://yum.postgresql.org/9.2/redhat/rhel-6-i386/pgdg-sl92-9.2-8.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.2/redhat/rhel-6-x86_64/pgdg-sl92-9.2-8.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.2/redhat/rhel-5-i386/pgdg-sl92-9.2-8.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.2/redhat/rhel-5-x86_64/pgdg-sl92-9.2-8.noarch.rpm" + } + }, + "fedora" => { + "19" => { + "i386" => "http://yum.postgresql.org/9.2/fedora/fedora-19-i386/pgdg-fedora92-9.2-6.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.2/fedora/fedora-19-x86_64/pgdg-fedora92-9.2-6.noarch.rpm" + }, + "18" => { + "i386" => "http://yum.postgresql.org/9.2/fedora/fedora-18-i386/pgdg-fedora92-9.2-6.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.2/fedora/fedora-18-x86_64/pgdg-fedora92-9.2-6.noarch.rpm" + }, + "17" => { + "i386" => "http://yum.postgresql.org/9.2/fedora/fedora-17-i386/pgdg-fedora92-9.2-6.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.2/fedora/fedora-17-x86_64/pgdg-fedora92-9.2-5.noarch.rpm" + }, + "16" => { + "i386" => "http://yum.postgresql.org/9.2/fedora/fedora-16-i386/pgdg-fedora92-9.2-5.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.2/fedora/fedora-16-x86_64/pgdg-fedora92-9.2-5.noarch.rpm" + } + } + }, + "9.1" => { + "centos" => { + "6" => { + "i386" => "http://yum.postgresql.org/9.1/redhat/rhel-6-i386/pgdg-centos91-9.1-4.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.1/redhat/rhel-5-x86_64/pgdg-centos91-9.1-4.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.1/redhat/rhel-5-i386/pgdg-centos91-9.1-4.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.1/redhat/rhel-5-x86_64/pgdg-centos91-9.1-4.noarch.rpm" + }, + "4" => { + "i386" => "http://yum.postgresql.org/9.1/redhat/rhel-4-i386/pgdg-centos91-9.1-4.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.1/redhat/rhel-4-x86_64/pgdg-centos91-9.1-4.noarch.rpm" + } + }, + "redhat" => { + "6" => { + "i386" => "http://yum.postgresql.org/9.1/redhat/rhel-6-i386/pgdg-redhat91-9.1-5.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.1/redhat/rhel-6-x86_64/pgdg-redhat91-9.1-5.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.1/redhat/rhel-5-i386/pgdg-redhat91-9.1-5.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.1/redhat/rhel-5-x86_64/pgdg-redhat91-9.1-5.noarch.rpm" + }, + "4" => { + "i386" => "http://yum.postgresql.org/9.1/redhat/rhel-4-i386/pgdg-redhat-9.1-4.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.1/redhat/rhel-4-x86_64/pgdg-redhat-9.1-4.noarch.rpm" + } + }, + "scientific" => { + "6" => { + "i386" => "http://yum.postgresql.org/9.1/redhat/rhel-6-i386/pgdg-sl91-9.1-6.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.1/redhat/rhel-6-x86_64/pgdg-sl91-9.1-6.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.1/redhat/rhel-5-i386/pgdg-sl91-9.1-6.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.1/redhat/rhel-5-x86_64/pgdg-sl91-9.1-6.noarch.rpm" + } + }, + "fedora" => { + "16" => { + "i386" => "http://yum.postgresql.org/9.1/fedora/fedora-16-i386/pgdg-fedora91-9.1-4.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.1/fedora/fedora-16-x86_64/pgdg-fedora91-9.1-4.noarch.rpm" + }, + "15" => { + "i386" => "http://yum.postgresql.org/9.1/fedora/fedora-15-i386/pgdg-fedora91-9.1-4.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.1/fedora/fedora-15-x86_64/pgdg-fedora91-9.1-4.noarch.rpm" + }, + "14" => { + "i386" => "http://yum.postgresql.org/9.1/fedora/fedora-14-i386/pgdg-fedora91-9.1-4.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.1/fedora/fedora-14-x86_64/pgdg-fedora-9.1-2.noarch.rpm" + } + } + }, + "9.0" => { + "centos" => { + "6" => { + "i386" => "http://yum.postgresql.org/9.0/redhat/rhel-6-i386/pgdg-centos90-9.0-5.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.0/redhat/rhel-6-x86_64/pgdg-centos90-9.0-5.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.0/redhat/rhel-5-i386/pgdg-centos90-9.0-5.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.0/redhat/rhel-5-x86_64/pgdg-centos90-9.0-5.noarch.rpm" + }, + "4" => { + "i386" => "http://yum.postgresql.org/9.0/redhat/rhel-4-i386/pgdg-centos90-9.0-5.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.0/redhat/rhel-4-x86_64/pgdg-centos90-9.0-5.noarch.rpm" + } + }, + "redhat" => { + "6" => { + "i386" => "http://yum.postgresql.org/9.0/redhat/rhel-6-i386/pgdg-redhat90-9.0-5.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.0/redhat/rhel-6-x86_64/pgdg-redhat90-9.0-5.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.0/redhat/rhel-5-i386/pgdg-redhat90-9.0-5.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.0/redhat/rhel-5-x86_64/pgdg-redhat90-9.0-5.noarch.rpm" + }, + "4" => { + "i386" => "http://yum.postgresql.org/9.0/redhat/rhel-4-i386/pgdg-redhat90-9.0-5.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.0/redhat/rhel-4-x86_64/pgdg-redhat90-9.0-5.noarch.rpm" + } + }, + "scientific" => { + "6" => { + "i386" => "http://yum.postgresql.org/9.0/redhat/rhel-6-i386/pgdg-sl90-9.0-6.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.0/redhat/rhel-6-x86_64/pgdg-sl90-9.0-6.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/9.0/redhat/rhel-5-i386/pgdg-sl90-9.0-6.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.0/redhat/rhel-5-x86_64/pgdg-sl90-9.0-6.noarch.rpm" + } + }, + "fedora" => { + "15" => { + "i386" => "http://yum.postgresql.org/9.0/fedora/fedora-15-i386/pgdg-fedora90-9.0-5.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.0/fedora/fedora-15-x86_64/pgdg-fedora90-9.0-5.noarch.rpm" + }, + "14" => { + "i386" => "http://yum.postgresql.org/9.0/fedora/fedora-14-i386/pgdg-fedora90-9.0-5.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/9.0/fedora/fedora-14-x86_64/pgdg-fedora90-9.0-5.noarch.rpm" + } + } + }, + "8.4" => { + "centos" => { + "6" => { + "i386" => "http://yum.postgresql.org/8.4/redhat/rhel-6-i386/pgdg-centos-8.4-3.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/8.4/redhat/rhel-6-x86_64/pgdg-centos-8.4-3.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/8.4/redhat/rhel-5-i386/pgdg-centos-8.4-3.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/8.4/redhat/rhel-5-x86_64/pgdg-centos-8.4-3.noarch.rpm" + }, + "4" => { + "i386" => "http://yum.postgresql.org/8.4/redhat/rhel-4-i386/pgdg-centos-8.4-3.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/8.4/redhat/rhel-4-x86_64/pgdg-centos-8.4-3.noarch.rpm" + } + }, + "redhat" => { + "6" => { + "i386" => "http://yum.postgresql.org/8.4/redhat/rhel-6-i386/pgdg-redhat-8.4-3.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/8.4/redhat/rhel-6-x86_64/pgdg-redhat-8.4-3.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/8.4/redhat/rhel-5-i386/pgdg-redhat-8.4-3.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/8.4/redhat/rhel-5-x86_64/pgdg-redhat-8.4-3.noarch.rpm" + }, + "4" => { + "i386" => "http://yum.postgresql.org/8.4/redhat/rhel-4-i386/pgdg-redhat-8.4-3.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/8.4/redhat/rhel-4-x86_64/pgdg-redhat-8.4-3.noarch.rpm" + } + }, + "scientific" => { + "6" => { + "i386" => "http://yum.postgresql.org/8.4/redhat/rhel-6-i386/pgdg-sl84-8.4-4.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/8.4/redhat/rhel-6-x86_64/pgdg-sl84-8.4-4.noarch.rpm" + }, + "5" => { + "i386" => "http://yum.postgresql.org/8.4/redhat/rhel-5-i386/pgdg-sl-8.4-4.noarch.rpm", + "x86_64" => "http://yum.postgresql.org/8.4/redhat/rhel-5-x86_64/pgdg-sl-8.4-4.noarch.rpm" + } + }, + "fedora" => { + "14" => { + "i386" => "http://yum.postgresql.org/8.4/fedora/fedora-14-i386/", + "x86_64" => "http://yum.postgresql.org/8.4/fedora/fedora-14-x86_64/" + }, + "13" => { + "i386" => "http://yum.postgresql.org/8.4/fedora/fedora-13-i386/", + "x86_64" => "http://yum.postgresql.org/8.4/fedora/fedora-13-x86_64/" + }, + "12" => { + "i386" => "http://yum.postgresql.org/8.4/fedora/fedora-12-i386/", + "x86_64" => "http://yum.postgresql.org/8.4/fedora/fedora-12-x86_64/" + }, + "8" => { + "i386" => "http://yum.postgresql.org/8.4/fedora/fedora-8-i386/", + "x86_64" => "http://yum.postgresql.org/8.4/fedora/fedora-8-x86_64/" + }, + "7" => { + "i386" => "http://yum.postgresql.org/8.4/fedora/fedora-7-i386/", + "x86_64" => "http://yum.postgresql.org/8.4/fedora/fedora-7-x86_64/" + } + } + }, +}; + diff --git a/cookbooks/postgresql/files/default/tests/minitest/apt_pgdg_postgresql_test.rb b/cookbooks/postgresql/files/default/tests/minitest/apt_pgdg_postgresql_test.rb new file mode 100644 index 0000000..41f1fcb --- /dev/null +++ b/cookbooks/postgresql/files/default/tests/minitest/apt_pgdg_postgresql_test.rb @@ -0,0 +1,39 @@ +# +# Copyright 2012, Opscode, Inc. +# +# 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. +# + +require File.expand_path('../support/helpers', __FILE__) + +describe 'postgresql::apt_pgdg_postgresql' do + include Helpers::Postgresql + + it 'removes the Pitti PPA sources.list' do + skip unless %w{debian}.include?(node['platform_family']) + file("/etc/apt/sources.list.d/pitti-postgresql-ppa").wont_exist + end + it 'creates the PGDG apt sources.list' do + skip unless %w{debian}.include?(node['platform_family']) + file("/etc/apt/sources.list.d/apt.postgresql.org.list").must_exist + end + + it 'installs postgresql-client-9.3' do + package("postgresql-client-9.3").must_be_installed + end + + it 'makes psql version 9.3 available' do + psql = shell_out("psql --version") + assert psql.stdout.include?("psql (PostgreSQL) 9.3") + end +end diff --git a/cookbooks/postgresql/files/default/tests/minitest/default_test.rb b/cookbooks/postgresql/files/default/tests/minitest/default_test.rb new file mode 100644 index 0000000..8acbabf --- /dev/null +++ b/cookbooks/postgresql/files/default/tests/minitest/default_test.rb @@ -0,0 +1,27 @@ +# +# Copyright 2012, Opscode, Inc. +# +# 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. +# + +require File.expand_path('../support/helpers', __FILE__) + +describe 'postgresql::default' do + include Helpers::Postgresql + + it 'installs the postgresql client packages' do + node['postgresql']['client']['packages'].each do |pkg| + package(pkg).must_be_installed + end + end +end diff --git a/cookbooks/postgresql/files/default/tests/minitest/ruby_test.rb b/cookbooks/postgresql/files/default/tests/minitest/ruby_test.rb new file mode 100644 index 0000000..3b3649f --- /dev/null +++ b/cookbooks/postgresql/files/default/tests/minitest/ruby_test.rb @@ -0,0 +1,28 @@ +# +# Cookbook Name:: postgresql_test +# Recipe:: default +# +# Copyright 2012, Opscode, Inc. +# +# 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. +# + +require File.expand_path('../support/helpers', __FILE__) + +describe 'postgresql::ruby' do + include Helpers::Postgresql + + it 'installs the pg gem in Chefs ruby environment' do + assert Gem::Specification.all_names.grep("pg-.*") + end +end diff --git a/cookbooks/postgresql/files/default/tests/minitest/server_test.rb b/cookbooks/postgresql/files/default/tests/minitest/server_test.rb new file mode 100644 index 0000000..51c3a21 --- /dev/null +++ b/cookbooks/postgresql/files/default/tests/minitest/server_test.rb @@ -0,0 +1,43 @@ +# +# Copyright 2012, Opscode, Inc. +# +# 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. +# + +require File.expand_path('../support/helpers', __FILE__) + +describe 'postgresql::server' do + include Helpers::Postgresql + + it 'installs the postgresql server packages' do + node['postgresql']['server']['packages'].each do |pkg| + package(pkg).must_be_installed + end + end + + it 'runs the postgresql service' do + service((node['postgresql']['server']['service_name'] || 'postgresql')).must_be_running + end + + it 'can connect to postgresql' do + require 'pg' + conn = PG::Connection.new( + :host => 'localhost', + :port => '5432', + :password => node['postgresql']['password']['postgres'], + :user => "postgres" + ) + assert_match(/localhost/, conn.host) + end + +end diff --git a/cookbooks/postgresql/files/default/tests/minitest/support/helpers.rb b/cookbooks/postgresql/files/default/tests/minitest/support/helpers.rb new file mode 100644 index 0000000..fd8fcea --- /dev/null +++ b/cookbooks/postgresql/files/default/tests/minitest/support/helpers.rb @@ -0,0 +1,29 @@ +# +# Cookbook Name:: postgresql_test +# Recipe:: default +# +# Copyright 2012, Opscode, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +module Helpers + module Postgresql + require 'chef/mixin/shell_out' + include Chef::Mixin::ShellOut + include MiniTest::Chef::Assertions + include MiniTest::Chef::Context + include MiniTest::Chef::Resources + + end +end diff --git a/cookbooks/postgresql/libraries/default.rb b/cookbooks/postgresql/libraries/default.rb new file mode 100644 index 0000000..176ff72 --- /dev/null +++ b/cookbooks/postgresql/libraries/default.rb @@ -0,0 +1,377 @@ +# +# Cookbook Name:: postgresql +# Library:: default +# Author:: David Crane () +# +# 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. +# + +include Chef::Mixin::ShellOut + +module Opscode + module PostgresqlHelpers + +####### +# Function to truncate value to 4 significant bits, render human readable. +# Used in recipes/config_initdb.rb to set this attribute: +# +# The memory settings (shared_buffers, effective_cache_size, work_mem, +# maintenance_work_mem and wal_buffers) will be rounded down to keep +# the 4 most significant bits, so that SHOW will be likely to use a +# larger divisor. The output is actually a human readable string that +# ends with "GB", "MB" or "kB" if over 1023, exactly what Postgresql +# will expect in a postgresql.conf setting. The output may be up to +# 6.25% less than the original value because of the rounding. +def binaryround(value) + + # Keep a multiplier which grows through powers of 1 + multiplier = 1 + + # Truncate value to 4 most significant bits + while value >= 16 + value = (value / 2).floor + multiplier = multiplier * 2 + end + + # Factor any remaining powers of 2 into the multiplier + while value == 2*((value / 2).floor) + value = (value / 2).floor + multiplier = multiplier * 2 + end + + # Factor enough powers of 2 back into the value to + # leave the multiplier as a power of 1024 that can + # be represented as units of "GB", "MB" or "kB". + if multiplier >= 1024*1024*1024 + while multiplier > 1024*1024*1024 + value = 2*value + multiplier = (multiplier/2).floor + end + multiplier = 1 + units = "GB" + + elsif multiplier >= 1024*1024 + while multiplier > 1024*1024 + value = 2*value + multiplier = (multiplier/2).floor + end + multiplier = 1 + units = "MB" + + elsif multiplier >= 1024 + while multiplier > 1024 + value = 2*value + multiplier = (multiplier/2).floor + end + multiplier = 1 + units = "kB" + + else + units = "" + end + + # Now we can return a nice human readable string. + return "#{multiplier * value}#{units}" +end + +####### +# Locale Configuration + +# Function to test the date order. +# Used in recipes/config_initdb.rb to set this attribute: +# node.default['postgresql']['config']['datestyle'] +def locale_date_order + # Test locale conversion of mon=11, day=22, year=33 + testtime = DateTime.new(2033,11,22,0,0,0,"-00:00") + #=> # + + # %x - Preferred representation for the date alone, no time + res = testtime.strftime("%x") + + if res.nil? + return 'mdy' + end + + posM = res.index("11") + posD = res.index("22") + posY = res.index("33") + + if (posM.nil? || posD.nil? || posY.nil?) + return 'mdy' + elseif (posY < posM && posM < posD) + return 'ymd' + elseif (posD < posM) + return 'dmy' + else + return 'mdy' + end +end + +####### +# Timezone Configuration +require 'find' + +# Function to determine where the system stored shared timezone data. +# Used in recipes/config_initdb.rb to detemine where it should have +# select_default_timezone(tzdir) search. +def pg_TZDIR() + # System time zone conversions are controlled by a timezone data file + # identified through environment variables (TZ and TZDIR) and/or file + # and directory naming conventions specific to the Linux distribution. + # Each of these timezone names will have been loaded into the PostgreSQL + # pg_timezone_names view by the package maintainer. + # + # Instead of using the timezone name configured as the system default, + # the PostgreSQL server uses ones named in postgresql.conf settings + # (timezone and log_timezone). The initdb utility does initialize those + # settings to the timezone name that corresponds to the system default. + # + # The system's timezone name is actually a filename relative to the + # shared zoneinfo directory. That is usually /usr/share/zoneinfo, but + # it was /usr/lib/zoneinfo in older distributions and can be anywhere + # if specified by the environment variable TZDIR. The tzset(3) manpage + # seems to indicate the following precedence: + tzdir = nil + if ::File.directory?("/usr/lib/zoneinfo") + tzdir = "/usr/lib/zoneinfo" + else + share_path = [ ENV['TZDIR'], "/usr/share/zoneinfo" ].compact.first + if ::File.directory?(share_path) + tzdir = share_path + end + end + return tzdir +end + +####### +# Function to support select_default_timezone(tzdir), which is +# used in recipes/config_initdb.rb. +def validate_zone(tzname) + # PostgreSQL does not support leap seconds, so this function tests + # the usual Linux tzname convention to avoid a misconfiguration. + # Assume that the tzdata package maintainer has kept all timezone + # data files with support for leap seconds is kept under the + # so-named "right/" subdir of the shared zoneinfo directory. + # + # The original PostgreSQL initdb is not Unix-specific, so it did a + # very complicated, thorough test in its pg_tz_acceptable() function + # that I could not begin to understand how to do in ruby :). + # + # Testing the tzname is good enough, since a misconfiguration + # will result in an immediate fatal error when the PostgreSQL + # service is started, with pgstartup.log messages such as: + # LOG: time zone "right/US/Eastern" appears to use leap seconds + # DETAIL: PostgreSQL does not support leap seconds. + + if tzname.index("right/") == 0 + return false + else + return true + end +end + +# Function to support select_default_timezone(tzdir), which is +# used in recipes/config_initdb.rb. +def scan_available_timezones(tzdir) + # There should be an /etc/localtime zoneinfo file that is a link to + # (or a copy of) a timezone data file under tzdir, which should have + # been installed under the "share" directory by the tzdata package. + # + # The initdb utility determines which shared timezone file is being + # used as the system's default /etc/localtime. The timezone name is + # the timezone file path relative to the tzdir. + + bestzonename = nil + + if (tzdir.nil?) + Chef::Log.error("The zoneinfo directory not found (looked for /usr/share/zoneinfo and /usr/lib/zoneinfo)") + elsif !::File.exists?("/etc/localtime") + Chef::Log.error("The system zoneinfo file not found (looked for /etc/localtime)") + elsif ::File.directory?("/etc/localtime") + Chef::Log.error("The system zoneinfo file not found (/etc/localtime is a directory instead)") + elsif ::File.symlink?("/etc/localtime") + # PostgreSQL initdb doesn't use the symlink target, but this + # certainly will make sense to any system administrator. A full + # scan of the tzdir to find the shortest filename could result + # "US/Eastern" instead of "America/New_York" as bestzonename, + # in spite of what the sysadmin had specified in the symlink. + # (There are many duplicates under tzdir, with the same timezone + # content appearing as an average of 2-3 different file names.) + path = ::File.readlink("/etc/localtime") + bestzonename = path.gsub("#{tzdir}/","") + else # /etc/localtime is a file, so scan for it under tzdir + localtime_content = File.read("/etc/localtime") + + Find.find(tzdir) do |path| + # Only consider files (skip directories or symlinks) + if !::File.directory?(path) && !::File.symlink?(path) + # Ignore any file named "posixrules" or "localtime" + if ::File.basename(path) != "posixrules" && ::File.basename(path) != "localtime" + # Do consider if content exactly matches /etc/localtime. + if localtime_content == File.read(path) + tzname = path.gsub("#{tzdir}/","") + if validate_zone(tzname) + if (bestzonename.nil? || + tzname.length < bestzonename.length || + (tzname.length == bestzonename.length && + (tzname <=> bestzonename) < 0) + ) + bestzonename = tzname + end + end + end + end + end + end + end + + return bestzonename +end + +# Function to support select_default_timezone(tzdir), which is +# used in recipes/config_initdb.rb. +def identify_system_timezone(tzdir) + resultbuf = scan_available_timezones(tzdir) + + if !resultbuf.nil? + # Ignore Olson's rather silly "Factory" zone; use GMT instead + if (resultbuf <=> "Factory") == 0 + resultbuf = nil + end + + else + # Did not find the timezone. Fallback to use a GMT zone. Note that the + # Olson timezone database names the GMT-offset zones in POSIX style: plus + # is west of Greenwich. + testtime = DateTime.now + std_ofs = testtime.strftime("%:z").split(":")[0].to_i + + resultbuf = [ + "Etc/GMT", + (-std_ofs > 0) ? "+" : "", + (-std_ofs).to_s + ].join('') + end + + return resultbuf +end + +####### +# Function to determine the name of the system's default timezone. +# Used in recipes/config_initdb.rb to set these attributes: +# node.default['postgresql']['config']['log_timezone'] +# node.default['postgresql']['config']['timezone'] +def select_default_timezone(tzdir) + + system_timezone = nil + + # Check TZ environment variable + tzname = ENV['TZ'] + if !tzname.nil? && !tzname.empty? && validate_zone(tzname) + system_timezone = tzname + + else + # Nope, so try to identify system timezone from /etc/localtime + tzname = identify_system_timezone(tzdir) + if validate_zone(tzname) + system_timezone = tzname + end + end + + return system_timezone +end + +####### +# Function to determine the name of the system's default timezone. +def get_result_orig(query) + # query could be a String or an Array of String + if (query.is_a?(String)) + stdin = query + else + stdin = query.join("\n") + end + @get_result ||= begin + cmd = shell_out("cat", :input => stdin) + cmd.stdout + end +end + +####### +# Function to execute an SQL statement in the default database. +# Input: Query could be a single String or an Array of String. +# Output: A String with |-separated columns and \n-separated rows. +# Note an empty output could mean psql couldn't connect. +# This is easiest for 1-field (1-row, 1-col) results, otherwise +# it will be complex to parse the results. +def execute_sql(query) + db_name = node['postgresql']['database_name'] + # query could be a String or an Array of String + statement = query.is_a?(String) ? query : query.join("\n") + @execute_sql ||= begin + cmd = shell_out("psql -q --tuples-only --no-align -d #{db_name} -f -", + :user => "postgres", + :input => statement + ) + # If psql fails, generally the postgresql service is down. + # Instead of aborting chef with a fatal error, let's just + # pass these non-zero exitstatus back as empty cmd.stdout. + if (cmd.exitstatus() == 0 and !cmd.stderr.empty?) + # An SQL failure is still a zero exitstatus, but then the + # stderr explains the error, so let's rais that as fatal. + Chef::Log.fatal("psql failed executing this SQL statement:\n#{statement}") + Chef::Log.fatal(cmd.stderr) + raise "SQL ERROR" + end + cmd.stdout.chomp + end +end + +####### +# Function to determine if a standard contrib extension is already installed. +# Input: Extension name +# Output: true or false +# Best use as a not_if gate on bash "install-#{pg_ext}-extension" resource. +def extension_installed?(pg_ext) + @extension_installed ||= begin + installed=execute_sql("select 'installed' from pg_extension where extname = '#{pg_ext}';") + installed =~ /^installed$/ + end +end + +###################################### +# Function to build information needed to install RPM for PGDG yum repository, +# since PGDG supports several versions of PostgreSQL, platforms, platform versions +# and architectures. +# Links to RPMs for installation are in an attribute so that new versions/platforms +# can be more easily added. (See attributes/default.rb) +def pgdgrepo_rpm_info + repo_rpm_url = node['postgresql']['pgdg']['repo_rpm_url']. + fetch(node['postgresql']['version']). # e.g., fetch for "9.1" + fetch(node['platform']). # e.g., fetch for "centos" + fetch(node['platform_version'].to_f.to_i.to_s). # e.g., fetch for "5" (truncated "5.7") + fetch(node['kernel']['machine']) # e.g., fetch for "i386" or "x86_64" + + # Extract the filename portion from the URL for the PGDG repository RPM. + # E.g., repo_rpm_filename = "pgdg-centos92-9.2-6.noarch.rpm" + repo_rpm_filename = File.basename(repo_rpm_url) + + # Extract the package name from the URL for the PGDG repository RPM. + # E.g., repo_rpm_package = "pgdg-centos92" + repo_rpm_package = repo_rpm_filename.split(/-/,3)[0..1].join('-') + + return [ repo_rpm_url, repo_rpm_filename, repo_rpm_package ] +end + +# End the Opscode::PostgresqlHelpers module + end +end diff --git a/cookbooks/postgresql/metadata.json b/cookbooks/postgresql/metadata.json new file mode 100644 index 0000000..5989b72 --- /dev/null +++ b/cookbooks/postgresql/metadata.json @@ -0,0 +1,56 @@ +{ + "name": "postgresql", + "description": "Installs and configures postgresql for clients or servers", + "long_description": "Description\n===========\n\nInstalls and configures PostgreSQL as a client or a server.\n\nRequirements\n============\n\n## Platforms\n\n* Debian, Ubuntu\n* Red Hat/CentOS/Scientific (6.0+ required) - \"EL6-family\"\n* Fedora\n* SUSE\n\nTested on:\n\n* Ubuntu 10.04, 11.10, 12.04, 14.04, 14.10\n* Red Hat 6.1, Scientific 6.1, CentOS 6.3\n\n## Cookbooks\n\nRequires Opscode's `openssl` cookbook for secure password generation.\n\nRequires a C compiler and development headers in order to build the\n`pg` RubyGem to provide Ruby bindings in the `ruby` recipe.\n\nOpscode's `build-essential` cookbook provides this functionality on\nDebian, Ubuntu, and EL6-family.\n\nWhile not required, Opscode's `database` cookbook contains resources\nand providers that can interact with a PostgreSQL database. This\ncookbook is a dependency of database.\n\nAttributes\n==========\n\nThe following attributes are set based on the platform, see the\n`attributes/default.rb` file for default values.\n\n* `node['postgresql']['version']` - version of postgresql to manage\n* `node['postgresql']['dir']` - home directory of where postgresql\n data and configuration lives.\n\n* `node['postgresql']['client']['packages']` - An array of package names\n that should be installed on \"client\" systems.\n* `node['postgresql']['server']['packages']` - An array of package names\n that should be installed on \"server\" systems.\n* `node['postgresql']['server']['config_change_notify']` - Type of\n notification triggered when a config file changes.\n* `node['postgresql']['contrib']['packages']` - An array of package names\n that could be installed on \"server\" systems for useful sysadmin tools.\n\n* `node['postgresql']['enable_pgdg_apt']` - Whether to enable the apt repo\n by the PostgreSQL Global Development Group, which contains newer versions\n of PostgreSQL.\n\n* `node['postgresql']['enable_pgdg_yum']` - Whether to enable the yum repo\n by the PostgreSQL Global Development Group, which contains newer versions\n of PostgreSQL.\n\n* `node['postgresql']['initdb_locale']` - Sets the default locale for the\n database cluster. If this attribute is not specified, the locale is\n inherited from the environment that initdb runs in. Sometimes you must\n have a system locale that is not what you want for your database cluster,\n and this attribute addresses that scenario. Valid only for EL-family\n distros (RedHat/Centos/etc.).\n\nThe following attributes are generated in\n`recipe[postgresql::server]`.\n\n* `node['postgresql']['password']['postgres']` - randomly generated\n password by the `openssl` cookbook's library.\n (TODO: This is broken, as it disables the password.)\n\nConfiguration\n-------------\n\nThe `postgresql.conf` and `pg_hba.conf` files are dynamically\ngenerated from attributes. Each key in `node['postgresql']['config']`\nis a postgresql configuration directive, and will be rendered in the\nconfig file. For example, the attribute:\n\n node['postgresql']['config']['listen_addresses'] = 'localhost'\n\nWill result in the following line in the `postgresql.conf` file:\n\n listen_addresses = 'localhost'\n\nThe attributes file contains default values for Debian and RHEL\nplatform families (per the `node['platform_family']`). These defaults\nhave disparity between the platforms because they were originally\nextracted from the postgresql.conf files in the previous version of\nthis cookbook, which differed in their default config. The resulting\nconfiguration files will be the same as before, but the content will\nbe dynamically rendered from the attributes. The helpful commentary\nwill no longer be present. You should consult the PostgreSQL\ndocumentation for specific configuration details.\n\nSee __Recipes__ `config_initdb` and `config_pgtune` below to\nauto-generate many postgresql.conf settings.\n\nFor values that are \"on\" or \"off\", they should be specified as literal\n`true` or `false`. String values will be used with single quotes. Any\nconfiguration option set to the literal `nil` will be skipped\nentirely. All other values (e.g., numeric literals) will be used as\nis. So for example:\n\n node.default['postgresql']['config']['logging_collector'] = true\n node.default['postgresql']['config']['datestyle'] = 'iso, mdy'\n node.default['postgresql']['config']['ident_file'] = nil\n node.default['postgresql']['config']['port'] = 5432\n\nWill result in the following config lines:\n\n logging_collector = 'on'\n datestyle = 'iso,mdy'\n port = 5432\n\n(no line printed for `ident_file` as it is `nil`)\n\nNote that the `unix_socket_directory` configuration was renamed to\n`unix_socket_directories` in Postgres 9.3 so make sure to use the\n`node['postgresql']['unix_socket_directories']` attribute instead of\n`node['postgresql']['unix_socket_directory']`.\n\nThe `pg_hba.conf` file is dynamically generated from the\n`node['postgresql']['pg_hba']` attribute. This attribute must be an\narray of hashes, each hash containing the authorization data. As it is\nan array, you can append to it in your own recipes. The hash keys in\nthe array must be symbols. Each hash will be written as a line in\n`pg_hba.conf`. For example, this entry from\n`node['postgresql']['pg_hba']`:\n\n [{:comment => '# Optional comment',\n :type => 'local', :db => 'all', :user => 'postgres', :addr => nil, :method => 'md5'}]\n\nWill result in the following line in `pg_hba.conf`:\n\n # Optional comment\n local all postgres md5\n\nUse `nil` if the CIDR-ADDRESS should be empty (as above).\nDon't provide a comment if none is desired in the `pg_hba.conf` file.\n\nNote that the following authorization rule is supplied automatically by\nthe cookbook template. The cookbook needs this to execute SQL in the\nPostgreSQL server without supplying the clear-text password (which isn't\nknown by the cookbook). Therefore, your `node['postgresql']['pg_hba']`\nattributes don't need to specify this authorization rule:\n\n # \"local\" is for Unix domain socket connections only\n local all all ident\n\n(By the way, the template uses `peer` instead of `ident` for PostgreSQL-9.1\nand above, which has the same effect.)\n\nRecipes\n=======\n\ndefault\n-------\n\nIncludes the client recipe.\n\nclient\n------\n\nInstalls the packages defined in the\n`node['postgresql']['client']['packages']` attribute.\n\nruby\n----\n\n**NOTE** This recipe may not currently work when installing Chef with\n the\n [\"Omnibus\" full stack installer](http://opscode.com/chef/install) on\n some platforms due to an incompatibility with OpenSSL. See\n [COOK-1406](http://tickets.opscode.com/browse/COOK-1406). You can\n build from source into the Chef omnibus installation to work around\n this issue.\n\nInstall the `pg` gem under Chef's Ruby environment so it can be used\nin other recipes. The build-essential packages and postgresql client\npackages will be installed during the compile phase, so that the\nnative extensions of `pg` can be compiled.\n\nserver\n------\n\nIncludes the `server_debian` or `server_redhat` recipe to get the\nappropriate server packages installed and service managed. Also\nmanages the configuration for the server:\n\n* generates a strong default password (via `openssl`) for `postgres`\n (TODO: This is broken, as it disables the password.)\n* sets the password for postgres\n* manages the `postgresql.conf` file.\n* manages the `pg_hba.conf` file.\n\nserver\\_debian\n--------------\n\nInstalls the postgresql server packages and sets up the service. You\nshould include the `postgresql::server` recipe, which will include\nthis on Debian platforms.\n\nserver\\_redhat\n--------------\n\nManages the postgres user and group (with UID/GID 26, per RHEL package\nconventions), installs the postgresql server packages, initializes the\ndatabase, and manages the postgresql service. You should include the\n`postgresql::server` recipe, which will include this on RHEL/Fedora\nplatforms.\n\nconfig\\_initdb\n--------------\n\nTakes locale and timezone settings from the system configuration.\nThis recipe creates `node.default['postgresql']['config']` attributes\nthat conform to the system's locale and timezone. In addition, this\nrecipe creates the same error reporting and logging settings that\n`initdb` provided: a rotation of 7 days of log files named\npostgresql-Mon.log, etc.\n\nThe default attributes created by this recipe are easy to override with\nnormal attributes because of Chef attribute precedence. For example,\nsuppose a DBA wanted to keep log files indefinitely, rolling over daily\nor when growing to 10MB. The Chef installation could include the\n`postgresql::config_initdb` recipe for the locale and timezone settings,\nbut customize the logging settings with these node JSON attributes:\n\n \"postgresql\": {\n \"config\": {\n \"log_rotation_age\": \"1d\",\n \"log_rotation_size\": \"10MB\",\n \"log_filename\": \"postgresql-%Y-%m-%d_%H%M%S.log\"\n }\n }\n\nCredits: This `postgresql::config_initdb` recipe is based on algorithms\nin the [source code](http://doxygen.postgresql.org/initdb_8c_source.html)\nfor the PostgreSQL `initdb` utility.\n\nconfig\\_pgtune\n--------------\n\nPerformance tuning.\nTakes the wimpy default postgresql.conf and expands the database server\nto be as powerful as the hardware it's being deployed on. This recipe\ncreates a baseline configuration of `node.default['postgresql']['config']`\nattributes in the right general range for a dedicated Postgresql system.\nMost installations won't need additional performance tuning.\n\nThe only decision you need to make is to choose a `db_type` from the\nfollowing database workloads. (See the recipe code comments for more\ndetailed descriptions.)\n\n * \"dw\" -- Data Warehouse\n * \"oltp\" -- Online Transaction Processing\n * \"web\" -- Web Application\n * \"mixed\" -- Mixed DW and OLTP characteristics\n * \"desktop\" -- Not a dedicated database\n\nThis recipe uses a performance model with three input parameters.\nThese node attributes are completely optional, but it is obviously\nimportant to choose the `db_type` correctly:\n\n * `node['postgresql']['config_pgtune']['db_type']` --\n Specifies database type from the list of five choices above.\n If not specified, the default is \"mixed\".\n\n * `node['postgresql']['config_pgtune']['max_connections']` --\n Specifies maximum number of connections expected.\n If not specified, it depends on database type:\n \"web\":200, \"oltp\":300, \"dw\":20, \"mixed\":80, \"desktop\":5\n\n * `node['postgresql']['config_pgtune']['total_memory']` --\n Specifies total system memory in kB. (E.g., \"49416564kB\".)\n If not specified, it will be taken from Ohai automatic attributes.\n This could be used to tune a system that isn't a dedicated database.\n\nThe default attributes created by this recipe are easy to override with\nnormal attributes because of Chef attribute precedence. For example, if\nyou are running application benchmarks to try different buffer cache\nsizes, you would experiment with this node JSON attribute:\n\n \"postgresql\": {\n \"config\": {\n \"shared_buffers\": \"3GB\"\n }\n }\n\nNote that the recipe uses `max_connections` in its computations. If\nyou want to override that setting, you should specify\n`node['postgresql']['config_pgtune']['max_connections']` instead of\n`node['postgresql']['config']['max_connections']`.\n\nCredits: This `postgresql::config_pgtune` recipe is based on the\n[pgtune python script](https://github.com/gregs1104/pgtune)\ndeveloped by\n[Greg Smith](http://notemagnet.blogspot.com/2008/11/automating-initial-postgresqlconf.html)\nand\n[other pgsql-hackers](http://www.postgresql.org/message-id/491C6CDC.8090506@agliodbs.com).\n\ncontrib\n-------\n\nInstalls the packages defined in the\n`node['postgresql']['contrib']['packages']` attribute. The contrib\ndirectory of the PostgreSQL distribution includes porting tools,\nanalysis utilities, and plug-in features that database engineers often\nrequire. Some (like `pgbench`) are executable. Others (like\n`pg_buffercache`) would need to be installed into the database.\n\nAlso installs any contrib module extensions defined in the\n`node['postgresql']['contrib']['extensions']` attribute. These will be\navailable in any subsequently created databases in the cluster, because\nthey will be installed into the `template1` database using the\n`CREATE EXTENSION` command. For example, it is often necessary/helpful\nfor problem troubleshooting and maintenance planning to install the\nviews and functions in these [standard instrumentation extensions]\n(http://www.postgresql.org/message-id/flat/4DC32600.6080900@pgexperts.com#4DD3D6C6.5060006@2ndquadrant.com):\n\n node['postgresql']['contrib']['extensions'] = [\n \"pageinspect\",\n \"pg_buffercache\",\n \"pg_freespacemap\",\n \"pgrowlocks\",\n \"pg_stat_statements\",\n \"pgstattuple\"\n ]\n\nNote that the `pg_stat_statements` view only works if `postgresql.conf`\nloads its shared library, which can be done with this node attribute:\n\n node['postgresql']['config']['shared_preload_libraries'] = 'pg_stat_statements'\n\nIf using `shared_preload_libraries` in combination with the `contrib` recipe,\nmake sure that the `contrib` recipe is called before the `server` recipe (to\nensure the dependencies are installed and setup in order).\n\napt\\_pgdg\\_postgresql\n----------------------\n\nEnables the PostgreSQL Global Development Group yum repository\nmaintained by Devrim Gündüz for updated PostgreSQL packages.\n(The PGDG is the groups that develops PostgreSQL.)\nAutomatically included if the `node['postgresql']['enable_pgdg_apt']`\nattribute is true. Also set the\n`node['postgresql']['client']['packages']` and\n`node['postgresql']['server]['packages']` to the list of packages to\nuse from this repository, and set the `node['postgresql']['version']`\nattribute to the version to use (e.g., \"9.2\").\n\nyum\\_pgdg\\_postgresql\n---------------------\n\nEnables the PostgreSQL Global Development Group yum repository\nmaintained by Devrim Gündüz for updated PostgreSQL packages.\n(The PGDG is the groups that develops PostgreSQL.)\nAutomatically included if the `node['postgresql']['enable_pgdg_yum']`\nattribute is true. Also use `override_attributes` to set a number of\nvalues that will need to have embedded version numbers. For example:\n\n node['postgresql']['enable_pgdg_yum'] = true\n node['postgresql']['version'] = \"9.2\"\n node['postgresql']['dir'] = \"/var/lib/pgsql/9.2/data\"\n node['postgresql']['config']['data_directory'] = node['postgresql']['dir']\n node['postgresql']['client']['packages'] = [\"postgresql92\", \"postgresql92-devel\"]\n node['postgresql']['server']['packages'] = [\"postgresql92-server\"]\n node['postgresql']['server']['service_name'] = \"postgresql-9.2\"\n node['postgresql']['contrib']['packages'] = [\"postgresql92-contrib\"]\n\nYou may set `node['postgresql']['pgdg']['repo_rpm_url']` attributes\nto pick up recent [PGDG repo packages](http://yum.postgresql.org/repopackages.php).\n\nResources/Providers\n===================\n\nSee the [database](http://community.opscode.com/cookbooks/database)\nfor resources and providers that can be used for managing PostgreSQL\nusers and databases.\n\nUsage\n=====\n\nOn systems that need to connect to a PostgreSQL database, add to a run\nlist `recipe[postgresql]` or `recipe[postgresql::client]`.\n\nOn systems that should be PostgreSQL servers, use\n`recipe[postgresql::server]` on a run list. This recipe does set a\npassword for the `postgres` user.\nIf you're using `chef server`, if the attribute\n`node['postgresql']['password']['postgres']` is not found,\nthe recipe generates a random password and performs a node.save.\n(TODO: This is broken, as it disables the password.)\nIf you're using `chef-solo`, you'll need\nto set the attribute `node['postgresql']['password']['postgres']` in\nyour node's `json_attribs` file or in a role.\n\nOn Debian family systems, SSL will be enabled, as the packages on\nDebian/Ubuntu also generate the SSL certificates. If you use another\nplatform and wish to use SSL in postgresql, then generate your SSL\ncertificates and distribute them in your own cookbook, and set the\n`node['postgresql']['config']['ssl']` attribute to true in your\nrole/cookboook/node.\n\nOn server systems, the postgres server is restarted when a configuration\nfile changes. This can be changed to reload only by setting the\nfollowing attribute:\n\n node['postgresql']['server']['config_change_notify'] = :reload\n\nChef Solo Note\n==============\n\nThe following node attribute is stored on the Chef Server when using\n`chef-client`. Because `chef-solo` does not connect to a server or\nsave the node object at all, to have the password persist across\n`chef-solo` runs, you must specify them in the `json_attribs` file\nused. For Example:\n\n {\n \"postgresql\": {\n \"password\": {\n \"postgres\": \"iloverandompasswordsbutthiswilldo\"\n }\n },\n \"run_list\": [\"recipe[postgresql::server]\"]\n }\n\nThat should actually be the \"encrypted password\" instead of cleartext,\nso you should generate it as an md5 hash using the PostgreSQL algorithm.\n\n* You could copy the md5-hashed password from an existing postgres\ndatabase if you have `postgres` access and want to use the same password:
    \n`select * from pg_shadow where usename='postgres';`\n* You can run this from any postgres database session to use a new password:
    \n`select 'md5'||md5('iloverandompasswordsbutthiswilldo'||'postgres');`\n* You can run this from a linux commandline:
    \n`echo -n 'iloverandompasswordsbutthiswilldo''postgres' | openssl md5 | sed -e 's/.* /md5/'`\n\nLicense and Author\n==================\n\n- Author:: Joshua Timberman ()\n- Author:: Lamont Granquist ()\n- Author:: Chris Roberts ()\n- Author:: David Crane ()\n- Author:: Aaron Baer ()\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n", + "maintainer": "Heavy Water Operations, LLC", + "maintainer_email": "support@hw-ops.com", + "license": "Apache 2.0", + "platforms": { + "ubuntu": "< 14.10", + "debian": ">= 0.0.0", + "fedora": ">= 0.0.0", + "suse": ">= 0.0.0", + "amazon": ">= 0.0.0", + "redhat": "~> 6.0", + "centos": "~> 6.0", + "scientific": "~> 6.0", + "oracle": "~> 6.0" + }, + "dependencies": { + "apt": ">= 1.9.0", + "build-essential": ">= 0.0.0", + "openssl": "~> 4.0.0" + }, + "recommendations": { + + }, + "suggestions": { + + }, + "conflicting": { + + }, + "providing": { + + }, + "replacing": { + + }, + "attributes": { + + }, + "groupings": { + + }, + "recipes": { + "postgresql": "Includes postgresql::client", + "postgresql::ruby": "Installs pg gem for Ruby bindings", + "postgresql::client": "Installs postgresql client package(s)", + "postgresql::server": "Installs postgresql server packages, templates", + "postgresql::server_redhat": "Installs postgresql server packages, redhat family style", + "postgresql::server_debian": "Installs postgresql server packages, debian family style" + }, + "version": "3.4.20", + "source_url": "", + "issues_url": "" +} diff --git a/cookbooks/postgresql/metadata.rb b/cookbooks/postgresql/metadata.rb new file mode 100644 index 0000000..6a43ddc --- /dev/null +++ b/cookbooks/postgresql/metadata.rb @@ -0,0 +1,28 @@ +name "postgresql" +maintainer "Heavy Water Operations, LLC" +maintainer_email "support@hw-ops.com" +license "Apache 2.0" +description "Installs and configures postgresql for clients or servers" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version "3.4.20" +recipe "postgresql", "Includes postgresql::client" +recipe "postgresql::ruby", "Installs pg gem for Ruby bindings" +recipe "postgresql::client", "Installs postgresql client package(s)" +recipe "postgresql::server", "Installs postgresql server packages, templates" +recipe "postgresql::server_redhat", "Installs postgresql server packages, redhat family style" +recipe "postgresql::server_debian", "Installs postgresql server packages, debian family style" + + +supports "ubuntu", "< 14.10" + +%w{debian fedora suse amazon}.each do |os| + supports os +end + +%w{redhat centos scientific oracle}.each do |el| + supports el, "~> 6.0" +end + +depends "apt", ">= 1.9.0" +depends "build-essential" +depends "openssl", "~> 4.0.0" diff --git a/cookbooks/postgresql/recipes/apt_pgdg_postgresql.rb b/cookbooks/postgresql/recipes/apt_pgdg_postgresql.rb new file mode 100644 index 0000000..bab1afe --- /dev/null +++ b/cookbooks/postgresql/recipes/apt_pgdg_postgresql.rb @@ -0,0 +1,18 @@ +if not %w(jessie squeeze wheezy sid lucid precise saucy trusty utopic).include? node['postgresql']['pgdg']['release_apt_codename'] + raise "Not supported release by PGDG apt repository" +end + +include_recipe 'apt' + +file "remove deprecated Pitti PPA apt repository" do + action :delete + path "/etc/apt/sources.list.d/pitti-postgresql-ppa" +end + +apt_repository 'apt.postgresql.org' do + uri 'http://apt.postgresql.org/pub/repos/apt' + distribution "#{node['postgresql']['pgdg']['release_apt_codename']}-pgdg" + components ['main', node['postgresql']['version']] + key 'https://www.postgresql.org/media/keys/ACCC4CF8.asc' + action :add +end diff --git a/cookbooks/postgresql/recipes/client.rb b/cookbooks/postgresql/recipes/client.rb new file mode 100644 index 0000000..5a94780 --- /dev/null +++ b/cookbooks/postgresql/recipes/client.rb @@ -0,0 +1,32 @@ +# +# Cookbook Name:: postgresql +# Recipe:: client +# +# 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. +# + +if platform_family?('debian') && node['postgresql']['version'].to_f > 9.3 + node.default['postgresql']['enable_pgdg_apt'] = true +end + +if(node['postgresql']['enable_pgdg_apt']) and platform_family?('debian') + include_recipe 'postgresql::apt_pgdg_postgresql' +end + +if(node['postgresql']['enable_pgdg_yum']) and platform_family?('rhel') + include_recipe 'postgresql::yum_pgdg_postgresql' +end + +node['postgresql']['client']['packages'].each do |pg_pack| + package pg_pack +end diff --git a/cookbooks/postgresql/recipes/config_initdb.rb b/cookbooks/postgresql/recipes/config_initdb.rb new file mode 100644 index 0000000..92973ef --- /dev/null +++ b/cookbooks/postgresql/recipes/config_initdb.rb @@ -0,0 +1,148 @@ +# +# Cookbook Name:: postgresql +# Recipe:: config_initdb +# Author:: David Crane () +# +# 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. +# + +####### +# Load the locale_date_order() and select_default_timezone(tzdir) +# methods from libraries/default.rb +::Chef::Recipe.send(:include, Opscode::PostgresqlHelpers) + +####### +# This recipe is derived from the setup_config() source code in the +# PostgreSQL initdb utility. It determines postgresql.conf settings that +# conform to the system's locale and timezone configuration, and also +# sets the error reporting and logging settings. +# +# See http://doxygen.postgresql.org/initdb_8c_source.html for the +# original initdb source code. +# +# By examining the system configuration, this recipe will set the +# following node.default['postgresql']['config'] attributes: +# +# - Locale and Formatting - +# * datestyle +# * lc_messages +# * lc_monetary +# * lc_numeric +# * lc_time +# * default_text_search_config +# +# - Timezone Conversion - +# * log_timezone +# * timezone +# +# In addition, this recipe will recommend the same error reporting and +# logging settings that initdb provided. These settings do differ from +# the PostgreSQL default settings, which would log to stderr only. The +# initdb settings rotate 7 days of log files named postgresql-Mon.log, +# etc. through these node.default['postgresql']['config'] attributes: +# +# - Where to Log - +# * log_destination = 'stderr' +# * log_directory = 'pg_log' +# * log_filename = 'postgresql-%a.log' +# (Default was: postgresql-%Y-%m-%d_%H%M%S.log) +# * logging_collector = true # on +# (Turned on to capture stderr logging and redirect into log files) +# (Default was: false # off) +# * log_rotation_age = 1d +# * log_rotation_size = 0 +# (Default was: 10MB) +# * log_truncate_on_rotation = true # on +# (Default was: false # off) + +####### +# Locale Configuration + +# See libraries/default.rb for the locale_date_order() method. +node.default['postgresql']['config']['datestyle'] = "iso, #{locale_date_order()}" + +# According to the locale(1) manpage, the locale settings are determined +# by environment variables according to the following precedence: +# LC_ALL > (LC_MESSAGES, LC_MONETARY, LC_NUMERIC, LC_TIME) > LANG. + +node.default['postgresql']['config']['lc_messages'] = + [ ENV['LC_ALL'], ENV['LC_MESSAGES'], ENV['LANG'] ].compact.first + +node.default['postgresql']['config']['lc_monetary'] = + [ ENV['LC_ALL'], ENV['LC_MONETARY'], ENV['LANG'] ].compact.first + +node.default['postgresql']['config']['lc_numeric'] = + [ ENV['LC_ALL'], ENV['LC_NUMERIC'], ENV['LANG'] ].compact.first + +node.default['postgresql']['config']['lc_time'] = + [ ENV['LC_ALL'], ENV['LC_TIME'], ENV['LANG'] ].compact.first + +node.default['postgresql']['config']['default_text_search_config'] = + case ENV['LANG'] + when /da_.*/ + 'pg_catalog.danish' + when /nl_.*/ + 'pg_catalog.dutch' + when /en_.*/ + 'pg_catalog.english' + when /fi_.*/ + 'pg_catalog.finnish' + when /fr_.*/ + 'pg_catalog.french' + when /de_.*/ + 'pg_catalog.german' + when /hu_.*/ + 'pg_catalog.hungarian' + when /it_.*/ + 'pg_catalog.italian' + when /no_.*/ + 'pg_catalog.norwegian' + when /pt_.*/ + 'pg_catalog.portuguese' + when /ro_.*/ + 'pg_catalog.romanian' + when /ru_.*/ + 'pg_catalog.russian' + when /es_.*/ + 'pg_catalog.spanish' + when /sv_.*/ + 'pg_catalog.swedish' + when /tr_.*/ + 'pg_catalog.turkish' + else + nil + end + +####### +# Timezone Configuration + +# Determine the name of the system's default timezone and specify node +# defaults for the postgresql.cof settings. If the timezone cannot be +# identified, do as initdb would do: leave it unspecified so PostgreSQL +# uses it's internal default of GMT. +tzdirpath = pg_TZDIR() # See libraries/default.rb +default_timezone = select_default_timezone(tzdirpath) # See libraries/default.rb +if !default_timezone.nil? + node.default['postgresql']['config']['log_timezone'] = default_timezone + node.default['postgresql']['config']['timezone'] = default_timezone +end + +####### +# - Where to Log - +node.default['postgresql']['config']['log_destination'] = 'stderr' +node.default['postgresql']['config']['log_directory'] = 'pg_log' +node.default['postgresql']['config']['log_filename'] = 'postgresql-%a.log' +node.default['postgresql']['config']['logging_collector'] = true # on +node.default['postgresql']['config']['log_rotation_age'] = '1d' +node.default['postgresql']['config']['log_rotation_size'] = 0 +node.default['postgresql']['config']['log_truncate_on_rotation'] = true # on diff --git a/cookbooks/postgresql/recipes/config_pgtune.rb b/cookbooks/postgresql/recipes/config_pgtune.rb new file mode 100644 index 0000000..361bae7 --- /dev/null +++ b/cookbooks/postgresql/recipes/config_pgtune.rb @@ -0,0 +1,284 @@ +# +# Cookbook Name:: postgresql +# Recipe:: config_pgtune +# Author:: David Crane () +# +# 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. +# + +####### +# Load the binaryround(value) method from libraries/default.rb +::Chef::Recipe.send(:include, Opscode::PostgresqlHelpers) + +####### +# This recipe is based on Greg Smith's pgtune script (the Feb 1, 2012 +# version at https://github.com/gregs1104/pgtune). Introduction: pgtune +# takes the wimpy default postgresql.conf and expands the database +# server to be as powerful as the hardware it's being deployed on. +# +# The default postgresql.conf aims at a system with approximately 128MB +# of RAM. This recipe recommends a baseline configuration in the right +# general range for a dedicated Postgresql system. +# +# This recipe takes three optional parameters that may be passed in as +# node['postgresql']['config_pgtune'] attributes: +# * db_type -- Specifies database type as one of: dw, oltp, +# web, mixed, desktop. If not specified, the default is mixed. +# * max_connections -- Specifies number of maximum connections +# expected. If not specified, it depends on database type. +# * total_memory -- Specifies total system memory. If not specified, +# it will be detected from the Ohai automatic attributes. +# +# Using those inputs, this recipe will compute and set the following +# node.default['postgresql']['config'] attributes: +# * max_connections +# * shared_buffers +# * effective_cache_size +# * work_mem +# * maintenance_work_mem +# * checkpoint_segments +# * checkpoint_completion_target +# * wal_buffers +# * default_statistics_target +# +# This recipe deviates from the original pgtune script for 2 settings: +# shared_buffers is capped for large memory systems (which Greg +# mentioned in a TODO.rst) and wal_buffers will auto-tune starting with +# 9.1 (which is a feature that Greg built into Postgresql). + +####### +# These are the workload characteristics of the five database types +# that can be specified as node['postgresql']['config_pgtune']['db_type']: +# +# dw -- Data Warehouse +# * Typically I/O- or RAM-bound +# * Large bulk loads of data +# * Large complex reporting queries +# * Also called "Decision Support" or "Business Intelligence" +# +# oltp -- Online Transaction Processing +# * Typically CPU- or I/O-bound +# * DB slightly larger than RAM to 1TB +# * 20-40% small data write queries +# * Some long transactions and complex read queries +# +# web -- Web Application +# * Typically CPU-bound +# * DB much smaller than RAM +# * 90% or more simple queries +# +# mixed -- Mixed DW and OLTP characteristics +# * A wide mixture of queries +# +# desktop -- Not a dedicated database +# * A general workstation, perhaps for a developer + +# Parse out db_type option, or use default. +db_type = 'mixed' + +if (node['postgresql'].attribute?('config_pgtune') && node['postgresql']['config_pgtune'].attribute?('db_type')) + db_type = node['postgresql']['config_pgtune']['db_type'] + if (!(["dw","oltp","web","mixed","desktop"].include?(db_type))) + Chef::Log.fatal([ + "Bad value (#{db_type})", + "for node['postgresql']['config_pgtune']['db_type'] attribute.", + "Valid values are one of dw, oltp, web, mixed, desktop." + ].join(' ')) + raise + end +end + +# Parse out max_connections option, or use a value based on db_type. +con = +{ "web" => 200, + "oltp" => 300, + "dw" => 20, + "mixed" => 80, + "desktop" => 5 +}.fetch(db_type) + +if (node['postgresql'].attribute?('config_pgtune') && node['postgresql']['config_pgtune'].attribute?('max_connections')) + max_connections = node['postgresql']['config_pgtune']['max_connections'].to_i + if max_connections <= 0 + Chef::Log.fatal([ + "Bad value (#{max_connections})", + "for node['postgresql']['config_pgtune']['max_connections'] attribute.", + "Valid values are non-zero integers only." + ].join(' ')) + raise + end + con = max_connections +end + +# Parse out total_memory option, or use value detected by Ohai. +total_memory = node['memory']['total'] + +# Override max_connections with a node attribute if DevOps desires. +# For example, on a system *not* dedicated to Postgresql. +if (node['postgresql'].attribute?('config_pgtune') && node['postgresql']['config_pgtune'].attribute?('total_memory')) + total_memory = node['postgresql']['config_pgtune']['total_memory'] + if (total_memory.match(/\A[1-9]\d*kB\Z/) == nil) + Chef::Application.fatal!([ + "Bad value (#{total_memory})", + "for node['postgresql']['config_pgtune']['total_memory'] attribute.", + "Valid values are non-zero integers followed by kB (e.g., 49416564kB)." + ].join(' ')) + end +end + +# Ohai reports node[:memory][:total] in kB, as in "921756kB" +mem = total_memory.split("kB")[0].to_i / 1024 # in MB + +####### +# RAM-related settings computed as in Greg Smith's pgtune script. +# Remember that con and mem were either chosen above based on the +# db_type or the actual total memory, or were passed in attributes. + +# (1) max_connections +# Sets the maximum number of concurrent connections. +node.default['postgresql']['config']['max_connections'] = con + +# The calculations for the next four settings would not be optimal +# for low memory systems. In that case, the calculation is skipped, +# leaving the built-in Postgresql settings, which are actually +# intended for those low memory systems. +if (mem >= 256) + + # (2) shared_buffers + # Sets the number of shared memory buffers used by the server. + shared_buffers = + { "web" => mem/4, + "oltp" => mem/4, + "dw" => mem/4, + "mixed" => mem/4, + "desktop" => mem/16 + }.fetch(db_type) + + # Robert Haas has advised to cap the size of shared_buffers based on + # the memory architecture: 2GB on 32-bit and 8GB on 64-bit machines. + # http://rhaas.blogspot.com/2012/03/tuning-sharedbuffers-and-walbuffers.html + case node['kernel']['machine'] + when "i386" # 32-bit machines + if shared_buffers > 2*1024 + shared_buffers = 2*1024 + end + when "x86_64" # 64-bit machines + if shared_buffers > 8*1024 + shared_buffers = 8*1024 + end + end + + node.default['postgresql']['config']['shared_buffers'] = binaryround(shared_buffers*1024*1024) + + # (3) effective_cache_size + # Sets the planner's assumption about the size of the disk cache. + # That is, the portion of the kernel's disk cache that will be + # used for PostgreSQL data files. + effective_cache_size = + { "web" => mem * 3 / 4, + "oltp" => mem * 3 / 4, + "dw" => mem * 3 / 4, + "mixed" => mem * 3 / 4, + "desktop" => mem / 4 + }.fetch(db_type) + + node.default['postgresql']['config']['effective_cache_size'] = binaryround(effective_cache_size*1024*1024) + + # (4) work_mem + # Sets the maximum memory to be used for query workspaces. + mem_con_v = (mem.to_f / con).ceil + + work_mem = + { "web" => mem_con_v, + "oltp" => mem_con_v, + "dw" => mem_con_v / 2, + "mixed" => mem_con_v / 2, + "desktop" => mem_con_v / 6 + }.fetch(db_type) + + node.default['postgresql']['config']['work_mem'] = binaryround(work_mem*1024*1024) + + # (5) maintenance_work_mem + # Sets the maximum memory to be used for maintenance operations. + # This includes operations such as VACUUM and CREATE INDEX. + maintenance_work_mem = + { "web" => mem / 16, + "oltp" => mem / 16, + "dw" => mem / 8, + "mixed" => mem / 16, + "desktop" => mem / 16 + }.fetch(db_type) + + # Cap maintenence RAM at 1GB on servers with lots of memory + if (maintenance_work_mem > 1*1024) + maintenance_work_mem = 1*1024 + end + + node.default['postgresql']['config']['maintenance_work_mem'] = binaryround(maintenance_work_mem*1024*1024) + +end + +####### +# Checkpoint-related parameters that affect transaction rate and +# maximum tolerable recovery playback time. + +# (6) checkpoint_segments +# Sets the maximum distance in log segments between automatic WAL checkpoints. +checkpoint_segments = +{ "web" => 8, + "oltp" => 16, + "dw" => 64, + "mixed" => 16, + "desktop" => 3 +}.fetch(db_type) + +node.default['postgresql']['config']['checkpoint_segments'] = checkpoint_segments + +# (7) checkpoint_completion_target +# Time spent flushing dirty buffers during checkpoint, as fraction +# of checkpoint interval. +checkpoint_completion_target = +{ "web" => "0.7", + "oltp" => "0.9", + "dw" => "0.9", + "mixed" => "0.9", + "desktop" => "0.5" +}.fetch(db_type) + +node.default['postgresql']['config']['checkpoint_completion_target'] = checkpoint_completion_target + +# (8) wal_buffers +# Sets the number of disk-page buffers in shared memory for WAL. +# Starting with 9.1, wal_buffers will auto-tune if set to the -1 default. +# For 8.X and 9.0, it needed to be specified, which pgtune did as follows. +if node['postgresql']['version'].to_f < 9.1 + wal_buffers = 512 * checkpoint_segments + # The pgtune seems to use 1kB units for wal_buffers + node.default['postgresql']['config']['wal_buffers'] = binaryround(wal_buffers*1024) +else + node.default['postgresql']['config']['wal_buffers'] = "-1" +end + +# (9) default_statistics_target +# Sets the default statistics target. This applies to table columns +# that have not had a column-specific target set via +# ALTER TABLE SET STATISTICS. +default_statistics_target = +{ "web" => 100, + "oltp" => 100, + "dw" => 500, + "mixed" => 100, + "desktop" => 100 +}.fetch(db_type) + +node.default['postgresql']['config']['default_statistics_target'] = default_statistics_target diff --git a/cookbooks/postgresql/recipes/contrib.rb b/cookbooks/postgresql/recipes/contrib.rb new file mode 100644 index 0000000..e4cae04 --- /dev/null +++ b/cookbooks/postgresql/recipes/contrib.rb @@ -0,0 +1,44 @@ +# +# Cookbook Name:: postgresql +# Recipe:: contrib +# +# 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. +# + +db_name = node['postgresql']['database_name'] + +# Install the PostgreSQL contrib package(s) from the distribution, +# as specified by the node attributes. +node['postgresql']['contrib']['packages'].each do |pg_pack| + + package pg_pack + +end + +include_recipe "postgresql::server" + +# Install PostgreSQL contrib extentions into the database, as specified by the +# node attribute node['postgresql']['database_name']. +if (node['postgresql']['contrib'].attribute?('extensions')) + node['postgresql']['contrib']['extensions'].each do |pg_ext| + bash "install-#{pg_ext}-extension" do + user 'postgres' + code <<-EOH + echo 'CREATE EXTENSION IF NOT EXISTS "#{pg_ext}";' | psql -d "#{db_name}" + EOH + action :run + ::Chef::Resource.send(:include, Opscode::PostgresqlHelpers) + not_if {extension_installed?(pg_ext)} + end + end +end diff --git a/cookbooks/postgresql/recipes/default.rb b/cookbooks/postgresql/recipes/default.rb new file mode 100644 index 0000000..ea68f9b --- /dev/null +++ b/cookbooks/postgresql/recipes/default.rb @@ -0,0 +1,18 @@ +# +# Cookbook Name:: postgresql +# Recipe:: default +# +# 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. +# + +include_recipe "postgresql::client" diff --git a/cookbooks/postgresql/recipes/ruby.rb b/cookbooks/postgresql/recipes/ruby.rb new file mode 100644 index 0000000..cf93356 --- /dev/null +++ b/cookbooks/postgresql/recipes/ruby.rb @@ -0,0 +1,117 @@ +# +# Cookbook Name:: postgresql +# Recipe:: ruby +# +# 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. +# + +# Load the pgdgrepo_rpm_info method from libraries/default.rb +::Chef::Recipe.send(:include, Opscode::PostgresqlHelpers) + +begin + require 'pg' +rescue LoadError + + if platform_family?('ubuntu', 'debian') + e = execute 'apt-get update' do + action :nothing + end + e.run_action(:run) unless ::File.exists?('/var/lib/apt/periodic/update-success-stamp') + end + + node.set['build-essential']['compile_time'] = true + include_recipe "build-essential" + include_recipe "postgresql::client" + + if node['postgresql']['enable_pgdg_yum'] + repo_rpm_url, repo_rpm_filename, repo_rpm_package = pgdgrepo_rpm_info + include_recipe "postgresql::yum_pgdg_postgresql" + resources("remote_file[#{Chef::Config[:file_cache_path]}/#{repo_rpm_filename}]").run_action(:create) + resources("package[#{repo_rpm_package}]").run_action(:install) + ENV['PATH'] = "/usr/pgsql-#{node['postgresql']['version']}/bin:#{ENV['PATH']}" + end + + if node['postgresql']['enable_pgdg_apt'] + include_recipe "postgresql::apt_pgdg_postgresql" + resources("file[remove deprecated Pitti PPA apt repository]").run_action(:delete) + resources("apt_repository[apt.postgresql.org]").run_action(:add) + end + + node['postgresql']['client']['packages'].each do |pg_pack| + resources("package[#{pg_pack}]").run_action(:install) + end + + begin + chef_gem "pg" + rescue Gem::Installer::ExtensionBuildError, Mixlib::ShellOut::ShellCommandFailed => e + # Are we an omnibus install? + raise if RbConfig.ruby.scan(%r{(chef|opscode)}).empty? + # Still here, must be omnibus. Lets make this thing install! + Chef::Log.warn 'Failed to properly build pg gem. Forcing properly linking and retrying (omnibus fix)' + gem_dir = e.message.scan(%r{will remain installed in ([^ ]+)}).flatten.first + raise unless gem_dir + gem_name = File.basename(gem_dir) + ext_dir = File.join(gem_dir, 'ext') + gem_exec = File.join(File.dirname(RbConfig.ruby), 'gem') + new_content = <<-EOS +require 'rbconfig' +%w( +configure_args +LIBRUBYARG_SHARED +LIBRUBYARG_STATIC +LIBRUBYARG +LDFLAGS +).each do |key| + RbConfig::CONFIG[key].gsub!(/-Wl[^ ]+( ?\\/[^ ]+)?/, '') + RbConfig::MAKEFILE_CONFIG[key].gsub!(/-Wl[^ ]+( ?\\/[^ ]+)?/, '') +end +RbConfig::CONFIG['RPATHFLAG'] = '' +RbConfig::MAKEFILE_CONFIG['RPATHFLAG'] = '' +EOS + new_content << File.read(extconf_path = File.join(ext_dir, 'extconf.rb')) + File.open(extconf_path, 'w') do |file| + file.write(new_content) + end + + lib_builder = execute 'generate pg gem Makefile' do + # [COOK-3490] pg gem install requires full path on RHEL + command "PATH=$PATH:/usr/pgsql-#{node['postgresql']['version']}/bin #{RbConfig.ruby} extconf.rb" + cwd ext_dir + action :nothing + end + lib_builder.run_action(:run) + + lib_maker = execute 'make pg gem lib' do + command 'make' + cwd ext_dir + action :nothing + end + lib_maker.run_action(:run) + + lib_installer = execute 'install pg gem lib' do + command 'make install' + cwd ext_dir + action :nothing + end + lib_installer.run_action(:run) + + spec_installer = execute 'install pg spec' do + command "#{gem_exec} spec ./cache/#{gem_name}.gem --ruby > ./specifications/#{gem_name}.gemspec" + cwd File.join(gem_dir, '..', '..') + action :nothing + end + spec_installer.run_action(:run) + + Chef::Log.warn 'Installation of pg gem successful!' + end +end diff --git a/cookbooks/postgresql/recipes/server.rb b/cookbooks/postgresql/recipes/server.rb new file mode 100644 index 0000000..2a9a1b5 --- /dev/null +++ b/cookbooks/postgresql/recipes/server.rb @@ -0,0 +1,89 @@ +# +# Cookbook Name:: postgresql +# Recipe:: server +# +# 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. +# + +::Chef::Recipe.send(:include, Opscode::OpenSSL::Password) + +include_recipe "postgresql::client" + +# randomly generate postgres password, unless using solo - see README +if Chef::Config[:solo] + missing_attrs = %w{ + postgres + }.select do |attr| + node['postgresql']['password'][attr].nil? + end.map { |attr| "node['postgresql']['password']['#{attr}']" } + + if !missing_attrs.empty? + Chef::Log.fatal([ + "You must set #{missing_attrs.join(', ')} in chef-solo mode.", + "For more information, see https://github.com/opscode-cookbooks/postgresql#chef-solo-note" + ].join(' ')) + raise + end +else + # TODO: The "secure_password" is randomly generated plain text, so it + # should be converted to a PostgreSQL specific "encrypted password" if + # it should actually install a password (as opposed to disable password + # login for user 'postgres'). However, a random password wouldn't be + # useful if it weren't saved as clear text in Chef Server for later + # retrieval. + unless node.key?('postgresql') && node['postgresql'].key?('password') && node['postgresql']['password'].key?('postgres') + node.set_unless['postgresql']['password']['postgres'] = secure_password + node.save + end +end + +# Include the right "family" recipe for installing the server +# since they do things slightly differently. +case node['platform_family'] +when "rhel", "fedora", "suse" + include_recipe "postgresql::server_redhat" +when "debian" + include_recipe "postgresql::server_debian" +end + +# Versions prior to 9.2 do not have a config file option to set the SSL +# key and cert path, and instead expect them to be in a specific location. +if node['postgresql']['version'].to_f < 9.2 && node['postgresql']['config'].attribute?('ssl_cert_file') + link ::File.join(node['postgresql']['config']['data_directory'], 'server.crt') do + to node['postgresql']['config']['ssl_cert_file'] + end +end + +if node['postgresql']['version'].to_f < 9.2 && node['postgresql']['config'].attribute?('ssl_key_file') + link ::File.join(node['postgresql']['config']['data_directory'], 'server.key') do + to node['postgresql']['config']['ssl_key_file'] + end +end + +# NOTE: Consider two facts before modifying "assign-postgres-password": +# (1) Passing the "ALTER ROLE ..." through the psql command only works +# if passwordless authorization was configured for local connections. +# For example, if pg_hba.conf has a "local all postgres ident" rule. +# (2) It is probably fruitless to optimize this with a not_if to avoid +# setting the same password. This chef recipe doesn't have access to +# the plain text password, and testing the encrypted (md5 digest) +# version is not straight-forward. +bash "assign-postgres-password" do + user 'postgres' + code <<-EOH + echo "ALTER ROLE postgres ENCRYPTED PASSWORD '#{node['postgresql']['password']['postgres']}';" | psql -p #{node['postgresql']['config']['port']} + EOH + action :run + not_if "ls #{node['postgresql']['config']['data_directory']}/recovery.conf" + only_if { node['postgresql']['assign_postgres_password'] } +end diff --git a/cookbooks/postgresql/recipes/server_conf.rb b/cookbooks/postgresql/recipes/server_conf.rb new file mode 100644 index 0000000..68a6b1f --- /dev/null +++ b/cookbooks/postgresql/recipes/server_conf.rb @@ -0,0 +1,34 @@ +# +# Cookbook Name:: postgresql +# Recipe:: server +# +# 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. +# + +change_notify = node['postgresql']['server']['config_change_notify'] + +template "#{node['postgresql']['dir']}/postgresql.conf" do + source "postgresql.conf.erb" + owner "postgres" + group "postgres" + mode 0600 + notifies change_notify, 'service[postgresql]', :immediately +end + +template "#{node['postgresql']['dir']}/pg_hba.conf" do + source "pg_hba.conf.erb" + owner "postgres" + group "postgres" + mode 00600 + notifies change_notify, 'service[postgresql]', :immediately +end diff --git a/cookbooks/postgresql/recipes/server_debian.rb b/cookbooks/postgresql/recipes/server_debian.rb new file mode 100644 index 0000000..65da795 --- /dev/null +++ b/cookbooks/postgresql/recipes/server_debian.rb @@ -0,0 +1,38 @@ +# +# Cookbook Name:: postgresql +# Recipe:: server +# +# 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. +# + +include_recipe "postgresql::client" + +node['postgresql']['server']['packages'].each do |pg_pack| + + package pg_pack + +end + +include_recipe "postgresql::server_conf" + +service "postgresql" do + service_name node['postgresql']['server']['service_name'] + supports :restart => true, :status => true, :reload => true + action [:enable, :start] +end + +execute 'Set locale and Create cluster' do + command 'export LC_ALL=C; /usr/bin/pg_createcluster --start ' + node['postgresql']['version'] + ' main' + action :run + not_if { ::File.directory?('/etc/postgresql/' + node['postgresql']['version'] + '/main') } +end diff --git a/cookbooks/postgresql/recipes/server_redhat.rb b/cookbooks/postgresql/recipes/server_redhat.rb new file mode 100644 index 0000000..0795b6f --- /dev/null +++ b/cookbooks/postgresql/recipes/server_redhat.rb @@ -0,0 +1,100 @@ +# +# Cookbook Name:: postgresql +# Recipe:: server +# +# 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. +# + +include_recipe "postgresql::client" + +svc_name = node['postgresql']['server']['service_name'] +dir = node['postgresql']['dir'] +initdb_locale = node['postgresql']['initdb_locale'] + +# Create a group and user like the package will. +# Otherwise the templates fail. + +group "postgres" do + gid 26 +end + +user "postgres" do + shell "/bin/bash" + comment "PostgreSQL Server" + home "/var/lib/pgsql" + gid "postgres" + system true + uid 26 + supports :manage_home => false +end + +directory dir do + owner "postgres" + group "postgres" + recursive true + action :create +end + +node['postgresql']['server']['packages'].each do |pg_pack| + + package pg_pack + +end + +# Starting with Fedora 16, the pgsql sysconfig files are no longer used. +# The systemd unit file does not support 'initdb' or 'upgrade' actions. +# Use the postgresql-setup script instead. + +unless platform_family?("fedora") and node['platform_version'].to_i >= 16 + + directory "/etc/sysconfig/pgsql" do + mode "0644" + recursive true + action :create + end + + template "/etc/sysconfig/pgsql/#{svc_name}" do + source "pgsql.sysconfig.erb" + mode "0644" + notifies :restart, "service[postgresql]", :delayed + end + +end + +if platform_family?("fedora") and node['platform_version'].to_i >= 16 + + execute "postgresql-setup initdb #{svc_name}" do + not_if { ::FileTest.exist?(File.join(dir, "PG_VERSION")) } + end + +elsif platform?("redhat") and node['platform_version'].to_i >= 7 + + execute "postgresql#{node['postgresql']['version'].split('.').join}-setup initdb #{svc_name}" do + not_if { ::FileTest.exist?(File.join(dir, "PG_VERSION")) } + end + +else !platform_family?("suse") + + execute "/sbin/service #{svc_name} initdb #{initdb_locale}" do + not_if { ::FileTest.exist?(File.join(dir, "PG_VERSION")) } + end + +end + +include_recipe "postgresql::server_conf" + +service "postgresql" do + service_name svc_name + supports :restart => true, :status => true, :reload => true + action [:enable, :start] +end diff --git a/cookbooks/postgresql/recipes/yum_pgdg_postgresql.rb b/cookbooks/postgresql/recipes/yum_pgdg_postgresql.rb new file mode 100644 index 0000000..fcbc54c --- /dev/null +++ b/cookbooks/postgresql/recipes/yum_pgdg_postgresql.rb @@ -0,0 +1,45 @@ +# +# Cookbook Name:: postgresql +# Recipe::yum_pgdg_postgresql +# +# 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. +# + +####### +# Load the pgdgrepo_rpm_info method from libraries/default.rb +::Chef::Recipe.send(:include, Opscode::PostgresqlHelpers) + +###################################### +# Install the "PostgreSQL RPM Building Project - Yum Repository" through +# the repo_rpm_url determined with pgdgrepo_rpm_info method from +# libraries/default.rb. The /etc/yum.repos.d/pgdg-*.repo +# will provide postgresql9X packages, but you may need to exclude +# postgresql packages from the repository of the distro in order to use +# PGDG repository properly. Conflicts will arise if postgresql9X does +# appear in your distro's repo and you want a more recent patch level. + +repo_rpm_url, repo_rpm_filename, repo_rpm_package = pgdgrepo_rpm_info + +# Download the PGDG repository RPM as a local file +remote_file "#{Chef::Config[:file_cache_path]}/#{repo_rpm_filename}" do + source repo_rpm_url + mode "0644" +end + +# Install the PGDG repository RPM from the local file +# E.g., /etc/yum.repos.d/pgdg-91-centos.repo +package repo_rpm_package do + provider Chef::Provider::Package::Rpm + source "#{Chef::Config[:file_cache_path]}/#{repo_rpm_filename}" + action :install +end diff --git a/cookbooks/postgresql/templates/default/pg_hba.conf.erb b/cookbooks/postgresql/templates/default/pg_hba.conf.erb new file mode 100644 index 0000000..55d757d --- /dev/null +++ b/cookbooks/postgresql/templates/default/pg_hba.conf.erb @@ -0,0 +1,35 @@ +# This file was automatically generated and dropped off by Chef! + +# PostgreSQL Client Authentication Configuration File +# =================================================== +# +# Refer to the "Client Authentication" section in the PostgreSQL +# documentation for a complete description of this file. + +<% if node['postgresql']['version'].to_f < 9.1 -%> +# TYPE DATABASE USER CIDR-ADDRESS METHOD +<% elsif node['postgresql']['version'].to_f >= 9.1 -%> +# TYPE DATABASE USER ADDRESS METHOD +<% end -%> + +########### +# Other authentication configurations taken from chef node defaults: +########### +<% node['postgresql']['pg_hba'].each do |auth| -%> + +<% if auth[:comment] %> +<%= auth[:comment] %> +<% end %> +<% if auth[:addr] %> +<%= auth[:type].ljust(7) %> <%= auth[:db].ljust(15) %> <%= auth[:user].ljust(15) %> <%= auth[:addr].ljust(23) %> <%= auth[:method] %> +<% else %> +<%= auth[:type].ljust(7) %> <%= auth[:db].ljust(15) %> <%= auth[:user].ljust(15) %> <%= auth[:method] %> +<% end %> +<% end %> + +# "local" is for Unix domain socket connections only +<% if node['postgresql']['version'].to_f < 9.1 -%> +local all all ident +<% elsif node['postgresql']['version'].to_f >= 9.1 -%> +local all all peer +<% end -%> diff --git a/cookbooks/postgresql/templates/default/pgsql.sysconfig.erb b/cookbooks/postgresql/templates/default/pgsql.sysconfig.erb new file mode 100644 index 0000000..5421211 --- /dev/null +++ b/cookbooks/postgresql/templates/default/pgsql.sysconfig.erb @@ -0,0 +1,4 @@ +PGDATA=<%= node['postgresql']['dir'] %> +<% if node['postgresql']['config'].attribute?("port") -%> +PGPORT=<%= node['postgresql']['config']['port'] %> +<% end -%> diff --git a/cookbooks/postgresql/templates/default/postgresql.conf.erb b/cookbooks/postgresql/templates/default/postgresql.conf.erb new file mode 100644 index 0000000..26d77f9 --- /dev/null +++ b/cookbooks/postgresql/templates/default/postgresql.conf.erb @@ -0,0 +1,21 @@ +# PostgreSQL configuration file +# This file was automatically generated and dropped off by chef! +# Please refer to the PostgreSQL documentation for details on +# configuration settings. + +<% node['postgresql']['config'].sort.each do |key, value| %> +<% next if value.nil? -%> +<% next if node['postgresql']['version'].to_f < 9.2 && /ssl_.*._file/.match(key) -%> +<%= key %> = <%= + case value + when String + "'#{value}'" + when TrueClass + 'on' + when FalseClass + 'off' + else + value + end +%> +<% end %> diff --git a/cookbooks/rbac/README.md b/cookbooks/rbac/README.md new file mode 100644 index 0000000..e7f3f74 --- /dev/null +++ b/cookbooks/rbac/README.md @@ -0,0 +1,82 @@ +Role based access control +========================= + +Solaris and Illumos provide sophisticated role-based access control for +delegating authorizations within the system. Using RBAC, users can be +given permissions to manage and update services without sudo. + +This cookbook provides chef with LWRPs to manage RBAC and grant permissions. + +At this time this cookbook ONLY manages SMF-related permissions (ie, ability +of non-priviliged users to start/stop SMF services), but in the future it may +be enhanced to support arbitrary Solaris permissions. + +## Installation + +In order to add the RBAC LWRPs to a chef run, add the following recipe +to the run_list: + + rbac::default + +This will do no work, but will load the providers. + +## LWRPs + +### rbac + +Defines a set of authorizations that can be applied to SMF services and +authorized to users, without actually applying them to users. + +Actions: + * create (default) + +Attributes: + * name + +Example: + +```ruby +rbac "nginx" do + action :create +end +``` + +This will update the authorizations file at `/etc/security/auth_attr` +with the following lines: + +``` +solaris.smf.manage.nginx:::Manage nginx Service States:: +solaris.smf.value.nginx:::Change value of nginx Service:: +``` + +Users who are given these authorizations can change properties of the +service as well as change its state (i.e. `svcadm disable|enable|restart|clear service` + +### rbac_auth + +Adds the rbac definition created by `auth` to the user `name`. + +Actions: + * add (default) + +Attributes: + * name - for descriptive purposes and to ensure that each LWRP call is uniquely + identified in the chef run + * user + * auth + +Example: + +```ruby +rbac_auth "add nginx management permissions to my_user" do + user "my_user" + auth "nginx" +end +``` + +This adds both manage and value auths to user `my_user`. + +## TODO + +* separate manage auth from value auth +* ability to delete all rbac attributes diff --git a/cookbooks/rbac/libraries/rbac.rb b/cookbooks/rbac/libraries/rbac.rb new file mode 100644 index 0000000..27b29a8 --- /dev/null +++ b/cookbooks/rbac/libraries/rbac.rb @@ -0,0 +1,15 @@ +# This module is used to retain state during the course of a chef +# run. The LWRPs in the cookbook modify a global hash in this module, +# and at the end of the chef run if user authorizations change they +# are written out into the system. +# +module RBAC + def self.authorizations + @authorizations ||= {} + end + + def self.add_authorization(username, auth) + authorizations[username] ||= [] + authorizations[username] << auth + end +end diff --git a/cookbooks/rbac/metadata.json b/cookbooks/rbac/metadata.json new file mode 100644 index 0000000..08c1d91 --- /dev/null +++ b/cookbooks/rbac/metadata.json @@ -0,0 +1,42 @@ +{ + "name": "rbac", + "description": "Allows delegation of service management to users with Solaris Role Based Access Control (RBAC)", + "long_description": "Role based access control\n=========================\n\nSolaris and Illumos provide sophisticated role-based access control for\ndelegating authorizations within the system. Using RBAC, users can be\ngiven permissions to manage and update services without sudo.\n\nThis cookbook provides chef with LWRPs to manage RBAC and grant permissions.\n\nAt this time this cookbook ONLY manages SMF-related permissions (ie, ability\nof non-priviliged users to start/stop SMF services), but in the future it may\nbe enhanced to support arbitrary Solaris permissions.\n\n## Installation\n\nIn order to add the RBAC LWRPs to a chef run, add the following recipe \nto the run_list:\n\n rbac::default\n\nThis will do no work, but will load the providers.\n\n## LWRPs\n\n### rbac\n\nDefines a set of authorizations that can be applied to SMF services and\nauthorized to users, without actually applying them to users.\n\nActions:\n * create (default)\n\nAttributes:\n * name\n\nExample:\n\n```ruby\nrbac \"nginx\" do\n action :create\nend\n```\n\nThis will update the authorizations file at `/etc/security/auth_attr`\nwith the following lines:\n\n```\nsolaris.smf.manage.nginx:::Manage nginx Service States::\nsolaris.smf.value.nginx:::Change value of nginx Service::\n```\n\nUsers who are given these authorizations can change properties of the\nservice as well as change its state (i.e. `svcadm disable|enable|restart|clear service`\n\n### rbac_auth\n\nAdds the rbac definition created by `auth` to the user `name`.\n\nActions:\n * add (default)\n\nAttributes:\n * name - for descriptive purposes and to ensure that each LWRP call is uniquely\n identified in the chef run\n * user\n * auth\n\nExample:\n\n```ruby\nrbac_auth \"add nginx management permissions to my_user\" do\n user \"my_user\"\n auth \"nginx\"\nend\n```\n\nThis adds both manage and value auths to user `my_user`.\n\n## TODO\n\n* separate manage auth from value auth\n* ability to delete all rbac attributes\n", + "maintainer": "Eric Saxby", + "maintainer_email": "sax@livinginthepast.org", + "license": "MIT", + "platforms": { + "solaris2": ">= 0.0.0", + "smartos": ">= 0.0.0" + }, + "dependencies": { + + }, + "recommendations": { + + }, + "suggestions": { + + }, + "conflicting": { + + }, + "providing": { + + }, + "replacing": { + + }, + "attributes": { + + }, + "groupings": { + + }, + "recipes": { + + }, + "version": "1.0.3", + "source_url": "", + "issues_url": "" +} diff --git a/cookbooks/rbac/metadata.rb b/cookbooks/rbac/metadata.rb new file mode 100644 index 0000000..91c58d2 --- /dev/null +++ b/cookbooks/rbac/metadata.rb @@ -0,0 +1,10 @@ +name 'rbac' +maintainer 'Eric Saxby' +maintainer_email 'sax@livinginthepast.org' +license 'MIT' +description 'Allows delegation of service management to users with Solaris Role Based Access Control (RBAC)' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '1.0.3' + +supports 'solaris2' +supports 'smartos' diff --git a/cookbooks/rbac/providers/auth.rb b/cookbooks/rbac/providers/auth.rb new file mode 100644 index 0000000..d922489 --- /dev/null +++ b/cookbooks/rbac/providers/auth.rb @@ -0,0 +1,20 @@ +def load_current_resource + @current_resource = Chef::Resource::RbacAuth.new(new_resource.name) + @new_resource.definition = run_context.resource_collection.find(:rbac => @new_resource.auth) + begin + @new_resource.user_definition = run_context.resource_collection.find(:rbac_user => @new_resource.user) + rescue Chef::Exceptions::ResourceNotFound + end +end + +action :add do + unless new_resource.user_definition + new_resource.user_definition = rbac_user new_resource.user + end + + new_resource.add_auth new_resource.user, new_resource.auth + + new_resource.updated_by_last_action(true) + + new_resource.notifies(:apply, new_resource.user_definition, :delayed) +end diff --git a/cookbooks/rbac/providers/default.rb b/cookbooks/rbac/providers/default.rb new file mode 100644 index 0000000..368de9e --- /dev/null +++ b/cookbooks/rbac/providers/default.rb @@ -0,0 +1,27 @@ + +def load_current_resource + @current_resource = Chef::Resource::Rbac.new(@new_resource.name) +end + +action :create do + definition = new_resource.name + + new_resource.updated_by_last_action(false) + + manage_auth = "solaris.smf.manage.#{definition}:::Manage #{definition} Service States::" + manage = execute "add RBAC #{definition} management to /etc/security/auth_attr" do + command "echo \"#{manage_auth}\" >> /etc/security/auth_attr" + not_if "grep \"#{manage_auth}\" /etc/security/auth_attr" + end + + # This additional permission allows the user to call svccfg -s service setprop + # to set dynamic properties without having to re-run chef. This may be + # moved into a separate LWRP in the future. + value_auth = "solaris.smf.value.#{definition}:::Change value of #{definition} Service::" + value = execute "add RBAC #{definition} value to /etc/security/auth_attr" do + command "echo \"#{value_auth}\" >> /etc/security/auth_attr" + not_if "grep \"#{value_auth}\" /etc/security/auth_attr" + end + + new_resource.updated_by_last_action(manage.updated_by_last_action? || value.updated_by_last_action?) +end diff --git a/cookbooks/rbac/providers/user.rb b/cookbooks/rbac/providers/user.rb new file mode 100644 index 0000000..cefb660 --- /dev/null +++ b/cookbooks/rbac/providers/user.rb @@ -0,0 +1,22 @@ +# The rbac_user LWRP is an internal set of classes used by other LWRPs to +# delay writing of user attributes until the end of the chef run. It should not be +# manually run. + +def load_current_resource + @current_resource = Chef::Resource::Rbac::User.new(@new_resource.user) +end + +action :apply do + username = new_resource.user + + auths = RBAC.authorizations[username] + permissions = auths.inject([]) do |auth, name| + auth + ["solaris.smf.manage.#{name}", "solaris.smf.value.#{name}"] + end.sort.uniq.join(',') + + execute "Apply rbac authorizations to #{username}" do + command "usermod -A #{permissions} #{username}" + action :nothing + not_if "grep #{username} /etc/user_attr | grep 'auths=#{permissions}'" + end.run_action(:run) +end diff --git a/cookbooks/rbac/recipes/default.rb b/cookbooks/rbac/recipes/default.rb new file mode 100644 index 0000000..3cab4b3 --- /dev/null +++ b/cookbooks/rbac/recipes/default.rb @@ -0,0 +1,6 @@ +# +# Cookbook Name:: rbac +# Recipe:: default +# +# Copyright 2012, ModCloth, Inc. +# diff --git a/cookbooks/rbac/resources/auth.rb b/cookbooks/rbac/resources/auth.rb new file mode 100644 index 0000000..4c8bedd --- /dev/null +++ b/cookbooks/rbac/resources/auth.rb @@ -0,0 +1,14 @@ + +default_action :add + +actions :add + +attribute :user, :kind_of => String, :required => true +attribute :auth, :kind_of => String, :required => true + +# private, internal attributes +attr_accessor :definition, :user_definition + +def add_auth(user, auth) + RBAC.add_authorization(user, auth) +end diff --git a/cookbooks/rbac/resources/default.rb b/cookbooks/rbac/resources/default.rb new file mode 100644 index 0000000..8a56e0a --- /dev/null +++ b/cookbooks/rbac/resources/default.rb @@ -0,0 +1,6 @@ + +default_action :create + +actions :create + +attribute :name, :kind_of => String, :name_attribute => true, :required => true diff --git a/cookbooks/rbac/resources/user.rb b/cookbooks/rbac/resources/user.rb new file mode 100644 index 0000000..25fb93c --- /dev/null +++ b/cookbooks/rbac/resources/user.rb @@ -0,0 +1,6 @@ + +default_action :nothing + +actions :apply + +attribute :user, :kind_of => String, :name_attribute => true, :required => true diff --git a/cookbooks/redis/.gitignore b/cookbooks/redis/.gitignore new file mode 100644 index 0000000..bf6420b --- /dev/null +++ b/cookbooks/redis/.gitignore @@ -0,0 +1,9 @@ +*.tgz +*.tar.gz +vendor/bundle +.bundle +.DS_Store +build/* +tmp/ +*.lock +.kitchen/* diff --git a/cookbooks/redis/.kitchen.yml b/cookbooks/redis/.kitchen.yml new file mode 100644 index 0000000..c69ece7 --- /dev/null +++ b/cookbooks/redis/.kitchen.yml @@ -0,0 +1,20 @@ +driver: + require_chef_omnibus: <%= ENV.fetch("CHEF_VERSION", "latest") %> + ssh_key: <%= File.expand_path("./test/support/keys/vagrant") %> + name: vagrant + +provisioner: + name: chef_solo + +platforms: + - name: debian-6.0.8 + - name: debian-7.2.0 + - name: ubuntu-12.04 + - name: ubuntu-14.04 + +suites: + - name: default + run_list: + - recipe[redis::server] + - recipe[redis::client] + # - recipe[minitest-handler] diff --git a/cookbooks/redis/.rubocop.yml b/cookbooks/redis/.rubocop.yml new file mode 100644 index 0000000..9e26e49 --- /dev/null +++ b/cookbooks/redis/.rubocop.yml @@ -0,0 +1,18 @@ +LineLength: + Max: 80 + Exclude: + - "**/attributes/*.rb" + - "**/metadata.rb" + +StringLiterals: + EnforcedStyle: double_quotes + +PercentLiteralDelimiters: + PreferredDelimiters: + "%w": "[]" # Arrays use brackets + +SingleSpaceBeforeFirstArg: + Enabled: false # too strict about metadata and certain formatting + +inherit_from: test/support/rubocop/enabled.yml +inherit_from: test/support/rubocop/disabled.yml diff --git a/cookbooks/redis/.ruby-gemset b/cookbooks/redis/.ruby-gemset new file mode 100644 index 0000000..515fa5d --- /dev/null +++ b/cookbooks/redis/.ruby-gemset @@ -0,0 +1 @@ +chef-redis diff --git a/cookbooks/redis/.ruby-version b/cookbooks/redis/.ruby-version new file mode 100644 index 0000000..cd57a8b --- /dev/null +++ b/cookbooks/redis/.ruby-version @@ -0,0 +1 @@ +2.1.5 diff --git a/cookbooks/redis/.travis.yml b/cookbooks/redis/.travis.yml new file mode 100644 index 0000000..852f917 --- /dev/null +++ b/cookbooks/redis/.travis.yml @@ -0,0 +1,19 @@ +language: ruby +bundler_args: --jobs 3 --without integration +rvm: +- 1.9.3 +- 2.0.0 +- 2.1.5 +before_script: +- "gem install bundler-audit --no-rdoc --no-ri && bundle-audit update" +script: +- bundle-audit +- bundle exec rake rubocop +- bundle exec rake foodcritic +- bundle exec rake chefspec +- bundle exec rake kitchen:all +- bundle exec license_finder --quiet +env: + matrix: + - CHEF_VERSION: "10.30" + - CHEF_VERSION: "11.16" diff --git a/cookbooks/redis/Berksfile b/cookbooks/redis/Berksfile new file mode 100644 index 0000000..06f8e4f --- /dev/null +++ b/cookbooks/redis/Berksfile @@ -0,0 +1,7 @@ +source "http://api.berkshelf.com" + +metadata + +group :integration do + cookbook "minitest-handler" +end diff --git a/cookbooks/redis/Gemfile b/cookbooks/redis/Gemfile new file mode 100644 index 0000000..88b54de --- /dev/null +++ b/cookbooks/redis/Gemfile @@ -0,0 +1,20 @@ +source "https://rubygems.org" + +chef_version = ENV.fetch("CHEF_VERSION", "11.16") + +gem "chef", "~> #{chef_version}" +gem "chefspec", "~> 4.1.1" if chef_version =~ /^11/ + +gem "berkshelf", "~> 3.2.1" +gem "foodcritic", "~> 4.0.0" +gem "license_finder", "~> 1.2.0" +gem "rake" +gem "rubocop", "~> 0.27.1" +gem "serverspec", "~> 2.3.1" + +group :integration do + gem "busser-serverspec", "~> 0.5.3" + gem "guard-rspec", "~> 4.3.1" + gem "kitchen-vagrant", "~> 0.15.0" + gem "test-kitchen", "~> 1.2.1" +end diff --git a/cookbooks/redis/Guardfile b/cookbooks/redis/Guardfile new file mode 100644 index 0000000..e40e264 --- /dev/null +++ b/cookbooks/redis/Guardfile @@ -0,0 +1,5 @@ +guard :rspec, cmd: "rspec --color", all_on_start: false do + watch(/^spec\/(.+)_spec\.rb$/) + watch(/^recipes\/(.+)\.rb$/) { |m| "spec/#{m[1]}_spec.rb" } + watch("spec/spec_helper.rb") { "spec" } +end diff --git a/cookbooks/redis/LICENSE.txt b/cookbooks/redis/LICENSE.txt new file mode 100644 index 0000000..20dac68 --- /dev/null +++ b/cookbooks/redis/LICENSE.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) +Copyright © 2012-2014 Phil Cohen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Softwareâ€), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS ISâ€, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/cookbooks/redis/README.md b/cookbooks/redis/README.md new file mode 100644 index 0000000..30954ef --- /dev/null +++ b/cookbooks/redis/README.md @@ -0,0 +1,155 @@ +# chef-redis [![Build Status](https://travis-ci.org/phlipper/chef-redis.svg?branch=master)](https://travis-ci.org/phlipper/chef-redis) + +## Description + +This cookbook installs [Redis](http://redis.io) from Chris Lea's [ppa archive](https://launchpad.net/~chris-lea/+archive/redis-server). + +Redis is an open source, advanced key-value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets and sorted sets. + + +## Requirements + +### Supported Platforms + +The following platforms are supported by this cookbook, meaning that the recipes run on these platforms without error: + +* Ubuntu 12.04+ +* Debian 6+ + +### Cookbooks + +* [apt](http://community.opscode.com/cookbooks/apt) +* [minitest-handler](http://community.opscode.com/cookbooks/minitest-handler) _(suggested, not required)_ + + +## Recipes + +* `redis` - The default recipe. Setup apt with ppa details. +* `redis::server` - Install the Redis server. +* `redis::client` - Install the Redis client. + +# Usage + +This cookbook installs the Redis components if not present, and pulls updates if they are installed on the system. + + +## Attributes + +```ruby +case node["platform"] +when "debian" + default["redis"]["apt_distribution"] = node["lsb"]["codename"] + default["redis"]["apt_repository"] = "dotdeb" + default["redis"]["apt_uri"] = "http://packages.dotdeb.org" + default["redis"]["apt_components"] = ["all"] + default["redis"]["apt_key"] = "http://www.dotdeb.org/dotdeb.gpg" +when "ubuntu" + default["redis"]["apt_distribution"] = node["lsb"]["codename"] + default["redis"]["apt_repository"] = "chris-lea-redis-server" + default["redis"]["apt_uri"] = "http://ppa.launchpad.net/chris-lea/redis-server/ubuntu" + default["redis"]["apt_components"] = ["main"] + default["redis"]["apt_keyserver"] = "keyserver.ubuntu.com" + default["redis"]["apt_key"] = "C7917B12" +end +default["redis"]["pidfile"] = "/var/run/redis/redis-server.pid" +default["redis"]["daemonize"] = "yes" +default["redis"]["port"] = 6379 +default["redis"]["bind"] = "127.0.0.1" +default["redis"]["unixsocket"] = "/var/run/redis/redis.sock" +default["redis"]["unixsocketperm"] = 755 +default["redis"]["timeout"] = 300 +default["redis"]["loglevel"] = "notice" +default["redis"]["logfile"] = "/var/log/redis/redis-server.log" +default["redis"]["syslog_enabled"] = "no" +default["redis"]["syslog_ident"] = "redis" +default["redis"]["syslog_facility"] = "local0" +default["redis"]["databases"] = 16 +default["redis"]["snapshots"] = { + 900 => 1, + 300 => 10, + 60 => 10000 +} +default["redis"]["stop_writes_on_bgsave_error"] = "yes" +default["redis"]["rdbcompression"] = "yes" +default["redis"]["rdbchecksum"] = "yes" +default["redis"]["dbfilename"] = "dump.rdb" +default["redis"]["dir"] = "/var/lib/redis" +default["redis"]["slaveof"] = "" +default["redis"]["masterauth"] = "" +default["redis"]["slave_serve_stale_data"] = "yes" +default["redis"]["slave_read_only"] = "yes" +default["redis"]["repl_ping_slave_period"] = 10 +default["redis"]["repl_timeout"] = 60 +default["redis"]["slave_priority"] = 100 +default["redis"]["requirepass"] = "" +default["redis"]["rename_commands"] = [] +default["redis"]["maxclients"] = 128 +default["redis"]["maxmemory"] = "64mb" +default["redis"]["maxmemory_policy"] = "volatile-lru" +default["redis"]["maxmemory_samples"] = 3 +default["redis"]["appendonly"] = "no" +default["redis"]["appendfilename"] = "appendonly.aof" +default["redis"]["appendfsync"] = "everysec" +default["redis"]["no_appendfsync_on_rewrite"] = "no" +default["redis"]["auto_aof_rewrite_percentage"] = 100 +default["redis"]["auto_aof_rewrite_min_size"] = "64mb" +default["redis"]["lua_time_limit"] = 5000 +default["redis"]["slowlog_log_slower_than"] = 10000 +default["redis"]["slowlog_max_len"] = 1024 +default["redis"]["hash_max_ziplist_entries"] = 512 +default["redis"]["hash_max_ziplist_value"] = 64 +default["redis"]["list_max_ziplist_entries"] = 512 +default["redis"]["list_max_ziplist_value"] = 64 +default["redis"]["set_max_intset_entries"] = 512 +default["redis"]["zset_max_ziplist_entries"] = 128 +default["redis"]["zset_max_ziplist_value"] = 64 +default["redis"]["activerehashing"] = "yes" +default["redis"]["client_output_buffer_limit"] = { + "normal" => "0 0 0", + "slave" => "256mb 64mb 60", + "pubsub" => "32mb 8mb 60" +} +default["redis"]["include_config_files"] = [] +default["redis"]["ulimit"] = "" +default["redis"]["auto_upgrade"] = false +``` + + +## Basic Settings + +* `node["redis"]["ulimit"]` - Sets the maximum number of file descriptors for the Redis process. If this is unset or empty, the limit is the system default. The default may not be high enough to handle a large number of concurrent connections. See [Redis Clients Handling](http://redis.io/topics/clients). + + +## Contributors + +Many thanks go to the following [contributors](https://github.com/phlipper/chef-redis/graphs/contributors) who have helped to make this cookbook even better: + +* **[@smoil](https://github.com/smoil)** + * `redis.conf` updates for Redis 2.6 +* **[@svend](https://github.com/svend)** + * add attribute to set ulimit +* **[@maciej](https://github.com/maciej)** + * ensure `node["redis"]["dir"]` exists +* **[@dwradcliffe](https://github.com/dwradcliffe)** + * allow bind to all interfaces +* **[@duggan](https://github.com/duggan)** + * allow customizable apt sources + +## Contributing + +This cookbook could have way more personality... Help make it so! + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Added some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request + + +## License + +**chef-redis** + +* Freely distributable and licensed under the [MIT license](http://phlipper.mit-license.org/2012-2014/license.html). +* Copyright (c) 2012-2014 Phil Cohen (github@phlippers.net) [![endorse](http://api.coderwall.com/phlipper/endorsecount.png)](http://coderwall.com/phlipper) [![Gittip](http://img.shields.io/gittip/phlipper.png)](https://www.gittip.com/phlipper/) +* http://phlippers.net/ diff --git a/cookbooks/redis/Rakefile b/cookbooks/redis/Rakefile new file mode 100644 index 0000000..52903cd --- /dev/null +++ b/cookbooks/redis/Rakefile @@ -0,0 +1,31 @@ +task default: "test" + +desc "Run all tests except `kitchen`" +task test: [:rubocop, :foodcritic, :chefspec] + +desc "Run all tests" +task all_tests: [:rubocop, :foodcritic, :chefspec, "kitchen:all"] + +# rubocop style checker +require "rubocop/rake_task" +RuboCop::RakeTask.new + +# foodcritic chef lint +require "foodcritic" +FoodCritic::Rake::LintTask.new do |t| + t.options = { fail_tags: ["any"] } +end + +# chefspec unit tests +require "rspec/core/rake_task" +RSpec::Core::RakeTask.new(:chefspec) do |t| + t.rspec_opts = "--color --format progress" +end + +# test-kitchen integration tests +begin + require "kitchen/rake_tasks" + Kitchen::RakeTasks.new +rescue LoadError + task("kitchen:all") { puts "Unable to run `test-kitchen`" } +end diff --git a/cookbooks/redis/attributes/default.rb b/cookbooks/redis/attributes/default.rb new file mode 100644 index 0000000..692fe4e --- /dev/null +++ b/cookbooks/redis/attributes/default.rb @@ -0,0 +1,77 @@ +case node["platform"] +when "debian" + default["redis"]["apt_distribution"] = node["lsb"]["codename"] + default["redis"]["apt_repository"] = "dotdeb" + default["redis"]["apt_uri"] = "http://packages.dotdeb.org" + default["redis"]["apt_components"] = ["all"] + default["redis"]["apt_key"] = "http://www.dotdeb.org/dotdeb.gpg" +when "ubuntu" + default["redis"]["apt_distribution"] = node["lsb"]["codename"] + default["redis"]["apt_repository"] = "chris-lea-redis-server" + default["redis"]["apt_uri"] = "http://ppa.launchpad.net/chris-lea/redis-server/ubuntu" + default["redis"]["apt_components"] = ["main"] + default["redis"]["apt_keyserver"] = "keyserver.ubuntu.com" + default["redis"]["apt_key"] = "C7917B12" +end + +default["redis"]["pidfile"] = "/var/run/redis/redis-server.pid" +default["redis"]["daemonize"] = "yes" +default["redis"]["port"] = 6379 +default["redis"]["bind"] = "127.0.0.1" +default["redis"]["unixsocket"] = "/var/run/redis/redis.sock" +default["redis"]["unixsocketperm"] = 755 +default["redis"]["timeout"] = 300 +default["redis"]["loglevel"] = "notice" +default["redis"]["logfile"] = "/var/log/redis/redis-server.log" +default["redis"]["syslog_enabled"] = "no" +default["redis"]["syslog_ident"] = "redis" +default["redis"]["syslog_facility"] = "local0" +default["redis"]["databases"] = 16 +default["redis"]["snapshots"] = { + 900 => 1, + 300 => 10, + 60 => 10_000 +} +default["redis"]["stop_writes_on_bgsave_error"] = "yes" +default["redis"]["rdbcompression"] = "yes" +default["redis"]["rdbchecksum"] = "yes" +default["redis"]["dbfilename"] = "dump.rdb" +default["redis"]["dir"] = "/var/lib/redis" +default["redis"]["slaveof"] = "" +default["redis"]["masterauth"] = "" +default["redis"]["slave_serve_stale_data"] = "yes" +default["redis"]["slave_read_only"] = "yes" +default["redis"]["repl_ping_slave_period"] = 10 +default["redis"]["repl_timeout"] = 60 +default["redis"]["slave_priority"] = 100 +default["redis"]["requirepass"] = "" +default["redis"]["rename_commands"] = [] +default["redis"]["maxclients"] = 128 +default["redis"]["maxmemory"] = "64mb" +default["redis"]["maxmemory_policy"] = "volatile-lru" +default["redis"]["maxmemory_samples"] = 3 +default["redis"]["appendonly"] = "no" +default["redis"]["appendfilename"] = "appendonly.aof" +default["redis"]["appendfsync"] = "everysec" +default["redis"]["no_appendfsync_on_rewrite"] = "no" +default["redis"]["auto_aof_rewrite_percentage"] = 100 +default["redis"]["auto_aof_rewrite_min_size"] = "64mb" +default["redis"]["lua_time_limit"] = 5000 +default["redis"]["slowlog_log_slower_than"] = 10_000 +default["redis"]["slowlog_max_len"] = 1024 +default["redis"]["hash_max_ziplist_entries"] = 512 +default["redis"]["hash_max_ziplist_value"] = 64 +default["redis"]["list_max_ziplist_entries"] = 512 +default["redis"]["list_max_ziplist_value"] = 64 +default["redis"]["set_max_intset_entries"] = 512 +default["redis"]["zset_max_ziplist_entries"] = 128 +default["redis"]["zset_max_ziplist_value"] = 64 +default["redis"]["activerehashing"] = "yes" +default["redis"]["client_output_buffer_limit"] = { + "normal" => "0 0 0", + "slave" => "256mb 64mb 60", + "pubsub" => "32mb 8mb 60" +} +default["redis"]["include_config_files"] = [] +default["redis"]["ulimit"] = "" +default["redis"]["auto_upgrade"] = false diff --git a/cookbooks/redis/config/license_finder.yml b/cookbooks/redis/config/license_finder.yml new file mode 100644 index 0000000..45173be --- /dev/null +++ b/cookbooks/redis/config/license_finder.yml @@ -0,0 +1,11 @@ +--- +whitelist: +- Apache 2.0 +- Apache v2 +- BSD +- BSD-3 +- ISC +- MIT +- Ruby +dependencies_file_dir: doc/license_finder +project_name: chef-redis diff --git a/cookbooks/redis/doc/license_finder/dependencies.csv b/cookbooks/redis/doc/license_finder/dependencies.csv new file mode 100644 index 0000000..91da360 --- /dev/null +++ b/cookbooks/redis/doc/license_finder/dependencies.csv @@ -0,0 +1,105 @@ +addressable, 2.3.6, Apache 2.0 +ast, 2.0.0, MIT +astrolabe, 1.3.0, MIT +berkshelf, 3.2.1, Apache 2.0 +berkshelf-api-client, 1.2.0, Apache 2.0 +buff-config, 1.0.1, Apache 2.0 +buff-extensions, 1.0.0, Apache 2.0 +buff-ignore, 1.1.1, Apache 2.0 +buff-ruby_engine, 0.1.0, Apache 2.0 +buff-shell_out, 0.2.0, Apache 2.0 +bundler, 1.7.6, MIT +busser, 0.6.0, Apache 2.0 +busser-serverspec, 0.5.3, Apache 2.0 +celluloid, 0.16.0, MIT +celluloid-io, 0.16.1, MIT +chef, 11.16.4, Apache 2.0 +chef-zero, 2.2.1, Apache 2.0 +chefspec, 4.1.1, MIT +cleanroom, 1.0.0, Apache 2.0 +coderay, 1.1.0, MIT +dep-selector-libgecode, 1.0.2, MIT, Apache 2.0 +dep_selector, 1.0.3, Apache v2 +diff-lcs, 1.2.5, MIT, Perl Artistic v2, GNU GPL v2 +erubis, 2.7.0, MIT +faraday, 0.9.0, MIT +fauxhai, 2.2.0, MIT +ffi, 1.9.6, BSD +ffi-yajl, 1.3.0, Apache 2.0 +foodcritic, 4.0.0, MIT +formatador, 0.2.5, MIT +gherkin, 2.12.2, MIT +guard, 2.8.2, MIT +guard-rspec, 4.3.1, MIT +hashie, 2.1.2, MIT +highline, 1.6.21, ruby +hitimes, 1.2.2, ISC +httparty, 0.13.3, MIT +ipaddress, 0.8.0, MIT +json, 1.8.1, ruby +kitchen-vagrant, 0.15.0, Apache 2.0 +libyajl2, 1.2.0, Apache 2.0 +license_finder, 1.2, MIT +listen, 2.8.0, MIT +lumberjack, 1.0.9, MIT +method_source, 0.8.2, MIT +mime-types, 1.25.1, MIT, Artistic 2.0, GPL-2 +mini_portile, 0.6.1, MIT +minitar, 0.5.4, ruby +mixlib-authentication, 1.3.0, Apache 2.0 +mixlib-cli, 1.5.0, Apache 2.0 +mixlib-config, 2.1.0, Apache 2.0 +mixlib-log, 1.6.0, Apache 2.0 +mixlib-shellout, 1.6.0, Apache 2.0 +multi_json, 1.10.1, MIT +multi_xml, 0.5.5, MIT +multipart-post, 2.0.0, MIT +net-http-persistent, 2.9.4, MIT +net-scp, 1.2.1, MIT +net-ssh, 2.9.1, MIT +net-ssh-gateway, 1.2.0, MIT +net-ssh-multi, 1.2.0, MIT +nio4r, 1.0.1, MIT +nokogiri, 1.6.4.1, MIT +octokit, 3.5.2, MIT +ohai, 7.4.0, Apache 2.0 +parser, 2.2.0.pre.8, MIT +plist, 3.1.0, MIT +polyglot, 0.3.5, MIT +powerpack, 0.0.9, MIT +pry, 0.10.1, MIT +rack, 1.5.2, MIT +rainbow, 2.0.0, MIT +rake, 10.3.2, MIT +rb-fsevent, 0.9.4, MIT +rb-inotify, 0.9.5, MIT +rest-client, 1.6.7, MIT +retryable, 1.3.6, MIT +ridley, 4.1.0, Apache 2.0 +rspec, 3.1.0, MIT +rspec-core, 3.1.7, MIT +rspec-expectations, 3.1.2, MIT +rspec-its, 1.1.0, MIT +rspec-mocks, 3.1.3, MIT +rspec-support, 3.1.2, MIT +rubocop, 0.27.1, MIT +ruby-progressbar, 1.7.0, MIT +rufus-lru, 1.0.5, MIT +safe_yaml, 1.0.4, MIT +sawyer, 0.5.5, MIT +semverse, 1.2.1, Apache 2.0 +sequel, 4.16.0, MIT +serverspec, 2.3.1, MIT +slop, 3.6.0, MIT +solve, 1.2.1, Apache 2.0 +specinfra, 2.5.0, MIT +sqlite3, 1.3.10, New BSD +systemu, 2.6.4, ruby +test-kitchen, 1.2.1, Apache 2.0 +thor, 0.19.1, MIT +timers, 4.0.1, MIT +treetop, 1.5.3, MIT +varia_model, 0.4.0, Apache 2.0 +wmi-lite, 1.0.0, Apache 2.0 +xml-simple, 1.1.4, ruby +yajl-ruby, 1.2.1, MIT diff --git a/cookbooks/redis/doc/license_finder/dependencies.db b/cookbooks/redis/doc/license_finder/dependencies.db new file mode 100644 index 0000000000000000000000000000000000000000..1e1e87a8738c2994f7202740649becaf9fc79192 GIT binary patch literal 90112 zcmeEv31D1Tb@rR(-8b*e7E7^(EIWCY>{ugfMyq!cN0t?PVo6pk8QU>N9*v%)u|_lU zES8o1jU))Av_PQ{XbJ=fv?L8>r)4SoUP_@XWhwh!)|Ta`%m1D8-W!c<#c@*lL;H`N z$Vd0xcb9X|{q8yEp8M|bE&Zv2p-*IUGl_!UC0!-CT+)rYE=kgE{J#$WJAbx=@H)TX zUsnbFXyk4w`Ykyq-Xf{q4@%+P;kO09<-Q~ek32l4mykM?Rcy~=?q2dW&n zm;>5&Z=i0=7S}Tx3W>3_F>cHnnem00bqXhLtVlF6$&`_|GFY^?_h?_wNS{8^v%kMj zKU4YWO!Jx4_!&KwDHxMRPCw9hQ_r#f5xsfvSbu*+A3QRm6QvIwjSuu3J*gk=JK3tA z$s}fsGy3^NE;*IRHFs{`9(lAEk@>ul%cruL$H_~=X`Q%t1OXwO(Sn>G@eOS)<0PN$MaCU1-;^7+(c<_Ww= z49!1wuOr%aw^Fx#yXzU-E*bC9u|#sZINM%1xK5f_G5(FKWofL+!Ai;ht;xX^h(W2V zMyZQNU5oWB29}tPCT3@I+4G5X{^H;LD~A8f#gEVEE60AdNw4=D8#xjmL_q_6gCm%! zWf__ut;COGgYjFA_320ZZt6SQH`v=ZT#*x?bVffih&j~X2QcdG8Sd>l(1)_Dh8wwy zdQQNDrN29@$qb>V~5(DU}&FF67Up zp#Y91iiIryZqM~)CXRMo%qJzVQSzEis#4e6>oNzJ^<%}%c-qK~PUf=3+5BjkE9mQG z9%(BFC3IEPh?6td<*T8ylBEr1oVp$W>{sl4ya$6X1^G$+q>7bV6714l_{M{&DnxgxY8W6U5I^et-;u(f55GD5yzor;Kv)ZXIrMO7I&^(V34S5? z#^7DSzTmpRzXU!Kcpz|F;6PwKvRD69IZ)+5l>=1{R5?)PK$Qbk4pcc%<-qSS2ioi0 zsm}I}?vD2Mu8!{Q9iwmyz?Q4D{2e2U(qp-?=5_9q^ak?p?%2`29dFEJ&l{sowuEI# zBWL$o_qlS;?vCA~a2k)6-B9__LiQ4;65hJ*YWFect@ifLUHsO?t|rP?Th6zuV`o>V z({elOM9N5y=c&Clc6&Rz+B-Wsx{we5!4sWLrr-o~+Krt08h0EucXf1h?8d+CqvN^k z?6SiPIigp&lgl~UsjTIKE@~sP?+m-=DzbN0bR|7+7Yy$l_)uvZXXo~g zuI_f!!#sBSmdnmK?H*m`_m-f0n5yHqI@))xXq`PgD7+=$K3&e%-rhY5UtnT%B6YzT z4Ld6_uiL-ekoMgjJGlqWNZYNswE3-?JHa({ckJre-q~Hz?^QZ`qv|mgD|>rq`|j?Y zqu4dcOd6x?4;W=}becr3JJm0Dyv^sHvFLQQ&)ZSpoJ>*u8y+lX2O{hfIwg_rP+d_Rex9$E%Gzo80chx-m!wdFQHrnz6!=!sM1m$CQPw0V*hSsv5iziPK< zt}_3XTl`_0fsfPV4{h}fbA^v#^9Nf!rej!f$#Zx))9U8G`#R6C>bM9lWB<#*EMNGugj{RGzYhOA{Dbhf!#@dsE&QeMXSI{s z0j*7|5C2$`eIHUD@x96SD&HUZ?(p5>yI#CfxnKD`C9mA7^vTbXTf8~{H#}#hDepG# zdauX*M%U+EyIt?|o^T-19Ng@eqIG6nYEqISW5tPyXs$RmKWbzqQyHT@))8ypk3_vV ztIubPIX#&jH}ttwVM{I>5wVh+Ph}?c!jz%UrV|A^x1i^WnG7W!MS(q! zEB92PFq_}g);5WXies^4cBbuUDqA>6E}^!I8+od+XEuR254|(i-gw&dwP;dPU0>g6 zr$5PTW+F9-j>I~8^n7Y&Hf^9AlvcEYLiA)BUc}2id+hdD1+HvL?KVxi)@JnCd^R(h zpEZ)^iAVtLGi%LSY9V5jQ;1IiSJUmW?pV9Sr$SbzV96A?ogF)CBnx?q>obYTR8mhA z5S%tv!~qdFK*x#4iOje@mrE5u{|1VNLuRCpSxkZv1rV{z7l+g(Z z>O4u3Gl6hA#3Ih?86!10HI~gyWwYZt<c({Hu;p>7`moqMbH9#^Y7magiu5SGSU#Yqvs>ZSk2b zCUWD^{A@Ni9llPJ>MD~%TQq5{oluy{=0NU@+P_~sS15UXr1D{Y%7B+PTP&E{H)+!L zx;yX0d7{Xek(;I`6Vb$MDw<4V=n5D}$mdX8@8w^4;$vpc6f>zLf~z2eHZJUUUe+mh z-pIl6`$XR(_5ldW?IA2!$)-_VlN#y{O2;8{6Ny}6JTc!M+Z}5^G%_;OcC>GJgj->C zp)6^uj7mD2FQoEmNLZU)j4dFgb=Uf(NL{M57ZTDiQ%o09h~6wjXYm3!!gJP1{#9g&~g%q)p%Afm%Lm^KBnvOBRI&AE<(Ur;-?bl z4ZR6)0~sxh7UpM-R=p{kO98GEY3w3ck!r0xQ<~D*Bob#6fa|$bI<1e<1j!>JSx?X$ z8^b%)KD@Nre8IR*=Tmg-qOIIE?zJ;>uBivW)g_ao;7bDWve|TE%;<=9fhCx=3|mn& z4Gl$&Js63ys54K75;+LR>`TH#SPnVns`;o|w0BfwtM0Qs`>+c~dVuSxZG(nPU1mdKg@8YDFh4-bPCkPh^# zlcq~-HfO|k^;m#mr87)p@NlF>pDo&861##d_Ee}?6X&K7B7P3gFx_UAZTGw8Nt(3L z>0+XoDWr`;q)w9#+iBJ!n9QD!=8I!##HI6^A4P(Z!vU1F>y{ajLmad{|F2rR+-poH z_%f=4d@A=tuaZ zj7~zqin)d1!!hjIC4hXIG@NA^rJWRvgl}NGzu!t zVofb@6XHkXmQYbU3I^Jb?dg0>vt_BGlQ9Al6hCRi@B^b&serCgmM1|PlP@_n9~R5( zV@$DF9d2As=B51^eJ)Iv`srt1PYP5Hx=lwr_}O`sv>euo20%sADf|lU5HZa&%M1g= z0#S%Tk28BrA4%k=t*%$@Lh3l8pb-M5u$Rw;m)TCW=y{$4F-_z8pi?oUC!n`w=V-=E z;Oq;eFuzCBT6D@o>r7s6&Vj@j+PlG|-BGXjVT9_Wi455(&@hf89p}~1GfiI}^~&mX zeK4z^7yw9c=@@QfJXOHDiR5#|RBn2{Ts#$F&0FNcyvEX(7=wW8&EPFBt_dA`-Z~7L zgr0{@VeE@g+p%Kj@_G`d>nv`}BNs7@)g1tQ4j7!u1BxLmc2^z2%EJhPxq%h9VB|6^ zjnsrGBcHkrLyCT;lBt4K9>083+PILyJkoDMcluL#=>1m906IMGiCiu*pVy)M>(iO+ zoZSNeUuw1pfd%8yP2z>l8|bVDkOE+}Z5gV-b~GKGoCe~L+dTqbSfd^*;`9lPA&r&A zSOi+6j9g5=3EVL*;22ZdXhlsHComV#BKw^}A~%Kr02`obfq@}F7@gH`8W|$)4i6uK zAx8Yt^=1?}W8~8Fs~2|kKo;f>Q2n5R$qd~dT+1ZWMVx*lai{h%FTm3It#}BQprFM0 zL<+hHm_Sp)DT2g?i-Ax=tP5De((^GM;$aI17--OTBaJ>9$)aO^a4%O=k=1{R5?)PK$Qbk4pcc%eh9C>?GL zcZF{b-%PvzuJ;Jp`|7&TF!rDK*)*h^m*v{CFQ;k{pkaCTut4`Re6SnGvUE$F! zdrFy!(y*NH40}DlI4t4Pz=f3& z4sqI^t!vt{mo{B#XQ+$99+`?Vx&Occ-XW$9q)Q_UIHSGuQ&k?S%0pFo_@A#lgg!2H zIT2K4<>BMfRZg5GDG$gKuGrxGqX+?I06a@ z`sG5V@O-Tk{9TT-s!S|LWK|}XW4Gx1KR)_TfBgKj`lHH$DhH|@sB)mnfhq^89H?@j z%7H2esvM|tpvr+N2maS_fX@HB!~a74S{MF5;eQE#G5qQ9$HVUrzXRU`d^r57@crQz zgr6OLdibvJ`EVvY5grX64<8914DSo?47Y{1hOZ6Rg~MSb^q-+$gnktIPUtK6df?xM zJ{0V3%w@vve1h{&k5ZdGDGvBbD>ly5jq*VB@_?s5A6wchFU_6q4lAfkU!)J z{yO-x;P-=H$DIS84t^~7zTn$&_rOEJR|fwi`267S2N#2P1dG9$pbw?z=*9AjC5%`b5&jUXUd@Jzfz-I%W2z)T`uE1LYj|5&Fcv;{@_;TUBfqMcM z0_Os$Kq7E!;Ar6H!2ZCVKxbfEpefK0s0nBRm;aakANjxK|C0aH{*U?J>;EhNU;1C; zf2seE{m=F<`S0-O{nP$2|4ILl|DgW{|8{??f3v^AU*lK(lJ*PjhuSx_FKVCCKBB!_ zd#m<{_MmpZ_I&MGS_x4SIW47~(T;0_TAy~k)~#*R8nt@uDos)UL;Y9v2kO_=e^fuI zen@?n`eyZE^_A*N)aR+sRPRye)Qmczo>oWH!|Hx@x7x05Rj*OksX^82`?c?|_uNGwRpYwGx0LfYI8}b+BPstyV-z~pYenfsyzF&TU{QL5Pe4Bhuo{~r9r^o~H0eP?7 zDMw^ou9HKu?EN?I&%EFBe%1Rq?+QM(;!3S9o9SeXjRj@7><>-i+7qp7xG- z`@Oy1J>Cv)v-eu>I&aYH_59lNQ_uH2U-f*>^9j!fJn!_p$@7rs6`mJ+p6j{SbGPTb zXU1cAp6VI)-0bP`?DRzOO^x*)_`#F~k5_WLqkHtfYO)0xrO$hj%Nb zyDh3slBE zB1BG}ZWjSG9lrlt|L*5xCkT=Z)@=k+L zQSMnzI3-FcCh*b}5wmxQ(j*hNi_!!WL!x9babA?hnYc}ql1!lBF(yz|f(g`c284N=KPEDN466aafdwm_RR&Fmb&o4Kk4wr2!_+ic&ulsQfS!Cq(IH zCPJbVXJVZw9by74J;=m_DBZ-wjiS`Y#0{c!fQfNY>Se+ZrTs*#!$9{iF(67eGJ%r! z5pfdoasv}{qI5kIYeebEOq`cXdqJppvWF7pR8iVZL>67&#Y91rb~2F{r5#Lwaod>y zy=5EG-K6lCIQq7-1_v?%$RKm{5Tm<=iuPZcE}5vRPO zq%eWH1qc-)DRTm<@-l&{fI7&FssK75P?d`bR0YgI%4uN&azJ>5$%u36slsH$IV@z8 z5Jx>MOa`38w+fR0$94FGFd1jbJK#8FWJ1I}^0#DH^L2$KQlINm0}aUE}yO+p+M6)@l& zuN5W(&PYI*3^*gh!eqc1IVwyBoROyplL2Ss$--p78M#?D32@XAVKUx~pcuj%*T^wp zGT@913X=fGHHh*UaR&XuB*bwIqF{!cL6pakGkCKw8FB`15hg><;E*sGat7V9NsyzW zJVG4TAgW@>89X9PhMd7I!eq#ahlR;yM@V!6GxqlIPpWmWW5hzDJmhIPsmr zWWi6GUOcE zFHDA&TgbI;_SkZG2-mfWs?v`#eg&5>_8R*9M=vopAn~f zyD%AXx;F@u5vRLTn2b2xXapgS3T83jbax5Ho9+(5aMLAZLL0S95Z1W5+5}@wM}uIf z>F5-UG#wiR15JmJ8D}~K!%RoJV3cVWGJ%ZRE*N6kqk<78+9nuaqA|hv67>jz7gy9R z6TGNV!Qj%`C>UE>w+e=q)(wJ?#npP9U}R}sFBn)_g-p1jwhBg-)>^@!(z-=3rnGJr z3@Mu%1tE%S^H#x#vU!7GK-qkqU_9BpUND?&7BZoUx>+!mY_1gyC7ZVhMv|6of`O!^ zQ813QY!wV6EgJ-(h^yr~!6?$QUNDHX2$?WMZ4rzhEwzFHq-Bd>{Ak%M7(QB>1fxew zL=bqmB8`H|c- zXxbteBbqh~f(X~9uwaPT)F>DsHVMH1v1z?b_@HhQj1HS>1%ZQWlP(AxTzaiwaL}(6 zj1BeI2!@9Gje?P(euH3OsBaLA3-x}%uu#8VFe=mw!JtqdmI)KodclZLUn>|8>T3k! zLCuo{!$D1*U^J*%Cm0NBLV~fNCMXyRY660gz*XZHj081q!9Y-Rm0%pG2@8gSnze#a zpyq0sKtQb#3;{K@f)Kz}vqmri)c6Dgz#6yA^M8%t>F>Kr@a*@61y6ooNbubE1qDxi zUqJB8_xS}+e4i$0-n)FNO!Hp#37++`B6!lv_=l2Z88cpWd1XEd=kX;5={qy~KPGJ&s2ll>nEzh1((|Nk}o!|-?T<^M0(5diNDzZDOvf2tg)a-hnA zDhH|@sB)mnfhq^89H?@j%7H2esvM|t;J3g5Z@{Cvj(Pp~*&u5k)h)eTs2-QwE!{8s zJT8~JUsmX!4YHtz&-Kd4c_$*GRo4;OLr+G$Zu}DWxIC&#_P>4p|F_VWYL!(ER5?)P zK$Qbk4pcc%xID=hjs;TmnLz-zv8c7 z_BRB-8%l=W86LXw_FQuLd?~t)Z=*k4qTA>jDsKDjz%{))?Q44J-fF&tk+0LXuhOQE ze9{MTTXDx?el~^6Vs%^!I+IG~vJ<$N)S!!Vo%=Q^uYGqW-2{mnbm?|=T%2oP^7}X{ zpo?OMapg1a;k~4?!hG7G`$R9TYL7l?6k2iLGX1~}*sZu(l&%=%I~{Sqc*Uj4iCl6j zmCl|k8f_Kqu)pv7!l#A;(x!TI+e3NWXB=O?fESmeZ;y41OyMR`=k{RSBblAcmv6Vl z&GVT8u2QAD(ydGF6MRo6u0o}YJ#pFTKzyK&F0Q4Uy>+_Q_cvFZM#bTxS85;rquX#3 zc|*5a-HLWx;BmpF)cJhTzMp(Hm%`=U1-eUj2KVo#@@O>%Qd{mc-oHSF;>y-yE=R3) z3Pv5cOSx6Y4adnTJ)1%A?5lS3)?L^gvE8@{RKty>xKtEZ$gXG*Ga&)7(!s;>J+d^i z)L;@gx&f5$6h+;}g;`t-&36W;wPXULMicuqa6$09Mkhp)i}Z=LIJ`5N8IHn!3| zxwv{ZXXxXpJl#^tGsh16;g-)7ae@38x|XpX7p8LA;^Y)xuv^R<8aQEO&ZlzO40Rj# z!{<_z5%;O`V4z>P`4l6a!OiihaqxH?V}MI-!KkQn@9r2bbj|OjBAIDuFea($vHd4UN#sXIP7d`A zkM{T6dUCY?NY8<78g7cEO8DA)ov-CUIbiYDi9|YYY>ny1r*MCIk#GL49AxG!dd=fS z*9GSa#aUt&RkLT$2;G!Td`WTd9C|5QaBbdN&89H6EYz+wdPm*nn$FzPDbEi(0dpw}Z&yp0ifWB- zLw(!mLg0i(w+quv#efRB+_$pZb1BUJsl<5$u#y;$&H;=JtQ|>QHEuO~B9%iA3z++m z?Tj&}IpTZikmrhyz!V4rsaD`f!IE*zv?Qv)G=T&q_`3Im2An8hIZ0IyT(x`dhgFlK0cvCoCn3@X8(cfIEw%Tm{jsRsF2A$?q|EirWQ zG_^20Gsbse)3wvuWu{N7eIYWsiT_$4FD+moUI$j#*Cu1>M2sQIA;ucx8fHEx;8Jno z?ur>}jSB&>l|4^4INKQ^C3XjwS)g6S0*H)-iOd$lW(s-CP2hTMYG1xUqkIWIT5AR) z0A0Ne(Hm#PB)D>rvQk!@DbmXGgU}-h>4yMUX2KQj7S>|Y<>*3eddMp+VS+((k#Ciz zI(J^T!PauG?Wtj_TAFp~yv4A1&tMOjjZ3_Fy>~ipH@7)& z;KFbNni+F6XG{QfGD#z1OM|mY&|=8f%+CRGp$1Jt2Oz*qwWoyWAp?s$TmiDkJ)zsVeEbL57Bm7d=WT#Zv zV@TO{*iJ5gUO$>Ij-??mQ5tgqI9`W3gIrfTcR5TZiW#ix1@oGEO}e%&nH+`H3*|4D zO(({Tj#yW$y(eR%V-(XKh>GdnGdzM73T9cJ0GDSw_wsRMC7$J18k+OPvjCOp#C&2V zfk|n%!RB1h{d#GQ^gKy=9=@Nn!Mol2$M_Q4FU5!5F6Gs})9NnubJ}~{Uk%lVez5Xy zxEZGez8C(URB^|j%_eDMn@x=s^BAKj{wIYA7v9tiFW;4wgl=3&XOmYQ)g)&qXxTA` z8vIg2{b}>`5%Lri$Fjh-_E_i6SVvD0D=n=P7Gx%~;|A7sGRCoPCt05=WV7j*jz3qL zSL4j78Q@wL`Usc~eSxOEy&4zhqvJUUNXDvjn5#Qj#JU;NE)~$Ru|y%CJ>O^UD@J{DncA|W90M7eoJBqG`>>UP=|yUveWi6+gCE~jmsv0brFt5ws6 zF-vQ$Zp=fuuxlozR-eg^mz@lGD>K%Y1hb8e1FY6yX%-+2zQP8?q6XV)9IwLu$P}>J zLBy#f%j8*Epo6d6UbLKgU^iGEl-eA5nJ5;=5;^5=sl~aP$!0u~?AeS(P+D6TheWCy z>DG%5bk6dh&~KzISlBymtuRNyvQv%3xW?0^b=Uf(NL{M57jwJ8Qe0u`Mai)PIL8#W zwOJ(6*v+uZ$F>}@IZKxsGzxq6kmeRU2E&S1v2qfzW<0xEAuv4j_z}x8r4=^;y@d>v zCR@83rKPl0Z*nw-QOCT2)`<113G*tE9-U2K&2$uJYBlLyG)PeJ6VyJizbq^jt|{oC z)A^JEd!%h;uk9fi)#+}_gAz&eL?V|MPt3Q+cE{SOn{Dj7>lupcw*Su7omQ(SjV7h? zX|%Y_F2)hc?VB`dd)=LP;^vdcn30>#PZ{ZnXks=MwMvEG0PII4y=lTuo;eljGt@uk zbVA?nysT4h3yGfSd-ilMw$hw5{&&VMtJ9 ztT-_dE#rG0cYnp&`y9Au{OQd?=_L^a^PoN%`Nw#?3fdn{WeW#k9Om0Ds%~XV|M%U; z+O;Jht*tkMW^gyevM`m+wZ}Sk(_Dwv%+N$=G!0vv7FJS(*6nlGSKqg#W)5>0njjiOV$)^|y82$J*%27VI^a}U4dH~SB}ZM&eyP6hw7KOLbPn#B zsg%))+v7S<(o!%%3dA@7Hem?8hH zjW;t1tSM9m?7SJ0ONUdd&1CQ^hb1x5W;M;3#wQ}|^{+L_>mOnF;V2dkBi$bBX85G~ z2nB6B(y>R3S@xb@9U$E z@k}F&Dm6)yx}&_`V5JqUW0%&PzTC6RHl?DHl}!o0Z}|o&zSoH~EpxIpfVIo)GUcSx zLITnBXtr2@HWh2%4>Zz?vz9W!s+0|x7Ds84kdvMm&M!I>#4^1d8NyRimQX16Je<*Z zBJVzS-^9PRqT*631@v{2sjpxa4@nEmWY4Lq~?=Cz#FpnIv?b=}~Z&7}y*+ zgI5L;N&U#Megfoi_{eB?tVO?>*VSX}%y(F7yF4r1@|L%sd|3chD5(?rnGqBmgWLCv zHL#Y7<@j`2Eh9fC2AYR8S2?QWHzDz3?-guUbo|oF&J0=Z&=~a~f2OSa!sAgK<7;(UKywFNcOK7gNZ3b&0 zX|1a8wZda(vl(Mc+88UiIVHtpXfL;9N1C zO^vr%S?uMgw5G!^U0Xj>>N$xnl{2AFJFplf3)x)Ma=QVxosCAzfr%B?*=Z(hBT4FY z>VVP6dtzu;7LG<*%~-hrUY1J%tH{R*aVoNhwBnKNA+}Ci-mMI(AkNW`5htG+Pu3geS_&aII(4+hmZ(SBMtS`_?AZWB+ zn%l>*(OqYHOb^tyi1RF+>WFP84S{_w#Tzu_xl^jhkkndJwZvE131yeabe z+PVRa7_#dtc4mo5u#m}NnVrqk@^`5=U$d{mTTx6FXQ26;8*bEo$C@MUtV<*+SOb3n zwk^q=AIht99^3jf ze;G_@yl1l5oP}`=^#rfl6JyYrqu-nveA#)zbvtV8-fq9NrQVDl2GlO&Ml_XW=;$az z!Ja)R9&0}>IPlRB6xa>1ZG6b&a&ve&!vlFjvze*~tw-PVhq=55AT*z4s< z-Bza1IH&dNx6|x7GEX?38;efl+5bWYd-qcecmZsgxDJ?1K&Ia`%nisBGb?Fp`5lH& z9|(@6uwX?%G?Us$xW=dmXI&yH_hB`~o&wV#{d~d*&Hf zRFhT!0`J38Dy=9`@nCA;7ke*{O1E}5v0ILwvfm3Jj74EZQxZCIQtS!1hMmPUHYKnD zPQXu2Rv40&a%^dZfc6W}=`gMk3sPceUtynq1oqn`nQN92N3#@r>`*yJsmI*B<&+Bs z>>(_QleCNM?7w1_uuA4A7N@c(f{@^(#zrA{P{8bcM!zw2aC#l<{Q8T2cu&3#;xMIt$%w~8|DhFsX zb)IrtyEzy2%HHyygIz$|>R1gnt%45N-kgF#IGJrL3*Aan#J^a(6Ta)Z`KRwY4tFD8 z=$wEXVtcH6$XT2$)fFKlm_2alvFNeJZ^c2d6x&jZz%?ruHk}f=J$nWy;v``^GiD)+ zQO<3f_&m7e1H&L&P7GK$yrsKxb-bwSFxu1BOe(jmxg!E8v)V^4LOTkA*HjLFV#|p< zr@ToyWniQ+`0%CkYhl0n%SGa!CB18&|# zCS^|&uwe$Gi)0KMtzK4$4w89O88J|qLYBNWBiUja;|8girs)NVb^M|v{FIp*mjfIj z3jhFeJe%gY0DfR0DRfe_H>ca+MVdhc#3l+{2lfeh46Z1X<3~QZ3p*Tj`=nz4;Cy0k z-e8f~K5PJl3i>g?fZl^Z9o7uVg}RF9Rd`CR5*%<3?6wFT z*sQV^Nvud|m^_fe<~&)7ka&_VtER{h?egi?FYJ`J*EXtq72Yr75c`Cz_f{fNcwqSs zd8}W?Qu_J;S#ukod7O6D49ta8zB9Hn){fv%I7YbnwriSn0E4@I(ZsbPO3H>iZw*At ztE%3|g|f2+h)xRlGFV_#ts-d!f{%~|Y}wp*Vi@;m*p6$_SuL^rHHq{LHVKd&rZ3xt z@T;J`5L2L9M(;bi-{L&Szjd^4S@D#*IUnO92Ph6{xv_R9^k(X3+t8t*w%)dz6Xz3c zWZlBR9zv7_C*+g4)NCQbWzd$QZse2LhQJuxMJzvogJ^~ojXZV)pl;jIZ(EIXCE7vgjn|G*^uQcpccfTDddV)jI-W66bEWU z$+)Ss8F@4_+Tdk6NDFFBaK!-zgbaXz2y=ja+4H2wRc%&#ygoDBC z!9!#sa1)5kAab&2pkJqWD4GD=y&)*v1qu;FM#8glnO5iRGFi;503b`>O5Mijj!kv; zMRR7BKTMaSXcdCDinG{FDL5Ohgw4~%IaG}iwLB!2L_2Qv-;#v}qoy1IkgPBme}^r! zp!g#k|IbOT>pkD_{)2p**e@Pd{>qn8Z_xI!)&G7eXWRN@@0b5Rik*K2$>yPBZ1Hb2 zH^OLbu(ssLW!*`e;ntQnZDU&wRBNl-3LqfG8ZHAUJ{-tA1fif{``#&If;~-{dBj{| zUKj}8{Y{l)uO-W^&->n5FIg)zFdRI~i4}!$_h6F35_Mb>h$qjc&l7}_Ek`FX zMtX-d4yKL*WoH?pk_l*qd3r*HfxoPkCeFj{#~%BPF_}d?J*NW6IB;2~P#r|YEi=2k znpO0+a&Q>w*eW1DrDI%SI|>{3sGR}~ZD6K-17}dl1)QXKYYJ!7%eC#GkeFgBP^aMg~nOj^#B71HOZIGOzcX=9z) zX0}01jO2|Oa3K!`2Rn#8;BeZ2(v1*fYpc*!$7Zof0s-b>!-9Z?0E!R&Hf+pbeRg8< zX^iM8Ek*GA7V^LG0#{Lh$~*9FPG44PKW1YuY22w4TpkhZOQwzaZfJ9G7Q%k#6ATt; z(2&|u3OvqmUz2HSpC)>IxmRiC@>aanhKm~6cOW3`t~2o$A7R-(ZpfmD zRE%ScuvbUxJSG`|0*qoryaE>}-jj6YgPaY*1j~>XZ_-f7TH^jxmiZeg#1be(HCDtt z9O=`f+PaNVgxEA7E)xfCVmlbEZn`P%{Oib;ExmK8=~Sh0h1c5f|Eb3P!v`8qt5Iov z#9l~pP|wJP6fGcBH($~Y2Ba-@=0Woytc$Ja-l)SeVpP{bj?+ThrU`Fb%Q9*?J`&i4 znWfP5Omdzk=gz0z%!V^|iT^N1le zI}Wk0bjtvIr4ejsp0{VTWwOu|CwpUgY9e|TdEh>9RC9YaWA0;b26LM7QyRxMMRUor z`SYk{ts?XlbCaqoH<{yO%qPbKFb*k5V?|_77eSG;w80;m%}$Tu|E(B9$TMbS4=gL& zp-qWShUF2(UfqB7eR~Yb9k_Huz<*&mye9$COqh2a=Kc(r1JGq6upK6%w;8!C_MWj( zMj=aJKkbK+^Nn^_48LBE z{D}Gg?(&_GT|Ky&O12Mh@)-vSWar=d%-qIdX(Tz;RxXY!bW#*BtyQWTBu) zL_PjiE4R64Gb5G+x>cfP@D8cgiCLU8#%AE`FuMn7)w=UeSm+2Tq0rfO+TwE9x1W>X zw>?^<7#(odQjr~h7%`ray&dIAxFQ`s~^%BWQb@l_Gpt3kT?vJV z?E=zIAOtI=giibZHmY@|3$1MxTZ(NsdkK-8ZzFJ6$+^%uDeeS+K>(%_NGM08Phc~C zB43$8!O__qQkHE@JA~SdjA&9@-SOkuvgA|*U6cR0t4FuR2b=Xc5{Ow+#Eu8gz2S0r z_7!DXMLa5{&X$pi`d4-u3w!G(dv&+4j%+_9taDPaS38=A{*s(BpK_cP*R|%_Q|KAA zqWQ$xbSEK8`{nc*C!+-|u*5ik467UbYJ5-_&fjUA%KJ@}u?jn!rQ2yn6dKG*CYZ_z zY%SLf@X`&Q zc5lXakNQLHL4TY713?k|TIj(`{_e3yZuy^uwp;xOu)bxfcP}}7!C2b4??gmQ7EvPD zt>6{|;;g}DDBweRM`;XzUCy38dDd|tmNtN_7YXPQT0CnhwzSb?9p+oPUf}HX7;OwW z>H%+aQ=Krp_Rs6gMp{dtGYgS`6_?6rY__sygavCZF<%b9;?tWrl{Z1r zK-476XF5t~xiHCsp!T2v+0VH09DcZ|3~yF2Gf~0 zg}JQWmnkA5jZI!F0*XBDR!|uU>_9S}&SmGLhaiM}9F+7>+8l+#2tTv)irx%Shub_o zi4DWT)Xe3@|M9g!dxCASLI(NE6VxT%!v*%x#|k)T0kg{q**G*p9(c@6JN}lS2rfCE ze#nF{f^j-pL^-StblMOEf~@j+j#xJt!`9b1pqKIC=^TJK3B}rq?XyZj#2;;pb)bo7|l^@ycJ@Fy^i6$XogKp*w=hihU8VMdvRlw6C+{5Ps|D z(-`FXqG!=G*@--nA=_4z1EQp$Xz?M3p2|$i=@y&Iss=lI*rq_F3muxrx@3DethJgW zP%xF~=s2tnyPmQFf^dj(%Mb}g$MEShF{B<)%lNQ4mh4Qi9QaLB1s-!PiE!^}jsZnb zv$a>jr+;5-9gnioY_w(L3bI^LL$02n^IWG-U%%WC9vcL}aXcz)bK0^XO{grV zeAMw+20J;>6zzcYE6wLL>SC*Mr50amo}@DnX1H_`&OnZ{%;%?Q^QnV3ABKky?SXVI z#|)KyfR=3yDWX*q**YDEIHpSh!F=i1f4Kk(f#*Pva!{ldZiO1)6QF~)G(Tw1heEud zIkLbZ64HWoWTS6|V0vLr;BW**1eI0o^Qi>4dbKl2v>^g6wAugK+Am;et%HSZw?els zm?u*K46BJf$qN2OC*fCuEx`6>S;B3BqUlKvW9SuFHlSWW7fIn*7N$mlOlP|sCv2@1Qug;(c;n_M)=ccoX6Im9af6}0aFnU@w$%p` zHk8X7LF6;Dl#cRRP~ij^OBXS7GSh8k-r3R-RK6+&T+6v zj=*GROSVcWuwrnNXAy!~m}+yl;7t4* zOpRvEoVk{w19L^_anW?HNLyvwdvrPx>a=hsaRF2NHiZ5HsW^5St>^gJ{-ejB%TSn7 zDnHYoEAm^s+M*9nqzrpOt7!gNxHq5-7B4`1wwq}8U|mL&9IX@Pe{|e{Wr9=b=K3rh z>OXxN2mT`2sYGfg9Va~kf*eZBCi@!+>CkPX!d9-u zrZh|O_R$mxCV%Atn?N}Ou`RKk%fCkX_x%SBu=|MSdrY@tR$xJqgO(OT<3fVeNo-bT zi?geZJeSwDT&gqj1LyowN8R=2Es1g)5YCN&6Gp^5)!k{mRhi6fS|z>GOJ^ZkD`o;* zbF|@19x%)dltb7{|8lH3X}Qo|Y1Ot&J$$Oe?#?O_T-lGx7CHUdkfRvQHnScZ`ZiB# z$Aml5Wp#vq=N@!19|50RX?;hW2IAlW0%eaJu#^rC*oedY&liegu<`8E04N#srGi@7 zO0LdsUQ=F^_Su+aHO4s|o6U^!>;=fa3Hn)sIbb%YDMB37Er<}eHY$ppFn79b8tMoc zBcwgT^+~&4U|_z0URZWLNF!&72M3-|tC0g?rZ^{N>q`18cC*&^yE4_xvKUJu)|g_(`16A^3pC&0qVQqx0rC4{HaSI}0gQ zO{e*w9-(!J6371X@qxJB?3~6NSl-JaAAAnJ9ITGCT1QqM(;}VEerfe8DWC`And$0; zXOa8-8>$#e9XpcfJF~Ri=rjX{5ByHNFKh9OS9c0&&0u{f`#kMH13p5Ea_n$bn&)1N7}v1=6DrD9|L;` zs2$9yVrcUJA%+4zZ@N5&0u+&H)6Pnu9X(6d+bHk`Fbln`)Ka9FnCXSO9xN2t6UyT> z1UU%O>8Kqxp%3B2J`O3*>-}WAtx)l8{E7{+Cx*Wn;y@ak1h!EDg>gk0tZ+abB45yS zhLm=o0atE+FMy^xIZAM5noG7c6c|xi-QF-EVBlD4K)oI901$w~2j|%6U+X)rj5;`o zzM>fRAn|rM7h<1vEE@q_jQJi^RyJrlsv>Nimk|ztqXSDL?;6B^MK?F>``N^ zCnzUiw;WUeEhq9nALIP2uuzEimA{CU7;bmO$&v|e=|H`#GgwBDG2RQEeu}M4CwhQt z=|9p7qy*F=meXFP?bdTYNm-4Jm*;VCj$B8_j*&WMZKRFn@zIo|LDpj@#agD|RAjKX z(mBe6moR(d?Mm|4?HD6H8STPuVP-rA&)hhxMOG-0#r5a~;=0+fk4#zI!F~Z6B*hDX z>r7W@TktCoLuosb4EA{k`djrA^kcZ^gk@+Q zrLWIgCwGk86MGB0$vrTjCw1rbt$B@2Z|CR=RZj{-#aT(j=i(F{6i^%;hklc1AIga# z`Y0EMtao_0%brX)b&359w3!Owla4q#37ZjnUQs1lEUgN^9Jf3 zRNEQ&Wg!y0!9bfaybGoo8UB(tRKrxknI#VYD)vwjdg@y6TqT8n75-`X`{8edzX+@T zEXK&`=1F<;BJ89;UnRL;eFwq;kNMB@U`K(a5$`l z{xkH8(2qjj34JB>xzOK*J{0V3%w@vve1h{&k5ZdGDGwDZa^xO2%QYw5{ko* zuqV_RY6&%l)`x0B{*Wj5>)_9V-w%F0_=Vu7gC7gNFZlN08-ou8Um5(9;PZpOA6&%u z2a3U&pbU4gd* z9tpfU@Up;*0?!HD8@MNMA#g5`3M2xz295@94(t!?33LXw1)2g4ftr98aQT1f|B?S& z{xA7I?f;nnz5c)Q|E2#m{+Igy*#B(*lK&2W-aqXh^Plt&`49SU@Nf6G`ZxO<{55{n zFKNHfeyDv@`=a(K?IYT|wYO@IXb)=lYtPr7rIobXw49dG&S=NAL9I`_UhCGjX^mRF zc9o{6|Dpb?`UCar>OZQVR6nG?OMSEYu=+~%CF=9kXR7z8b81GNP*1BP>S1-ix?62m zx2o5u>(rp?_5IrSQ{Q)eU-5m`_qV?H``+$*gYUJz2YmPWp5uFl?=D}_m-dbOPWg`d z;=UVwyL@fF>wFu1SNr@vxAH6HC(3t}e^Nf9d|Y|I@^mGs zLB35sCr{z4lTVQcJHrI>rkOzAX%H&PJaGDb4Yvs~YDFNL+MF|(OqI4@0lcIEzh$HCA z2`13a<4mBqr!Y~FOUFQ{;ME8x+$u`LOcX`wC=(||=@upqi_#Di=;aY6t{0_2CX%8w zz{FWm>SqF#A7`u4iJ6C_R~p^Kxk~2o+EEP{N!lO1p{3qU*bu zD2UQdCi0@Rg9$KhI}>1BHxsjRsSAXPrgw5epD1-OF(XRtOiYVX8x!bNj0tom$^`n* z$^?3~jR|n4g^9E%MVLTmnu(anh|*RjPRgb0K&Yr;3nzdBo0&j=n}|3InQCNWN|ZJ+ zkrE}H2@J%wM5K`C8YYluBNKSEfrv>wYG4A7))O&-D(ZnLFy zPu4PlCs)%GbgGsTLSDI4LkZ~08crA!rK^}oh*Fq|Goln?VpNoZOgv4L0!*A1B|j6W zKw|>4L1p5pqU0mulvk7#CQ!Ekp+Y2OPC!*&CQubn2YFEyKnDb>0_cE1Rlpn&r-cc~ z0pSrQBhIO(3X>7%u#inc9QCj;8E_8YDog?#*WnYwWW+h#EKEk6!#Bt#A&!bH3^=!{ z!eqcXaa@=TI4Amq$$)brBuoaJ6K%p|z&UYJm;^Yk6R469M@0n;ILGl41I}?FOa`3e zc$)ynb-YbB32{_Zz<_hSR+tPpBLQJD;EW6llL2Sss4y9DMxG)}2Aq*63zGq7@m66n;>4ST$%qqg5hf!}e3LK#}GUD`trHnYeUJbDluBclkcu}K*!KJlPFt)UA6$~w{8w4SXtMxj;$kMuAFtD@=nQ%pI z6^tsawSqyVb&Ft3Y27RsQZ_dVLKN5Lt%4C{^9I3yviUl}c(QrDU^v+s@LDi}suHV8rySIc#RQKV(PU=V2$GGU0?A{aqhY6SyG z%ND`-(Xv@Ee6%zPMvs<=AnjQ7}Sm5`qC@(|VckLER)69X8bp z0teS7T@X09^jg8-pkFH(8|tqS3=Q=g1tUZK2Eo8k-yj$l>ivRYp?P%sqK1Oy?0tHv)F32NMe zfuQCp!8lM877PP5YXze~&DAo2fLbFM0%~dnA%Lr9jbH?*@d*ZiHEx;b{~E#5-*=VZ z+3yPrp8UR$;JNP$3ZDADfZ&<$^9!E%K26ZPcllJA=Dq3@JnLme@T8aV4<*YoX1wb1 z%9!t}%Ohj9t1dU$|0C|#N#UQv`u|G!AHp9E{|zkv*M=Vm-xvPF@KX4Wa6UX89t)p@ z)qhj?`fwM%{J$|=1B?IPLq7|B5BC0NLLUviJM`wzL!no|)_->4ULCRg^t3` z-xt~*+J;Dib)i7W9sE`BN5OBv!vAFOL&0~#zJG1-f#7|?=LYW$-W4naQ^8T#_J@P} zgS&!l!7afJ!P=ld=nniU@Z-R@Vb}jd;G==}1l|^SUEsmMp9G! z9Owz`2($(^1=a=p0k{7b{vY_i=Kq}kZ(+&5&Hsr1LI0olpXa~Vf49E~J3iq*?jQ6Y z@bB@r`?tV~zsfIb|3~|=_HFG;+NWW|zejtU_B!p=+MmLPf2MY~c3w+s3GD=1@O!mR ztwr0UJxL2|vifWFC+fG=FR7nWKMec*P3mjam#Z&SpRF#a^J-Q#)Kls$>Ou8-wM&ht z*Q#q(zv}Y+!uQX`Th+$~k3HIjsyUaoFqIm2JuJ`E9V$UnRdpey;os`A#`6r(mNWlMl;1@=iG_H_7#|&^_K?djHw`HScG= zAM?K3`xfuRu+H!E{-Jltd%O3XcM`VwQSU+TlVO=(=WXy_<&{1E$Ma*)H$DI8`8!zU zZ}+_3^BT{edY@AGtfBA#nJwH}}Q-`ziTf7|^<_b1&SaR0UY zb-pEFtJ^KTTKZ?-65y4|xBHfWuuT5Iw*-&{`GE96-x6?^$@lw~fVCiBCB50V1h@tH za_K|9B_J-7-}fy6beTk6;4YKj^DO~-LB3Ra#J2?WW%3igB>*s!@9`}GgPHsb-x6S$ z$%lPQKw^;hOCR$s0gRdasBZ~?%;fu&CBkHPzw~BhiBK8TN0lYQWl(QWmI#?aeOFl` zYzFl)Wr@%k6y7C(2K8ZOi69!(!^#qYG^md#O9a!P(0c$is(Z2YVc!z4n#q4qmI$uh z8zgWNh>gS-NZ;`-0klCrpST0u2KhYc+rA}0H^}Em-}Eg3y_rOh0pK8iU;4an2^h}g zw|q;0aVC)$P|oBxlqEuQ_ifT6$`au@sIMzagy^8&sw@$vgL;p$M5qo59U)u?_4mpW zAv>tADNBUypuVas5xRr=gtA2V4hn1s!lSx-q_6mv0P;-!lWz$?&m?*TtY`Aez9qmt zlV9>J0r8pqqHhVH&m@K(xX&coM9}ZPQ~IE?MBon!gGKNU>I=#eEdcHuwV0LwP{>D% z0I2sXOSBBQ&(Tv_2teT}Ed`+PlvV@xtn`n{60HZIKCdj%iU10ON^1fryg{o1D3n4A z11P*pON0A}^f_gT76+yWDtcktQs!2A^nB2goVT99w$m7DumHpyxXEa ztt{SUQJ+#4@3g4DR~GNEs81@3w_DWTDT}vR)F+h1d5iiBW$}VVJ*+IwS=8Sui|2_t zMRgP{3Uw4L3U%Zy3U%Zx3U!>bDAX})QK%zpQK%zhQ7Czas9V9c#WYbTAcu?7L>(vU zEKwuU8Jl!!lK@%EE*Q|L1l5As6kBH#iT{OL0KHLD7>4n zD7Ti@qfFe<7GceKR z-b_y!5;xORhQ!VEw3nW?Nbgn__gfV5^;i`0F(kH7K8C~ymBNr1p;8zUBlMIZv5B4n z5?$^ldJ04&3QrjjHxYLMh%Wag$_G3o3QqxtE_c23PGu2jNYp!&MSvku7*${)QOE}< zBnqVf30>|Q>93VV03lI-r7SWI)<|zs78wU?NUj(MYp9e~D<62pI9MaSMOkDVtfBH4 z2WzCaDT|DQH9*cq#=$kjUB*Ely#X9_xqb8oV2~)()?`tr4IoHqsBM!)y7uk|eg@tFK`-=fDR|4dl~=Ajv{Uhi9U*@>@F z76E#cD3k?&9;gRyRu%wyMC}s`06p~U0c8Q0M@hBH0x*xLbIJlRkEmH?0hkBsK_M1^ zd3fPLWdV>!Nq7N}2kKQ}WdV>!6tV&GknL5<0uYaqkPV0j>g5Bz1t1<;|8nV-$^syd z645$99#JQi1wbBA`+N(4JkIzE-vTg?$p^#&Ko4cV+_wPCr5_=xf- z3jjW%+{yxg57hm`$^w9osH4gPfRCsrD+>TVqWZ-G0iUZsEEWj&T>VGI0>PfE->)n% z>h(*1A{GesT>Zmhfk4mI|75X1nCI#r5(@-*u70;zAjEU^-y#+W@Lc^j%L{~eDykr~ zbM<2fh``R(zeQPKtm~IvA{GeiT>X>E0z+ND^kT6hoVZVVk+J}&L*jigWdTqJ>P4ulmrB{7$P0vY>IU(D+dIG5IF34w@3qa{ zCUt7kHl%Kn_L8(F5waiOUyl%{P^Nb5E>lqjmwvwd+c{@b&iKSouvUP4OZ8E zp_iwHi=*hv(Ho@{A4h?g$qgUJW4~NVad9$zs+8j5baY#s;^ORz*GnlrPR5~9ijULL zo>GdBmy0V5QGz^NVTjytaOC!C^ja~+!%^z1af*Y(_DXc2 zl;YvYUy29G!Vv483 zKo`W%aqw&@m7C+LmrAMp97it}Q*(67&lFR0bju7DIXcdLHBNDLROVcCK2Gs(&G9xzeN9U^rra0+Fz}Ew<}&>zvYwN z!==|tJBu%_`NNu9Z#;U#w+p{0+!c+MHvXr7J~Asx9!rq$gXU+cBC=5t%N;{}5P}0_ ze?|~eFy=^t7d-Tzc&R4r_-bg9k`GuYd<)H$0zCnt5tBg-rI@I}t7m~kEsOvIPg?Ai zmFn2U+r*$uELP_#p{iLB;I8CO@#~&G9iTJg^8(H6*=JxXhYwg?V+DeYh?vf)lX0tt zrTVWfYE#$u0Qx+c^boMyBStX={OdxL>4efB92k5YK+A$r1}MUITUjgua4ndXu+xPl zqb?{Ir=rjR4habh>E`E9eZiM=jlgd-&eBIN*WRsXKSJtPKiuIZ{ z0g028sDbc61_Xnk-AjYok$Gmk4a;t&%&z>C+EY4tI#jb&8xeM6alh9gS;pd z>=0Km95~u%$QmDywRf)spCECVa0x_{V;%SZRlb5K{Uh-aV-|DQDt75v^l1su>7ZXs zJJLnDIc36Wr$gglUtSc<#qvoYJ-aRofNDaWj7=cRh45hhRI)C&H`yrVn@5wU5PsdP zfX&*hBr$g@V3b%zLh)1=!NhX~%uzAtWtiyL8=~K1ju#=Z@ z>~7Q#(pVyN~6qrnEFjFd2xi~hI3LV zajiPzHI3v4aSK_9^XfK&gR45r&DQ5Cp+w(IYkwGR-;nhw!{crBzm8AW&M3Te<<#?z zsVSY&OqoTXigk0()3g+_9V0}fW)L7nYD)4Jpc6njf_ZUzP&Ue!LD1l6&Gr+;iebDy zUt8FwAC`dyWh>}AguE&zrY#HU&2!pmQM_5kph{Wn-(d=;%^!7 z4zcV3Oj+&ei3*fH4o|P<5n)xbys~j@u1d%bJ)>D8^rcIjsy7ywK={m5#SSlwRD-3$ z+>(TzOe#6^SGoykUcKyulLGU>z5dbM{ALbk#I=neFh4>k_c?EG>Q=kU})omt(pxlbof znYV;*y<}q&5slrmE9p_UMU+n>2`ujj$o4<_-LHM_Ss!pI%5;0gas8NbG8kfP;2|HT zA5WJ@#DOjyIz3W$PcS zA>LF!ewH&m0Vqi&bQ1EXnuhAf9R`;pL=6$TB5a=H98l}gx+|NKA|gkdYAc@yRJmDX zT*ovPskTT6_l#(*z@)}vOJ+wWLnt!kbK~-Jp288B#`UB%^i=*TkdxMESla?cvm^*9 z7XwdUR7;ktlfqIH7M(M?I?pD+m4e_k=0G1B04)=_^mwh+9b|e*D#UQ>R!!d{yK(<4 zx_=Zs7TR4TCk}>+_oX58$=2vk94$&q12<}C#F3qRuZE8-Ql9IxfOR zPW&dy)@NXd&yJzcj`1haCf|-{pqn^><@;gj>(;oKzSHjA0sBiKsaqvuT59P>7$$;~ zZ0T~GIT-HCcTjBi2b*`0*tUn#o17{3oGhZn{l-^r{AEZ@e$`iuSN^=2B8Y zuxKlz;>6W(m)7W($G1X}W^=RJ zeI!2DlDXps!(4B9aGpsF`%|HYDv(veWDVgmpQyH#AGo>&ALdXv8MrckErE+9m}bUyc3|u2;2> zP0lXPWBq+7&t1YDj7d6tJ3VGtquwohyt;6DzhZh|Vg-r4Kg%0y9#)m?xvycv8=nHpat)ce-togdt z6$<0(RMIDDu_H$aTH7puqY*!|Lol$L6}G4y9jA^~qy9xo@0?mmqK+_At-ZfgeqvpD zA^AjC)W0d&kUk^vQZ;oyD^;`?7Zzwl=sX5?AI4Z=Bg>2rXWD#n0+Q}_+ZEY0Y&M4X zZ(A|z;(@j~0Q^Muggred6|W~VC=@d-_KaU3NG#=aG+FIvC&8wfQ!=KyK<~D99T@3x zkupp|3PZ(>#{=<0$x!FT_R^@-?J?JO9orZzZ~Wa1WBr&EA~BVV(iIf=06 z_zCs^M4Q;sw*C=xbv?)q$Ohjh8x4NuQ^_>_cKpOyZ1=NUf9EJ9^lBk_^r+R3MjV?vZMqEm5dvsPF$>vw5YXbv zmxvb<2~Ui`#8PW;iy+b8XR#+$!sxo1^XemKiLqKen(Lr?`CN>Ll;7-s=yey~ zP|JpQ+68vBvwCN5z*YPD#XBiCs`q51;nBERKo?8n5z$k_3iHD<(aH?1R{19F7&;C| zw*qZDMCa?L=Fpz0wA{XLafaNN!oV7@c0JqVv%~yBqQQtgQ~P6=3^12tXeYMRwg zTPemtuJ1{37O+oD2euV8so8RMO#w#z39&EQcc>=2Q0sHExJK))Fxk<&jEKOOg7hbP z5ZdH~yTb2+T{9IRnb?>D%FaiTx!H<;7Pd+~x?Qr}4@($mF}VwYjGhAr4ru-tx$ra_ z%qV1x()N#Jtue~RJE|LDhW)oE{ey9t4V>ij*vwgUmuD9S_{N&Kz9Y$2*dcqwpg0pn zyS$`F|EVf{VHVE0JuE=H*v8(yk07sHL#+3U?koWQedX-fowJ&aG6`kEC<7np;x<0g zUI%aIf>i%Wwn^7QhThfbT|siO&O>vBN+A3*nYW AGynhq literal 0 HcmV?d00001 diff --git a/cookbooks/redis/doc/license_finder/dependencies.html b/cookbooks/redis/doc/license_finder/dependencies.html new file mode 100644 index 0000000..5df7d0d --- /dev/null +++ b/cookbooks/redis/doc/license_finder/dependencies.html @@ -0,0 +1,2509 @@ + + + + + + + +
    +

    chef-redis

    +
    +
    +
    +

    Dependencies

    + +

    As of November 22, 2014 9:37pm

    + +

    105 total

    + +
      +
    • 65 MIT
    • +
    • 28 Apache 2.0
    • +
    • 5 ruby
    • +
    • 1 Apache 2.0, MIT
    • +
    • 1 BSD
    • +
    • 1 Apache v2
    • +
    • 1 ISC
    • +
    • 1 Artistic 2.0, GPL-2, MIT
    • +
    • 1 GNU GPL v2, MIT, Perl Artistic v2
    • +
    • 1 New BSD
    • +
    +
    +
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + addressable + v2.3.6 +

    +

    URI Implementation

    +

    Addressable is a replacement for the URI implementation that is part of +Ruby's standard library. It more closely conforms to the relevant RFCs and +adds support for IRIs and URI templates. +

    +
    +
    addressable is required by:
    +
    sawyer, ridley, berkshelf
    +
    +
    +
    addressable relies on:
    +
    rake, rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + ast + v2.0.0 +

    +

    A library for working with Abstract Syntax Trees.

    +

    A library for working with Abstract Syntax Trees.

    +
    +
    ast is required by:
    +
    parser
    +
    +
    +
    ast relies on:
    +
    rake, mime-types
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + astrolabe + v1.3.0 +

    +

    An object-oriented AST extension for Parser

    +

    An object-oriented AST extension for Parser

    +
    +
    astrolabe is required by:
    +
    rubocop
    +
    +
    +
    astrolabe relies on:
    +
    parser, bundler, rake, rspec, rubocop, guard-rspec
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + berkshelf + v3.2.1 (default) +

    +

    Manages a Cookbook's, or an Application's, Cookbook dependencies

    +

    Manages a Cookbook's, or an Application's, Cookbook dependencies

    +
    +
    berkshelf relies on:
    +
    addressable, berkshelf-api-client, buff-config, buff-extensions, buff-shell_out, cleanroom, faraday, minitar, retryable, ridley, solve, thor, octokit, celluloid, celluloid-io, chef-zero, rake, rspec, test-kitchen
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + berkshelf-api-client + v1.2.0 +

    +

    API Client for communicating with a Berkshelf API server

    +

    API Client for communicating with a Berkshelf API server

    +
    +
    berkshelf-api-client is required by:
    +
    berkshelf
    +
    +
    +
    berkshelf-api-client relies on:
    +
    faraday, bundler, rake, rspec
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + buff-config + v1.0.1 +

    +

    A simple configuration class

    +

    A simple configuration class

    +
    +
    buff-config is required by:
    +
    ridley, berkshelf
    +
    +
    +
    buff-config relies on:
    +
    varia_model, buff-extensions, buff-ruby_engine, thor, bundler, rake, rspec, guard, guard-rspec
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + buff-extensions + v1.0.0 +

    +

    Extensions to Core Ruby classes

    +

    Extensions to Core Ruby classes

    +
    +
    buff-extensions is required by:
    +
    varia_model, buff-config, ridley, berkshelf
    +
    +
    +
    buff-extensions relies on:
    +
    buff-ruby_engine, thor, bundler, rake, rspec, guard, guard-rspec
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + buff-ignore + v1.1.1 +

    +

    A Ruby library for parsing lists of files and applying pattern matching exclusion (such as .gitignore)

    +

    Parse ignore files with Ruby

    +
    +
    buff-ignore is required by:
    +
    ridley
    +
    +
    +
    buff-ignore relies on:
    +
    bundler, rake, rspec
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + buff-ruby_engine + v0.1.0 +

    +

    Buff up your code with a mixin for querying the platform running Ruby

    +

    A mixin for querying the platform running Ruby

    +
    +
    buff-ruby_engine is required by:
    +
    buff-extensions, varia_model, buff-config, buff-shell_out, ridley
    +
    +
    +
    buff-ruby_engine relies on:
    +
    thor, bundler, rake, rspec, guard, guard-rspec
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + buff-shell_out + v0.2.0 +

    +

    Buff up your code with a mixin for issuing shell commands and collecting the output

    +

    A mixin for issuing shell commands and collecting the output

    +
    +
    buff-shell_out is required by:
    +
    ridley, berkshelf
    +
    +
    +
    buff-shell_out relies on:
    +
    buff-ruby_engine, thor, bundler, rake, rspec, guard, guard-rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + bundler + v1.7.6 +

    +

    +

    +
    +
    bundler is required by:
    +
    parser, astrolabe, faraday, berkshelf-api-client, buff-extensions, varia_model, buff-config, buff-ruby_engine, buff-shell_out, cleanroom, octokit, retryable, buff-ignore, semverse, dep-selector-libgecode, thor, busser, busser-serverspec, libyajl2, wmi-lite, pry, rspec-support, multi_json, gherkin, rb-fsevent, listen, guard-rspec, multi_xml, test-kitchen, license_finder, powerpack, rainbow, rspec-its, rubocop, specinfra, serverspec
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + busser + v0.6.0 +

    +

    Kitchen Busser - Runs tests for projects in test-kitchen

    +

    Kitchen Busser - Runs tests for projects in test-kitchen

    +
    +
    busser is required by:
    +
    busser-serverspec
    +
    +
    +
    busser relies on:
    +
    thor, chef, bundler, rake
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + busser-serverspec + v0.5.3 (integration) +

    +

    A Busser runner plugin for Serverspec

    +

    A Busser runner plugin for Serverspec

    +
    +
    busser-serverspec relies on:
    +
    busser, serverspec, bundler, rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + celluloid + v0.16.0 +

    +

    Actor-based concurrent object framework for Ruby

    +

    Celluloid enables people to build concurrent programs out of concurrent objects just as easily as they build sequential programs out of sequential objects

    +
    +
    celluloid is required by:
    +
    celluloid-io, ridley, berkshelf, listen
    +
    +
    +
    celluloid relies on:
    +
    timers, rake, rspec, guard-rspec, rubocop
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + celluloid-io + v0.16.1 +

    +

    Celluloid::IO allows you to monitor multiple IO objects within a Celluloid actor

    +

    Evented IO for Celluloid actors

    +
    +
    celluloid-io is required by:
    +
    ridley, berkshelf, listen
    +
    +
    +
    celluloid-io relies on:
    +
    celluloid, nio4r, rake, rspec, guard-rspec, rb-fsevent
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + chef + v11.16.4 (default) +

    +

    A systems integration framework, built to bring the benefits of configuration management to your entire infrastructure.

    +

    A systems integration framework, built to bring the benefits of configuration management to your entire infrastructure.

    +
    +
    chef is required by:
    +
    busser, ohai, chefspec
    +
    +
    +
    chef relies on:
    +
    mixlib-config, mixlib-cli, mixlib-log, mixlib-authentication, mixlib-shellout, ohai, rest-client, mime-types, ffi-yajl, net-ssh, net-ssh-multi, highline, erubis, diff-lcs, chef-zero, pry, plist, rack, rake, rspec-core, rspec-expectations, rspec-mocks
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + chef-zero + v2.2.1 +

    +

    Self-contained, easy-setup, fast-start in-memory Chef server for testing and solo setup purposes

    +

    Self-contained, easy-setup, fast-start in-memory Chef server for testing and solo setup purposes

    +
    +
    chef-zero is required by:
    +
    berkshelf, chef
    +
    +
    +
    chef-zero relies on:
    +
    mixlib-log, hashie, ffi-yajl, rack, rake, rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + chefspec + v4.1.1 (default) +

    +

    Write RSpec examples and generate coverage reports for Chef recipes!

    +

    ChefSpec is a unit testing and resource coverage (code coverage) framework for testing Chef cookbooks ChefSpec makes it easy to write examples and get fast feedback on cookbook changes without the need for virtual machines or cloud servers.

    +
    +
    chefspec relies on:
    +
    chef, fauxhai, rspec, rake
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + cleanroom + v1.0.0 +

    +

    (More) safely evaluate Ruby DSLs with cleanroom

    +

    Ruby is an excellent programming language for creating and managing custom DSLs, but how can you securely evaluate a DSL while explicitly controlling the methods exposed to the user? Our good friends instance_eval and instance_exec are great, but they expose all methods - public, protected, and private - to the user. Even worse, they expose the ability to accidentally or intentionally alter the behavior of the system! The cleanroom pattern is a safer, more convenient, Ruby-like approach for limiting the information exposed by a DSL while giving users the ability to write awesome code!

    +
    +
    cleanroom is required by:
    +
    berkshelf
    +
    +
    +
    cleanroom relies on:
    +
    rspec, bundler, rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + coderay + v1.1.0 +

    +

    Fast syntax highlighting for selected languages.

    +

    Fast and easy syntax highlighting for selected languages, written in Ruby. Comes with RedCloth integration and LOC counter.

    +
    +
    coderay is required by:
    +
    pry, rspec-core
    +
    +
    +
    +
    +

    + MIT, Apache 2.0 + whitelisted + +

    +
    +

    + dep-selector-libgecode + v1.0.2 +

    +

    Installs a vendored copy of Gecode suitable for use with dep-selector

    +

    Installs a vendored copy of Gecode suitable for use with dep-selector

    +
    +
    dep-selector-libgecode is required by:
    +
    dep_selector
    +
    +
    +
    dep-selector-libgecode relies on:
    +
    bundler, rake
    +
    +
    +
    +
    +

    + Apache v2 + whitelisted + +

    +
    +

    + dep_selector + v1.0.3 +

    +

    Given packages, versions, and a dependency graph, find a valid assignment of package versions

    +

    Given packages, versions, and a dependency graph, find a valid assignment of package versions

    +
    +
    dep_selector is required by:
    +
    solve
    +
    +
    +
    dep_selector relies on:
    +
    ffi, dep-selector-libgecode, rake, rspec, solve
    +
    +
    +
    +
    +

    + MIT, Perl Artistic v2, GNU GPL v2 + whitelisted + +

    +
    +

    + diff-lcs + v1.2.5 +

    +

    Diff::LCS computes the difference between two Enumerable sequences using the McIlroy-Hunt longest common subsequence (LCS) algorithm

    +

    Diff::LCS computes the difference between two Enumerable sequences using the +McIlroy-Hunt longest common subsequence (LCS) algorithm. It includes utilities +to create a simple HTML diff output format and a standard diff-like tool. + +This is release 1.2.4, fixing a bug introduced after diff-lcs 1.1.3 that did +not properly prune common sequences at the beginning of a comparison set. +Thanks to Paul Kunysch for fixing this issue. + +Coincident with the release of diff-lcs 1.2.3, we reported an issue with +Rubinius in 1.9 mode +({rubinius/rubinius#2268}[https://github.com/rubinius/rubinius/issues/2268]). +We are happy to report that this issue has been resolved.

    +
    +
    diff-lcs is required by:
    +
    chef, rspec-expectations
    +
    +
    +
    diff-lcs relies on:
    +
    rake, rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + erubis + v2.7.0 +

    +

    a fast and extensible eRuby implementation which supports multi-language

    +

    Erubis is an implementation of eRuby and has the following features: + + * Very fast, almost three times faster than ERB and about 10% faster than eruby. + * Multi-language support (Ruby/PHP/C/Java/Scheme/Perl/Javascript) + * Auto escaping support + * Auto trimming spaces around '<% %>' + * Embedded pattern changeable (default '<% %>') + * Enable to handle Processing Instructions (PI) as embedded pattern (ex. '') + * Context object available and easy to combine eRuby template with YAML datafile + * Print statement available + * Easy to extend and customize in subclass + * Ruby on Rails support +

    +
    +
    erubis is required by:
    +
    ridley, chef, foodcritic
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + faraday + v0.9.0 +

    +

    HTTP/REST API client library.

    +

    +
    +
    faraday is required by:
    +
    berkshelf-api-client, sawyer, ridley, berkshelf
    +
    +
    +
    faraday relies on:
    +
    multipart-post, bundler
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + fauxhai + v2.2.0 +

    +

    Fauxhai provides an easy way to mock out your ohai data for testing with chefspec!

    +

    Easily mock out ohai data

    +
    +
    fauxhai is required by:
    +
    chefspec
    +
    +
    +
    fauxhai relies on:
    +
    net-ssh, ohai, rake
    +
    +
    +
    +
    +

    + BSD + whitelisted + +

    +
    +

    + ffi + v1.9.6 +

    +

    Ruby FFI

    +

    Ruby FFI library

    +
    +
    ffi is required by:
    +
    dep_selector, libyajl2, ffi-yajl, ohai, rb-inotify
    +
    +
    +
    ffi relies on:
    +
    rake, rspec
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + ffi-yajl + v1.3.0 +

    +

    Ruby FFI wrapper around YAJL 2.x

    +

    Ruby FFI wrapper around YAJL 2.x

    +
    +
    ffi-yajl is required by:
    +
    chef-zero, ohai, chef
    +
    +
    +
    ffi-yajl relies on:
    +
    rake, rspec, pry, mime-types, ffi, libyajl2
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + foodcritic + v4.0.0 (default) +

    +

    foodcritic-4.0.0

    +

    Lint tool for Opscode Chef cookbooks.

    +
    +
    foodcritic relies on:
    +
    gherkin, nokogiri, rake, treetop, yajl-ruby, erubis, rufus-lru
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + formatador + v0.2.5 +

    +

    Ruby STDOUT text formatting

    +

    STDOUT text formatting

    +
    +
    formatador is required by:
    +
    guard
    +
    +
    +
    formatador relies on:
    +
    rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + gherkin + v2.12.2 +

    +

    gherkin-2.12.2

    +

    A fast Gherkin lexer/parser based on the Ragel State Machine Compiler.

    +
    +
    gherkin is required by:
    +
    foodcritic
    +
    +
    +
    gherkin relies on:
    +
    multi_json, rake, bundler, rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + guard + v2.8.2 +

    +

    Guard keeps an eye on your file modifications

    +

    Guard is a command line tool to easily handle events on file system modifications.

    +
    +
    guard is required by:
    +
    buff-extensions, varia_model, buff-config, buff-ruby_engine, buff-shell_out, guard-rspec
    +
    +
    +
    guard relies on:
    +
    thor, listen, pry, lumberjack, formatador
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + guard-rspec + v4.3.1 (integration) +

    +

    Guard gem for RSpec

    +

    Guard::RSpec automatically run your specs (much like autotest).

    +
    +
    guard-rspec is required by:
    +
    astrolabe, buff-extensions, varia_model, buff-config, buff-ruby_engine, buff-shell_out, celluloid, celluloid-io, rb-fsevent
    +
    +
    +
    guard-rspec relies on:
    +
    guard, rspec, bundler, rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + hashie + v2.1.2 +

    +

    Your friendly neighborhood hash library.

    +

    Hashie is a collection of classes and mixins that make hashes more powerful.

    +
    +
    hashie is required by:
    +
    varia_model, ridley, chef-zero
    +
    +
    +
    hashie relies on:
    +
    rake, rspec
    +
    +
    +
    +
    +

    + ruby + whitelisted + +

    +
    +

    + highline + v1.6.21 +

    +

    HighLine is a high-level command-line IO library.

    +

    A high-level IO library that provides validation, type conversion, and more for +command-line interfaces. HighLine also includes a complete menu system that can +crank out anything from simple list selection to complete shells with just +minutes of work. +

    +
    +
    highline is required by:
    +
    chef
    +
    +
    +
    +
    +

    + ISC + whitelisted + +

    +
    +

    + hitimes + v1.2.2 +

    +

    Hitimes is a fast, high resolution timer library for recording performance metrics. It uses the appropriate low method calls for each system to get the highest granularity time increments possible.

    +

    Hitimes is a fast, high resolution timer library for recording performance metrics. It uses the appropriate low method calls for each system to get the highest granularity time increments possible. It currently supports any of the following systems: * any system with the POSIX call `clock_gettime()` * Mac OS X * Windows * JRuby Using Hitimes can be faster than using a series of `Time.new` calls, and it will have a much higher granularity. It is definitely faster than using `Process.times`.

    +
    +
    hitimes is required by:
    +
    timers
    +
    +
    +
    hitimes relies on:
    +
    rake, json
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + httparty + v0.13.3 +

    +

    Makes http fun! Also, makes consuming restful web services dead easy.

    +

    Makes http fun! Also, makes consuming restful web services dead easy.

    +
    +
    httparty is required by:
    +
    license_finder
    +
    +
    +
    httparty relies on:
    +
    json, multi_xml
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + ipaddress + v0.8.0 +

    +

    IPv4/IPv6 addresses manipulation library

    +

    IPAddress is a Ruby library designed to make manipulation + of IPv4 and IPv6 addresses both powerful and simple. It mantains + a layer of compatibility with Ruby's own IPAddr, while + addressing many of its issues. +

    +
    +
    ipaddress is required by:
    +
    ohai
    +
    +
    +
    +
    +

    + ruby + whitelisted + +

    +
    +

    + json + v1.8.1 +

    +

    This json is bundled with Ruby

    +

    +
    +
    json is required by:
    +
    hitimes, ridley, yajl-ruby, httparty
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + kitchen-vagrant + v0.15.0 (integration) +

    +

    Kitchen::Driver::Vagrant - A Vagrant Driver for Test Kitchen.

    +

    Kitchen::Driver::Vagrant - A Vagrant Driver for Test Kitchen.

    +
    +
    kitchen-vagrant relies on:
    +
    test-kitchen
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + libyajl2 + v1.2.0 +

    +

    Installs a vendored copy of libyajl2 for distributions which lack it

    +

    Installs a vendored copy of libyajl2 for distributions which lack it

    +
    +
    libyajl2 is required by:
    +
    ffi-yajl
    +
    +
    +
    libyajl2 relies on:
    +
    bundler, rake, mime-types, rspec, ffi
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + license_finder + v1.2 (default) +

    +

    Audit the OSS licenses of your application's dependencies.

    +

    LicenseFinder works with your package managers to find + dependencies, detect the licenses of the packages in them, compare + those licenses against a user-defined whitelist, and give you an + actionable exception report. +

    +
    +
    license_finder relies on:
    +
    bundler, sequel, thor, httparty, xml-simple, sqlite3, rake, rspec-its, pry, rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + listen + v2.8.0 +

    +

    Listen to file modifications

    +

    The Listen gem listens to file modifications and notifies you about the changes. Works everywhere!

    +
    +
    listen is required by:
    +
    guard
    +
    +
    +
    listen relies on:
    +
    celluloid, rb-fsevent, rb-inotify, bundler, celluloid-io, rake, rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + lumberjack + v1.0.9 +

    +

    A simple, powerful, and very fast logging utility that can be a drop in replacement for Logger or ActiveSupport::BufferedLogger.

    +

    A simple, powerful, and very fast logging utility that can be a drop in replacement for Logger or ActiveSupport::BufferedLogger. Provides support for automatically rolling log files even with multiple processes writing the same log file.

    +
    +
    lumberjack is required by:
    +
    guard
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + method_source + v0.8.2 +

    +

    retrieve the sourcecode for a method

    +

    retrieve the sourcecode for a method

    +
    +
    method_source is required by:
    +
    pry
    +
    +
    +
    method_source relies on:
    +
    rake
    +
    +
    +
    +
    +

    + MIT, Artistic 2.0, GPL-2 + whitelisted + +

    +
    +

    + mime-types + v1.25.1 +

    +

    This library allows for the identification of a file's likely MIME content type

    +

    This library allows for the identification of a file's likely MIME content +type. This is release 1.25.1, fixing an issue with priority comparison for +mime-types 1.x. The current release is 2.0, which only supports Ruby 1.9 or +later. + +Release 1.25.1 contains all features of 1.25, including the experimental +caching and lazy loading functionality. The caching and lazy loading features +were initially implemented by Greg Brockman (gdb). As these features are +experimental, they are disabled by default and must be enabled through the use +of environment variables. The cache is invalidated on a per-version basis; the +cache for version 1.25 will not be reused for any later version. + +To use lazy loading, set the environment variable +RUBY_MIME_TYPES_LAZY_LOAD+ +to any value other than 'false'. When using lazy loading, the initial startup +of MIME::Types is around 12–25× faster than normal startup (on my system, +normal startup is about 90 ms; lazy startup is about 4 ms). This isn't +generally useful, however, as the MIME::Types database has not been loaded. +Lazy startup and load is just *slightly* faster—around 1 ms. The real advantage +comes from using the cache. + +To enable the cache, set the environment variable +RUBY_MIME_TYPES_CACHE+ to a +filename where MIME::Types will have read-write access. The first time a new +version of MIME::Types is run using this file, it will be created, taking a +little longer than normal. Subsequent loads using the same cache file will be +approximately 3½× faster (25 ms) than normal loads. This can be combined with ++RUBY_MIME_TYPES_LAZY_LOAD+, but this is *not* recommended in a multithreaded +or multiprocess environment where all threads or processes will be using the +same cache file. + +As the caching interface is still experimental, the only values cached are the +default MIME::Types database, not any custom MIME::Types added by users. + +MIME types are used in MIME-compliant communications, as in e-mail or HTTP +traffic, to indicate the type of content which is transmitted. MIME::Types +provides the ability for detailed information about MIME entities (provided as +a set of MIME::Type objects) to be determined and used programmatically. There +are many types defined by RFCs and vendors, so the list is long but not +complete; don't hesitate to ask to add additional information. This library +follows the IANA collection of MIME types (see below for reference). + +MIME::Types for Ruby was originally based on MIME::Types for Perl by Mark +Overmeer, copyright 2001 - 2009. + +MIME::Types is built to conform to the MIME types of RFCs 2045 and 2231. It +tracks the {IANA registry}[http://www.iana.org/assignments/media-types/] +({ftp}[ftp://ftp.iana.org/assignments/media-types]) with some unofficial types +added from the {LTSW collection}[http://www.ltsw.se/knbase/internet/mime.htp] +and added by the users of MIME::Types.

    +
    +
    mime-types is required by:
    +
    ast, parser, libyajl2, ffi-yajl, ohai, rest-client, chef
    +
    +
    +
    mime-types relies on:
    +
    rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + mini_portile + v0.6.1 +

    +

    Simplistic port-like solution for developers

    +

    Simplistic port-like solution for developers. It provides a standard and simplified way to compile against dependency libraries without messing up your system.

    +
    +
    mini_portile is required by:
    +
    nokogiri, sqlite3
    +
    +
    +
    +
    +

    + ruby + whitelisted + +

    +
    +

    + minitar + v0.5.4 +

    +

    Provides POSIX tarchive management from Ruby programs.

    +

    Archive::Tar::Minitar is a pure-Ruby library and command-line utility that provides the ability to deal with POSIX tar(1) archive files. The implementation is based heavily on Mauricio Ferna'ndez's implementation in rpa-base, but has been reorganised to promote reuse in other projects. Antoine Toulme forked the original project on rubyforge to place it on github, under http://www.github.com/atoulme/minitar

    +
    +
    minitar is required by:
    +
    berkshelf
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + mixlib-authentication + v1.3.0 +

    +

    Mixes in simple per-request authentication

    +

    Mixes in simple per-request authentication

    +
    +
    mixlib-authentication is required by:
    +
    ridley, chef
    +
    +
    +
    mixlib-authentication relies on:
    +
    mixlib-log
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + mixlib-cli + v1.5.0 +

    +

    A simple mixin for CLI interfaces, including option parsing

    +

    A simple mixin for CLI interfaces, including option parsing

    +
    +
    mixlib-cli is required by:
    +
    ohai, chef
    +
    +
    +
    mixlib-cli relies on:
    +
    rake, rspec
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + mixlib-config + v2.1.0 +

    +

    A class based configuration library

    +

    A class based configuration library

    +
    +
    mixlib-config is required by:
    +
    ohai, chef
    +
    +
    +
    mixlib-config relies on:
    +
    rake, rspec
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + mixlib-log + v1.6.0 +

    +

    A gem that provides a simple mixin for log functionality

    +

    +
    +
    mixlib-log is required by:
    +
    mixlib-authentication, chef-zero, ohai, chef
    +
    +
    +
    mixlib-log relies on:
    +
    rake, rspec
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + mixlib-shellout + v1.6.0 +

    +

    Run external commands on Unix or Windows

    +

    Run external commands on Unix or Windows

    +
    +
    mixlib-shellout is required by:
    +
    ohai, chef, test-kitchen
    +
    +
    +
    mixlib-shellout relies on:
    +
    rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + multi_json + v1.10.1 +

    +

    A common interface to multiple JSON libraries.

    +

    A common interface to multiple JSON libraries, including Oj, Yajl, the JSON gem (with C-extensions), the pure-Ruby JSON gem, NSJSONSerialization, gson.rb, JrJackson, and OkJson.

    +
    +
    multi_json is required by:
    +
    gherkin, serverspec
    +
    +
    +
    multi_json relies on:
    +
    bundler
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + multi_xml + v0.5.5 +

    +

    A generic swappable back-end for XML parsing

    +

    Provides swappable XML backends utilizing LibXML, Nokogiri, Ox, or REXML.

    +
    +
    multi_xml is required by:
    +
    httparty
    +
    +
    +
    multi_xml relies on:
    +
    bundler
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + multipart-post + v2.0.0 +

    +

    A multipart form post accessory for Net::HTTP.

    +

    Use with Net::HTTP to do multipart form posts. IO values that have #content_type, #original_filename, and #local_path will be posted as a binary file.

    +
    +
    multipart-post is required by:
    +
    faraday
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + net-http-persistent + v2.9.4 +

    +

    Manages persistent connections using Net::HTTP plus a speed fix for Ruby 1.8

    +

    Manages persistent connections using Net::HTTP plus a speed fix for Ruby 1.8. +It's thread-safe too! + +Using persistent HTTP connections can dramatically increase the speed of HTTP. +Creating a new HTTP connection for every request involves an extra TCP +round-trip and causes TCP congestion avoidance negotiation to start over. + +Net::HTTP supports persistent connections with some API methods but does not +handle reconnection gracefully. Net::HTTP::Persistent supports reconnection +and retry according to RFC 2616.

    +
    +
    net-http-persistent is required by:
    +
    ridley
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + net-scp + v1.2.1 +

    +

    A pure Ruby implementation of the SCP client protocol

    +

    A pure Ruby implementation of the SCP client protocol

    +
    +
    net-scp is required by:
    +
    test-kitchen, specinfra
    +
    +
    +
    net-scp relies on:
    +
    net-ssh
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + net-ssh + v2.9.1 +

    +

    Net::SSH: a pure-Ruby implementation of the SSH2 client protocol.

    +

    Net::SSH: a pure-Ruby implementation of the SSH2 client protocol. It allows you to write programs that invoke and interact with processes on remote servers, via SSH2.

    +
    +
    net-ssh is required by:
    +
    net-ssh-gateway, net-ssh-multi, chef, fauxhai, net-scp, test-kitchen, specinfra
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + net-ssh-gateway + v1.2.0 +

    +

    A simple library to assist in establishing tunneled Net::SSH connections

    +

    A simple library to assist in establishing tunneled Net::SSH connections

    +
    +
    net-ssh-gateway is required by:
    +
    net-ssh-multi
    +
    +
    +
    net-ssh-gateway relies on:
    +
    net-ssh
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + net-ssh-multi + v1.2.0 +

    +

    Control multiple Net::SSH connections via a single interface.

    +

    Control multiple Net::SSH connections via a single interface.

    +
    +
    net-ssh-multi is required by:
    +
    chef
    +
    +
    +
    net-ssh-multi relies on:
    +
    net-ssh, net-ssh-gateway
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + nio4r + v1.0.1 +

    +

    NIO provides a high performance selector API for monitoring IO objects

    +

    New IO for Ruby

    +
    +
    nio4r is required by:
    +
    celluloid-io
    +
    +
    +
    nio4r relies on:
    +
    rake, rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + nokogiri + v1.6.4.1 +

    +

    Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser

    +

    Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser. Among Nokogiri's +many features is the ability to search documents via XPath or CSS3 selectors. + +XML is like violence - if it doesn’t solve your problems, you are not using +enough of it.

    +
    +
    nokogiri is required by:
    +
    rspec-core, foodcritic
    +
    +
    +
    nokogiri relies on:
    +
    mini_portile, rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + octokit + v3.5.2 +

    +

    Ruby toolkit for working with the GitHub API

    +

    Simple wrapper for the GitHub API

    +
    +
    octokit is required by:
    +
    berkshelf
    +
    +
    +
    octokit relies on:
    +
    bundler, sawyer
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + ohai + v7.4.0 +

    +

    Ohai profiles your system and emits JSON

    +

    Ohai profiles your system and emits JSON

    +
    +
    ohai is required by:
    +
    chef, fauxhai
    +
    +
    +
    ohai relies on:
    +
    mime-types, systemu, ffi-yajl, mixlib-cli, mixlib-config, mixlib-log, mixlib-shellout, ipaddress, wmi-lite, ffi, rake, rspec-core, rspec-expectations, rspec-mocks, chef
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + parser + v2.2.0.pre.8 +

    +

    A Ruby parser written in pure Ruby.

    +

    A Ruby parser written in pure Ruby.

    +
    +
    parser is required by:
    +
    astrolabe, rubocop
    +
    +
    +
    parser relies on:
    +
    ast, slop, bundler, rake, mime-types, rest-client
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + plist + v3.1.0 +

    +

    All-purpose Property List manipulation library.

    +

    Plist is a library to manipulate Property List files, also known as plists. It can parse plist files into native Ruby data structures as well as generating new plist files from your Ruby objects. +

    +
    +
    plist is required by:
    +
    chef
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + polyglot + v0.3.5 +

    +

    Augment 'require' to load non-Ruby file types

    +

    +The Polyglot library allows a Ruby module to register a loader +for the file type associated with a filename extension, and it +augments 'require' to find and load matching files.

    +
    +
    polyglot is required by:
    +
    treetop
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + powerpack + v0.0.9 +

    +

    A few useful extensions to core Ruby classes.

    +

    A few useful extensions to core Ruby classes.

    +
    +
    powerpack is required by:
    +
    rubocop
    +
    +
    +
    powerpack relies on:
    +
    bundler, rake, rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + pry + v0.10.1 +

    +

    An IRB alternative and runtime developer console

    +

    An IRB alternative and runtime developer console

    +
    +
    pry is required by:
    +
    ffi-yajl, wmi-lite, chef, guard, license_finder
    +
    +
    +
    pry relies on:
    +
    coderay, slop, method_source, bundler
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rack + v1.5.2 +

    +

    a modular Ruby webserver interface

    +

    Rack provides a minimal, modular and adaptable interface for developing +web applications in Ruby. By wrapping HTTP requests and responses in +the simplest way possible, it unifies and distills the API for web +servers, web frameworks, and software in between (the so-called +middleware) into a single method call. + +Also see http://rack.github.com/. +

    +
    +
    rack is required by:
    +
    chef-zero, chef
    +
    +
    +
    rack relies on:
    +
    rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rainbow + v2.0.0 +

    +

    Colorize printed text on ANSI terminals

    +

    Colorize printed text on ANSI terminals

    +
    +
    rainbow is required by:
    +
    rubocop
    +
    +
    +
    rainbow relies on:
    +
    bundler, rake, rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rake + v10.3.2 (default) +

    +

    Rake is a Make-like program implemented in Ruby

    +

    Rake is a Make-like program implemented in Ruby. Tasks and dependencies are +specified in standard Ruby syntax. + +Rake has the following features: + +* Rakefiles (rake's version of Makefiles) are completely defined in + standard Ruby syntax. No XML files to edit. No quirky Makefile + syntax to worry about (is that a tab or a space?) + +* Users can specify tasks with prerequisites. + +* Rake supports rule patterns to synthesize implicit tasks. + +* Flexible FileLists that act like arrays but know about manipulating + file names and paths. + +* A library of prepackaged tasks to make building rakefiles easier. For example, + tasks for building tarballs and publishing to FTP or SSH sites. (Formerly + tasks for building RDoc and Gems were included in rake but they're now + available in RDoc and RubyGems respectively.) + +* Supports parallel execution of tasks.

    +
    +
    rake is required by:
    +
    addressable, ast, slop, parser, astrolabe, berkshelf-api-client, buff-extensions, hashie, varia_model, buff-config, buff-ruby_engine, buff-shell_out, hitimes, timers, celluloid, nio4r, celluloid-io, cleanroom, buff-ignore, mixlib-log, semverse, dep-selector-libgecode, ffi, dep_selector, berkshelf, busser, busser-serverspec, libyajl2, ffi-yajl, rack, chef-zero, diff-lcs, mime-types, mixlib-cli, mixlib-config, wmi-lite, ohai, method_source, chef, fauxhai, rspec-support, rspec-core, rspec-expectations, rspec-mocks, chefspec, gherkin, nokogiri, rufus-lru, treetop, foodcritic, formatador, listen, guard-rspec, test-kitchen, license_finder, powerpack, rainbow, rspec-its, rubocop, specinfra, serverspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rb-fsevent + v0.9.4 +

    +

    Very simple & usable FSEvents API

    +

    FSEvents API with Signals catching (without RubyCocoa)

    +
    +
    rb-fsevent is required by:
    +
    celluloid-io, listen
    +
    +
    +
    rb-fsevent relies on:
    +
    bundler, rspec, guard-rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rb-inotify + v0.9.5 +

    +

    A Ruby wrapper for Linux's inotify, using FFI

    +

    A Ruby wrapper for Linux's inotify, using FFI

    +
    +
    rb-inotify is required by:
    +
    listen
    +
    +
    +
    rb-inotify relies on:
    +
    ffi
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rest-client + v1.6.7 +

    +

    Simple HTTP and REST client for Ruby, inspired by microframework syntax for specifying actions.

    +

    A simple HTTP and REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete.

    +
    +
    rest-client is required by:
    +
    parser, chef
    +
    +
    +
    rest-client relies on:
    +
    mime-types, rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + retryable + v1.3.6 +

    +

    Kernel#retryable, allow for retrying of code blocks.

    +

    Kernel#retryable, allow for retrying of code blocks.

    +
    +
    retryable is required by:
    +
    ridley, berkshelf
    +
    +
    +
    retryable relies on:
    +
    bundler
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + ridley + v4.1.0 +

    +

    A reliable Chef API client with a clean syntax

    +

    A reliable Chef API client with a clean syntax

    +
    +
    ridley is required by:
    +
    berkshelf
    +
    +
    +
    ridley relies on:
    +
    addressable, varia_model, buff-config, buff-extensions, buff-ignore, buff-shell_out, celluloid, celluloid-io, erubis, faraday, hashie, json, mixlib-authentication, net-http-persistent, retryable, semverse, buff-ruby_engine
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rspec + v3.1.0 +

    +

    rspec-3.1.0

    +

    BDD for Ruby

    +
    +
    rspec is required by:
    +
    addressable, astrolabe, berkshelf-api-client, buff-extensions, hashie, varia_model, buff-config, buff-ruby_engine, buff-shell_out, timers, celluloid, nio4r, celluloid-io, cleanroom, buff-ignore, mixlib-log, ffi, dep_selector, berkshelf, libyajl2, ffi-yajl, chef-zero, diff-lcs, mixlib-cli, mixlib-config, mixlib-shellout, wmi-lite, rest-client, chefspec, gherkin, rufus-lru, treetop, yajl-ruby, rb-fsevent, listen, guard-rspec, license_finder, powerpack, rainbow, ruby-progressbar, rubocop, specinfra, serverspec
    +
    +
    +
    rspec relies on:
    +
    rspec-core, rspec-expectations, rspec-mocks
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rspec-core + v3.1.7 +

    +

    rspec-core-3.1.7

    +

    BDD for Ruby. RSpec runner and example groups.

    +
    +
    rspec-core is required by:
    +
    ohai, chef, rspec, rspec-its
    +
    +
    +
    rspec-core relies on:
    +
    rspec-support, rake, nokogiri, coderay
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rspec-expectations + v3.1.2 +

    +

    rspec-expectations-3.1.2

    +

    rspec-expectations provides a simple, readable API to express expected outcomes of a code example.

    +
    +
    rspec-expectations is required by:
    +
    ohai, chef, rspec, rspec-its
    +
    +
    +
    rspec-expectations relies on:
    +
    rspec-support, diff-lcs, rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rspec-its + v1.1.0 +

    +

    Provides "its" method formally part of rspec-core

    +

    RSpec extension gem for attribute matching

    +
    +
    rspec-its is required by:
    +
    license_finder, specinfra, serverspec
    +
    +
    +
    rspec-its relies on:
    +
    rspec-core, rspec-expectations, bundler, rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rspec-mocks + v3.1.3 +

    +

    rspec-mocks-3.1.3

    +

    RSpec's 'test double' framework, with support for stubbing and mocking

    +
    +
    rspec-mocks is required by:
    +
    ohai, chef, rspec
    +
    +
    +
    rspec-mocks relies on:
    +
    rspec-support, rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rspec-support + v3.1.2 +

    +

    rspec-support-3.1.2

    +

    Support utilities for RSpec gems

    +
    +
    rspec-support is required by:
    +
    rspec-core, rspec-expectations, rspec-mocks
    +
    +
    +
    rspec-support relies on:
    +
    bundler, rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rubocop + v0.27.1 (default) +

    +

    Automatic Ruby code style checking tool.

    +

    Automatic Ruby code style checking tool. + Aims to enforce the community-driven Ruby Style Guide. +

    +
    +
    rubocop is required by:
    +
    astrolabe, celluloid
    +
    +
    +
    rubocop relies on:
    +
    rainbow, parser, powerpack, astrolabe, ruby-progressbar, rake, rspec, bundler
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + ruby-progressbar + v1.7.0 +

    +

    Ruby/ProgressBar is a flexible text progress bar library for Ruby.

    +

    Ruby/ProgressBar is an extremely flexible text progress bar library for Ruby. +The output can be customized with a flexible formatting system including: +percentage, bars of various formats, elapsed time and estimated time remaining. +

    +
    +
    ruby-progressbar is required by:
    +
    rubocop
    +
    +
    +
    ruby-progressbar relies on:
    +
    rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + rufus-lru + v1.0.5 +

    +

    A Hash with a max size, controlled by a LRU mechanism

    +

    LruHash class, a Hash with a max size, controlled by a LRU mechanism

    +
    +
    rufus-lru is required by:
    +
    foodcritic
    +
    +
    +
    rufus-lru relies on:
    +
    rake, rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + safe_yaml + v1.0.4 +

    +

    SameYAML provides an alternative implementation of YAML.load suitable for accepting user input in Ruby applications.

    +

    Parse YAML safely

    +
    +
    safe_yaml is required by:
    +
    test-kitchen
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + sawyer + v0.5.5 +

    +

    Secret User Agent of HTTP

    +

    +
    +
    sawyer is required by:
    +
    octokit
    +
    +
    +
    sawyer relies on:
    +
    faraday, addressable
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + semverse + v1.2.1 +

    +

    An elegant library for representing and comparing SemVer versions and constraints

    +

    An elegant library for representing and comparing SemVer versions and constraints

    +
    +
    semverse is required by:
    +
    ridley, solve
    +
    +
    +
    semverse relies on:
    +
    bundler, rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + sequel + v4.16.0 +

    +

    The Database Toolkit for Ruby

    +

    The Database Toolkit for Ruby

    +
    +
    sequel is required by:
    +
    license_finder
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + serverspec + v2.3.1 (default) +

    +

    RSpec tests for your servers configured by Puppet, Chef or anything else

    +

    RSpec tests for your servers configured by Puppet, Chef or anything else

    +
    +
    serverspec is required by:
    +
    busser-serverspec
    +
    +
    +
    serverspec relies on:
    +
    rspec, rspec-its, multi_json, specinfra, bundler, rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + slop + v3.6.0 +

    +

    Simple Lightweight Option Parsing

    +

    A simple DSL for gathering options and parsing the command line

    +
    +
    slop is required by:
    +
    parser, pry
    +
    +
    +
    slop relies on:
    +
    rake
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + solve + v1.2.1 +

    +

    A Ruby version constraint solver implementing Semantic Versioning 2.0.0-rc.1

    +

    A Ruby version constraint solver

    +
    +
    solve is required by:
    +
    dep_selector, berkshelf
    +
    +
    +
    solve relies on:
    +
    semverse, dep_selector
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + specinfra + v2.5.0 +

    +

    Common layer for serverspec and itamae

    +

    Common layer for serverspec and itamae

    +
    +
    specinfra is required by:
    +
    serverspec
    +
    +
    +
    specinfra relies on:
    +
    net-ssh, net-scp, bundler, rake, rspec, rspec-its
    +
    +
    +
    +
    +

    + New BSD + whitelisted + +

    +
    +

    + sqlite3 + v1.3.10 +

    +

    This module allows Ruby programs to interface with the SQLite3 database engine (http://www.sqlite.org)

    +

    This module allows Ruby programs to interface with the SQLite3 +database engine (http://www.sqlite.org). You must have the +SQLite engine installed in order to build this module. + +Note that this module is only compatible with SQLite 3.6.16 or newer.

    +
    +
    sqlite3 is required by:
    +
    license_finder
    +
    +
    +
    sqlite3 relies on:
    +
    mini_portile
    +
    +
    +
    +
    +

    + ruby + whitelisted + +

    +
    +

    + systemu + v2.6.4 +

    +

    systemu

    +

    universal capture of stdout and stderr and handling of child process pid for windows, *nix, etc.

    +
    +
    systemu is required by:
    +
    ohai
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + test-kitchen + v1.2.1 (integration) +

    +

    Test Kitchen is an integration tool for developing and testing infrastructure code and software on isolated target platforms.

    +

    Test Kitchen is an integration tool for developing and testing infrastructure code and software on isolated target platforms.

    +
    +
    test-kitchen is required by:
    +
    berkshelf, kitchen-vagrant
    +
    +
    +
    test-kitchen relies on:
    +
    mixlib-shellout, net-scp, net-ssh, safe_yaml, thor, bundler, rake
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + thor + v0.19.1 +

    +

    Thor is a toolkit for building powerful command-line interfaces.

    +

    Thor is a toolkit for building powerful command-line interfaces.

    +
    +
    thor is required by:
    +
    buff-extensions, varia_model, buff-config, buff-ruby_engine, buff-shell_out, berkshelf, busser, guard, test-kitchen, license_finder
    +
    +
    +
    thor relies on:
    +
    bundler
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + timers + v4.0.1 +

    +

    Schedule procs to run after a certain time, or at periodic intervals, using any API that accepts a timeout

    +

    Pure Ruby one-shot and periodic timers

    +
    +
    timers is required by:
    +
    celluloid
    +
    +
    +
    timers relies on:
    +
    hitimes, rake, rspec
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + treetop + v1.5.3 +

    +

    A Ruby-based text parsing and interpretation DSL

    +

    +
    +
    treetop is required by:
    +
    foodcritic
    +
    +
    +
    treetop relies on:
    +
    polyglot, rspec, rake
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + varia_model + v0.4.0 +

    +

    A mixin to provide objects with magic attribute reading and writing

    +

    A mixin to provide objects with magic attribute reading and writing

    +
    +
    varia_model is required by:
    +
    buff-config, ridley
    +
    +
    +
    varia_model relies on:
    +
    hashie, buff-extensions, buff-ruby_engine, thor, bundler, rake, rspec, guard, guard-rspec
    +
    +
    +
    +
    +

    + Apache 2.0 + whitelisted + +

    +
    +

    + wmi-lite + v1.0.0 +

    +

    A lightweight utility library for accessing basic WMI (Windows Management Instrumentation) functionality on Windows

    +

    A lightweight utility over win32ole for accessing basic WMI (Windows Management Instrumentation) functionality in the Microsoft Windows operating system. It has no runtime dependencies other than Ruby, so it can be used without concerns around dependency issues.

    +
    +
    wmi-lite is required by:
    +
    ohai
    +
    +
    +
    wmi-lite relies on:
    +
    bundler, rspec, rake, pry
    +
    +
    +
    +
    +

    + ruby + whitelisted + +

    +
    +

    + xml-simple + v1.1.4 +

    +

    A simple API for XML processing.

    +

    +
    +
    xml-simple is required by:
    +
    license_finder
    +
    +
    +
    +
    +

    + MIT + whitelisted + +

    +
    +

    + yajl-ruby + v1.2.1 +

    +

    Ruby C bindings to the excellent Yajl JSON stream-based parser library.

    +

    +
    +
    yajl-ruby is required by:
    +
    foodcritic
    +
    +
    +
    yajl-ruby relies on:
    +
    rspec, json
    +
    +
    +
    +
    + + diff --git a/cookbooks/redis/doc/license_finder/dependencies.md b/cookbooks/redis/doc/license_finder/dependencies.md new file mode 100644 index 0000000..581d441 --- /dev/null +++ b/cookbooks/redis/doc/license_finder/dependencies.md @@ -0,0 +1,991 @@ +# chef-redis + +As of November 22, 2014 9:37pm. 105 total + +## Summary +* 65 MIT +* 28 Apache 2.0 +* 5 ruby +* 1 Apache 2.0, MIT +* 1 BSD +* 1 Apache v2 +* 1 ISC +* 1 Artistic 2.0, GPL-2, MIT +* 1 GNU GPL v2, MIT, Perl Artistic v2 +* 1 New BSD + + + +## Items + + + +### addressable v2.3.6 +#### URI Implementation + +Apache 2.0 whitelisted + +Addressable is a replacement for the URI implementation that is part of +Ruby's standard library. It more closely conforms to the relevant RFCs and +adds support for IRIs and URI templates. + + + +### ast v2.0.0 +#### A library for working with Abstract Syntax Trees. + +MIT whitelisted + +A library for working with Abstract Syntax Trees. + + +### astrolabe v1.3.0 +#### An object-oriented AST extension for Parser + +MIT whitelisted + +An object-oriented AST extension for Parser + + +### berkshelf v3.2.1 (default) +#### Manages a Cookbook's, or an Application's, Cookbook dependencies + +Apache 2.0 whitelisted + +Manages a Cookbook's, or an Application's, Cookbook dependencies + + +### berkshelf-api-client v1.2.0 +#### API Client for communicating with a Berkshelf API server + +Apache 2.0 whitelisted + +API Client for communicating with a Berkshelf API server + + +### buff-config v1.0.1 +#### A simple configuration class + +Apache 2.0 whitelisted + +A simple configuration class + + +### buff-extensions v1.0.0 +#### Extensions to Core Ruby classes + +Apache 2.0 whitelisted + +Extensions to Core Ruby classes + + +### buff-ignore v1.1.1 +#### A Ruby library for parsing lists of files and applying pattern matching exclusion (such as .gitignore) + +Apache 2.0 whitelisted + +Parse ignore files with Ruby + + +### buff-ruby_engine v0.1.0 +#### Buff up your code with a mixin for querying the platform running Ruby + +Apache 2.0 whitelisted + +A mixin for querying the platform running Ruby + + +### buff-shell_out v0.2.0 +#### Buff up your code with a mixin for issuing shell commands and collecting the output + +Apache 2.0 whitelisted + +A mixin for issuing shell commands and collecting the output + + +### bundler v1.7.6 +#### + +MIT whitelisted + + + +### busser v0.6.0 +#### Kitchen Busser - Runs tests for projects in test-kitchen + +Apache 2.0 whitelisted + +Kitchen Busser - Runs tests for projects in test-kitchen + + +### busser-serverspec v0.5.3 (integration) +#### A Busser runner plugin for Serverspec + +Apache 2.0 whitelisted + +A Busser runner plugin for Serverspec + + +### celluloid v0.16.0 +#### Actor-based concurrent object framework for Ruby + +MIT whitelisted + +Celluloid enables people to build concurrent programs out of concurrent objects just as easily as they build sequential programs out of sequential objects + + +### celluloid-io v0.16.1 +#### Celluloid::IO allows you to monitor multiple IO objects within a Celluloid actor + +MIT whitelisted + +Evented IO for Celluloid actors + + +### chef v11.16.4 (default) +#### A systems integration framework, built to bring the benefits of configuration management to your entire infrastructure. + +Apache 2.0 whitelisted + +A systems integration framework, built to bring the benefits of configuration management to your entire infrastructure. + + +### chef-zero v2.2.1 +#### Self-contained, easy-setup, fast-start in-memory Chef server for testing and solo setup purposes + +Apache 2.0 whitelisted + +Self-contained, easy-setup, fast-start in-memory Chef server for testing and solo setup purposes + + +### chefspec v4.1.1 (default) +#### Write RSpec examples and generate coverage reports for Chef recipes! + +MIT whitelisted + +ChefSpec is a unit testing and resource coverage (code coverage) framework for testing Chef cookbooks ChefSpec makes it easy to write examples and get fast feedback on cookbook changes without the need for virtual machines or cloud servers. + + +### cleanroom v1.0.0 +#### (More) safely evaluate Ruby DSLs with cleanroom + +Apache 2.0 whitelisted + +Ruby is an excellent programming language for creating and managing custom DSLs, but how can you securely evaluate a DSL while explicitly controlling the methods exposed to the user? Our good friends instance_eval and instance_exec are great, but they expose all methods - public, protected, and private - to the user. Even worse, they expose the ability to accidentally or intentionally alter the behavior of the system! The cleanroom pattern is a safer, more convenient, Ruby-like approach for limiting the information exposed by a DSL while giving users the ability to write awesome code! + + +### coderay v1.1.0 +#### Fast syntax highlighting for selected languages. + +MIT whitelisted + +Fast and easy syntax highlighting for selected languages, written in Ruby. Comes with RedCloth integration and LOC counter. + + +### dep-selector-libgecode v1.0.2 +#### Installs a vendored copy of Gecode suitable for use with dep-selector + +MIT, Apache 2.0 whitelisted + +Installs a vendored copy of Gecode suitable for use with dep-selector + + +### dep_selector v1.0.3 +#### Given packages, versions, and a dependency graph, find a valid assignment of package versions + +Apache v2 whitelisted + +Given packages, versions, and a dependency graph, find a valid assignment of package versions + + +### diff-lcs v1.2.5 +#### Diff::LCS computes the difference between two Enumerable sequences using the McIlroy-Hunt longest common subsequence (LCS) algorithm + +MIT, Perl Artistic v2, GNU GPL v2 whitelisted + +Diff::LCS computes the difference between two Enumerable sequences using the +McIlroy-Hunt longest common subsequence (LCS) algorithm. It includes utilities +to create a simple HTML diff output format and a standard diff-like tool. + +This is release 1.2.4, fixing a bug introduced after diff-lcs 1.1.3 that did +not properly prune common sequences at the beginning of a comparison set. +Thanks to Paul Kunysch for fixing this issue. + +Coincident with the release of diff-lcs 1.2.3, we reported an issue with +Rubinius in 1.9 mode +({rubinius/rubinius#2268}[https://github.com/rubinius/rubinius/issues/2268]). +We are happy to report that this issue has been resolved. + + +### erubis v2.7.0 +#### a fast and extensible eRuby implementation which supports multi-language + +MIT whitelisted + + Erubis is an implementation of eRuby and has the following features: + + * Very fast, almost three times faster than ERB and about 10% faster than eruby. + * Multi-language support (Ruby/PHP/C/Java/Scheme/Perl/Javascript) + * Auto escaping support + * Auto trimming spaces around '<% %>' + * Embedded pattern changeable (default '<% %>') + * Enable to handle Processing Instructions (PI) as embedded pattern (ex. '') + * Context object available and easy to combine eRuby template with YAML datafile + * Print statement available + * Easy to extend and customize in subclass + * Ruby on Rails support + + + +### faraday v0.9.0 +#### HTTP/REST API client library. + +MIT whitelisted + + + +### fauxhai v2.2.0 +#### Fauxhai provides an easy way to mock out your ohai data for testing with chefspec! + +MIT whitelisted + +Easily mock out ohai data + + +### ffi v1.9.6 +#### Ruby FFI + +BSD whitelisted + +Ruby FFI library + + +### ffi-yajl v1.3.0 +#### Ruby FFI wrapper around YAJL 2.x + +Apache 2.0 whitelisted + +Ruby FFI wrapper around YAJL 2.x + + +### foodcritic v4.0.0 (default) +#### foodcritic-4.0.0 + +MIT whitelisted + +Lint tool for Opscode Chef cookbooks. + + +### formatador v0.2.5 +#### Ruby STDOUT text formatting + +MIT whitelisted + +STDOUT text formatting + + +### gherkin v2.12.2 +#### gherkin-2.12.2 + +MIT whitelisted + +A fast Gherkin lexer/parser based on the Ragel State Machine Compiler. + + +### guard v2.8.2 +#### Guard keeps an eye on your file modifications + +MIT whitelisted + +Guard is a command line tool to easily handle events on file system modifications. + + +### guard-rspec v4.3.1 (integration) +#### Guard gem for RSpec + +MIT whitelisted + +Guard::RSpec automatically run your specs (much like autotest). + + +### hashie v2.1.2 +#### Your friendly neighborhood hash library. + +MIT whitelisted + +Hashie is a collection of classes and mixins that make hashes more powerful. + + +### highline v1.6.21 +#### HighLine is a high-level command-line IO library. + +ruby whitelisted + +A high-level IO library that provides validation, type conversion, and more for +command-line interfaces. HighLine also includes a complete menu system that can +crank out anything from simple list selection to complete shells with just +minutes of work. + + + +### hitimes v1.2.2 +#### Hitimes is a fast, high resolution timer library for recording performance metrics. It uses the appropriate low method calls for each system to get the highest granularity time increments possible. + +ISC whitelisted + +Hitimes is a fast, high resolution timer library for recording performance metrics. It uses the appropriate low method calls for each system to get the highest granularity time increments possible. It currently supports any of the following systems: * any system with the POSIX call `clock_gettime()` * Mac OS X * Windows * JRuby Using Hitimes can be faster than using a series of `Time.new` calls, and it will have a much higher granularity. It is definitely faster than using `Process.times`. + + +### httparty v0.13.3 +#### Makes http fun! Also, makes consuming restful web services dead easy. + +MIT whitelisted + +Makes http fun! Also, makes consuming restful web services dead easy. + + +### ipaddress v0.8.0 +#### IPv4/IPv6 addresses manipulation library + +MIT whitelisted + + IPAddress is a Ruby library designed to make manipulation + of IPv4 and IPv6 addresses both powerful and simple. It mantains + a layer of compatibility with Ruby's own IPAddr, while + addressing many of its issues. + + + +### json v1.8.1 +#### This json is bundled with Ruby + +ruby whitelisted + + + +### kitchen-vagrant v0.15.0 (integration) +#### Kitchen::Driver::Vagrant - A Vagrant Driver for Test Kitchen. + +Apache 2.0 whitelisted + +Kitchen::Driver::Vagrant - A Vagrant Driver for Test Kitchen. + + +### libyajl2 v1.2.0 +#### Installs a vendored copy of libyajl2 for distributions which lack it + +Apache 2.0 whitelisted + +Installs a vendored copy of libyajl2 for distributions which lack it + + +### license_finder v1.2 (default) +#### Audit the OSS licenses of your application's dependencies. + +MIT whitelisted + + LicenseFinder works with your package managers to find + dependencies, detect the licenses of the packages in them, compare + those licenses against a user-defined whitelist, and give you an + actionable exception report. + + + +### listen v2.8.0 +#### Listen to file modifications + +MIT whitelisted + +The Listen gem listens to file modifications and notifies you about the changes. Works everywhere! + + +### lumberjack v1.0.9 +#### A simple, powerful, and very fast logging utility that can be a drop in replacement for Logger or ActiveSupport::BufferedLogger. + +MIT whitelisted + +A simple, powerful, and very fast logging utility that can be a drop in replacement for Logger or ActiveSupport::BufferedLogger. Provides support for automatically rolling log files even with multiple processes writing the same log file. + + +### method_source v0.8.2 +#### retrieve the sourcecode for a method + +MIT whitelisted + +retrieve the sourcecode for a method + + +### mime-types v1.25.1 +#### This library allows for the identification of a file's likely MIME content type + +MIT, Artistic 2.0, GPL-2 whitelisted + +This library allows for the identification of a file's likely MIME content +type. This is release 1.25.1, fixing an issue with priority comparison for +mime-types 1.x. The current release is 2.0, which only supports Ruby 1.9 or +later. + +Release 1.25.1 contains all features of 1.25, including the experimental +caching and lazy loading functionality. The caching and lazy loading features +were initially implemented by Greg Brockman (gdb). As these features are +experimental, they are disabled by default and must be enabled through the use +of environment variables. The cache is invalidated on a per-version basis; the +cache for version 1.25 will not be reused for any later version. + +To use lazy loading, set the environment variable +RUBY_MIME_TYPES_LAZY_LOAD+ +to any value other than 'false'. When using lazy loading, the initial startup +of MIME::Types is around 12–25× faster than normal startup (on my system, +normal startup is about 90 ms; lazy startup is about 4 ms). This isn't +generally useful, however, as the MIME::Types database has not been loaded. +Lazy startup and load is just *slightly* faster—around 1 ms. The real advantage +comes from using the cache. + +To enable the cache, set the environment variable +RUBY_MIME_TYPES_CACHE+ to a +filename where MIME::Types will have read-write access. The first time a new +version of MIME::Types is run using this file, it will be created, taking a +little longer than normal. Subsequent loads using the same cache file will be +approximately 3½× faster (25 ms) than normal loads. This can be combined with ++RUBY_MIME_TYPES_LAZY_LOAD+, but this is *not* recommended in a multithreaded +or multiprocess environment where all threads or processes will be using the +same cache file. + +As the caching interface is still experimental, the only values cached are the +default MIME::Types database, not any custom MIME::Types added by users. + +MIME types are used in MIME-compliant communications, as in e-mail or HTTP +traffic, to indicate the type of content which is transmitted. MIME::Types +provides the ability for detailed information about MIME entities (provided as +a set of MIME::Type objects) to be determined and used programmatically. There +are many types defined by RFCs and vendors, so the list is long but not +complete; don't hesitate to ask to add additional information. This library +follows the IANA collection of MIME types (see below for reference). + +MIME::Types for Ruby was originally based on MIME::Types for Perl by Mark +Overmeer, copyright 2001 - 2009. + +MIME::Types is built to conform to the MIME types of RFCs 2045 and 2231. It +tracks the {IANA registry}[http://www.iana.org/assignments/media-types/] +({ftp}[ftp://ftp.iana.org/assignments/media-types]) with some unofficial types +added from the {LTSW collection}[http://www.ltsw.se/knbase/internet/mime.htp] +and added by the users of MIME::Types. + + +### mini_portile v0.6.1 +#### Simplistic port-like solution for developers + +MIT whitelisted + +Simplistic port-like solution for developers. It provides a standard and simplified way to compile against dependency libraries without messing up your system. + + +### minitar v0.5.4 +#### Provides POSIX tarchive management from Ruby programs. + +ruby whitelisted + +Archive::Tar::Minitar is a pure-Ruby library and command-line utility that provides the ability to deal with POSIX tar(1) archive files. The implementation is based heavily on Mauricio Ferna'ndez's implementation in rpa-base, but has been reorganised to promote reuse in other projects. Antoine Toulme forked the original project on rubyforge to place it on github, under http://www.github.com/atoulme/minitar + + +### mixlib-authentication v1.3.0 +#### Mixes in simple per-request authentication + +Apache 2.0 whitelisted + +Mixes in simple per-request authentication + + +### mixlib-cli v1.5.0 +#### A simple mixin for CLI interfaces, including option parsing + +Apache 2.0 whitelisted + +A simple mixin for CLI interfaces, including option parsing + + +### mixlib-config v2.1.0 +#### A class based configuration library + +Apache 2.0 whitelisted + +A class based configuration library + + +### mixlib-log v1.6.0 +#### A gem that provides a simple mixin for log functionality + +Apache 2.0 whitelisted + + + +### mixlib-shellout v1.6.0 +#### Run external commands on Unix or Windows + +Apache 2.0 whitelisted + +Run external commands on Unix or Windows + + +### multi_json v1.10.1 +#### A common interface to multiple JSON libraries. + +MIT whitelisted + +A common interface to multiple JSON libraries, including Oj, Yajl, the JSON gem (with C-extensions), the pure-Ruby JSON gem, NSJSONSerialization, gson.rb, JrJackson, and OkJson. + + +### multi_xml v0.5.5 +#### A generic swappable back-end for XML parsing + +MIT whitelisted + +Provides swappable XML backends utilizing LibXML, Nokogiri, Ox, or REXML. + + +### multipart-post v2.0.0 +#### A multipart form post accessory for Net::HTTP. + +MIT whitelisted + +Use with Net::HTTP to do multipart form posts. IO values that have #content_type, #original_filename, and #local_path will be posted as a binary file. + + +### net-http-persistent v2.9.4 +#### Manages persistent connections using Net::HTTP plus a speed fix for Ruby 1.8 + +MIT whitelisted + +Manages persistent connections using Net::HTTP plus a speed fix for Ruby 1.8. +It's thread-safe too! + +Using persistent HTTP connections can dramatically increase the speed of HTTP. +Creating a new HTTP connection for every request involves an extra TCP +round-trip and causes TCP congestion avoidance negotiation to start over. + +Net::HTTP supports persistent connections with some API methods but does not +handle reconnection gracefully. Net::HTTP::Persistent supports reconnection +and retry according to RFC 2616. + + +### net-scp v1.2.1 +#### A pure Ruby implementation of the SCP client protocol + +MIT whitelisted + +A pure Ruby implementation of the SCP client protocol + + +### net-ssh v2.9.1 +#### Net::SSH: a pure-Ruby implementation of the SSH2 client protocol. + +MIT whitelisted + +Net::SSH: a pure-Ruby implementation of the SSH2 client protocol. It allows you to write programs that invoke and interact with processes on remote servers, via SSH2. + + +### net-ssh-gateway v1.2.0 +#### A simple library to assist in establishing tunneled Net::SSH connections + +MIT whitelisted + +A simple library to assist in establishing tunneled Net::SSH connections + + +### net-ssh-multi v1.2.0 +#### Control multiple Net::SSH connections via a single interface. + +MIT whitelisted + +Control multiple Net::SSH connections via a single interface. + + +### nio4r v1.0.1 +#### NIO provides a high performance selector API for monitoring IO objects + +MIT whitelisted + +New IO for Ruby + + +### nokogiri v1.6.4.1 +#### Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser + +MIT whitelisted + +Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser. Among Nokogiri's +many features is the ability to search documents via XPath or CSS3 selectors. + +XML is like violence - if it doesn’t solve your problems, you are not using +enough of it. + + +### octokit v3.5.2 +#### Ruby toolkit for working with the GitHub API + +MIT whitelisted + +Simple wrapper for the GitHub API + + +### ohai v7.4.0 +#### Ohai profiles your system and emits JSON + +Apache 2.0 whitelisted + +Ohai profiles your system and emits JSON + + +### parser v2.2.0.pre.8 +#### A Ruby parser written in pure Ruby. + +MIT whitelisted + +A Ruby parser written in pure Ruby. + + +### plist v3.1.0 +#### All-purpose Property List manipulation library. + +MIT whitelisted + +Plist is a library to manipulate Property List files, also known as plists. It can parse plist files into native Ruby data structures as well as generating new plist files from your Ruby objects. + + + +### polyglot v0.3.5 +#### Augment 'require' to load non-Ruby file types + +MIT whitelisted + + +The Polyglot library allows a Ruby module to register a loader +for the file type associated with a filename extension, and it +augments 'require' to find and load matching files. + + +### powerpack v0.0.9 +#### A few useful extensions to core Ruby classes. + +MIT whitelisted + +A few useful extensions to core Ruby classes. + + +### pry v0.10.1 +#### An IRB alternative and runtime developer console + +MIT whitelisted + +An IRB alternative and runtime developer console + + +### rack v1.5.2 +#### a modular Ruby webserver interface + +MIT whitelisted + +Rack provides a minimal, modular and adaptable interface for developing +web applications in Ruby. By wrapping HTTP requests and responses in +the simplest way possible, it unifies and distills the API for web +servers, web frameworks, and software in between (the so-called +middleware) into a single method call. + +Also see http://rack.github.com/. + + + +### rainbow v2.0.0 +#### Colorize printed text on ANSI terminals + +MIT whitelisted + +Colorize printed text on ANSI terminals + + +### rake v10.3.2 (default) +#### Rake is a Make-like program implemented in Ruby + +MIT whitelisted + +Rake is a Make-like program implemented in Ruby. Tasks and dependencies are +specified in standard Ruby syntax. + +Rake has the following features: + +* Rakefiles (rake's version of Makefiles) are completely defined in + standard Ruby syntax. No XML files to edit. No quirky Makefile + syntax to worry about (is that a tab or a space?) + +* Users can specify tasks with prerequisites. + +* Rake supports rule patterns to synthesize implicit tasks. + +* Flexible FileLists that act like arrays but know about manipulating + file names and paths. + +* A library of prepackaged tasks to make building rakefiles easier. For example, + tasks for building tarballs and publishing to FTP or SSH sites. (Formerly + tasks for building RDoc and Gems were included in rake but they're now + available in RDoc and RubyGems respectively.) + +* Supports parallel execution of tasks. + + +### rb-fsevent v0.9.4 +#### Very simple & usable FSEvents API + +MIT whitelisted + +FSEvents API with Signals catching (without RubyCocoa) + + +### rb-inotify v0.9.5 +#### A Ruby wrapper for Linux's inotify, using FFI + +MIT whitelisted + +A Ruby wrapper for Linux's inotify, using FFI + + +### rest-client v1.6.7 +#### Simple HTTP and REST client for Ruby, inspired by microframework syntax for specifying actions. + +MIT whitelisted + +A simple HTTP and REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete. + + +### retryable v1.3.6 +#### Kernel#retryable, allow for retrying of code blocks. + +MIT whitelisted + +Kernel#retryable, allow for retrying of code blocks. + + +### ridley v4.1.0 +#### A reliable Chef API client with a clean syntax + +Apache 2.0 whitelisted + +A reliable Chef API client with a clean syntax + + +### rspec v3.1.0 +#### rspec-3.1.0 + +MIT whitelisted + +BDD for Ruby + + +### rspec-core v3.1.7 +#### rspec-core-3.1.7 + +MIT whitelisted + +BDD for Ruby. RSpec runner and example groups. + + +### rspec-expectations v3.1.2 +#### rspec-expectations-3.1.2 + +MIT whitelisted + +rspec-expectations provides a simple, readable API to express expected outcomes of a code example. + + +### rspec-its v1.1.0 +#### Provides "its" method formally part of rspec-core + +MIT whitelisted + +RSpec extension gem for attribute matching + + +### rspec-mocks v3.1.3 +#### rspec-mocks-3.1.3 + +MIT whitelisted + +RSpec's 'test double' framework, with support for stubbing and mocking + + +### rspec-support v3.1.2 +#### rspec-support-3.1.2 + +MIT whitelisted + +Support utilities for RSpec gems + + +### rubocop v0.27.1 (default) +#### Automatic Ruby code style checking tool. + +MIT whitelisted + + Automatic Ruby code style checking tool. + Aims to enforce the community-driven Ruby Style Guide. + + + +### ruby-progressbar v1.7.0 +#### Ruby/ProgressBar is a flexible text progress bar library for Ruby. + +MIT whitelisted + +Ruby/ProgressBar is an extremely flexible text progress bar library for Ruby. +The output can be customized with a flexible formatting system including: +percentage, bars of various formats, elapsed time and estimated time remaining. + + + +### rufus-lru v1.0.5 +#### A Hash with a max size, controlled by a LRU mechanism + +MIT whitelisted + +LruHash class, a Hash with a max size, controlled by a LRU mechanism + + +### safe_yaml v1.0.4 +#### SameYAML provides an alternative implementation of YAML.load suitable for accepting user input in Ruby applications. + +MIT whitelisted + +Parse YAML safely + + +### sawyer v0.5.5 +#### Secret User Agent of HTTP + +MIT whitelisted + + + +### semverse v1.2.1 +#### An elegant library for representing and comparing SemVer versions and constraints + +Apache 2.0 whitelisted + +An elegant library for representing and comparing SemVer versions and constraints + + +### sequel v4.16.0 +#### The Database Toolkit for Ruby + +MIT whitelisted + +The Database Toolkit for Ruby + + +### serverspec v2.3.1 (default) +#### RSpec tests for your servers configured by Puppet, Chef or anything else + +MIT whitelisted + +RSpec tests for your servers configured by Puppet, Chef or anything else + + +### slop v3.6.0 +#### Simple Lightweight Option Parsing + +MIT whitelisted + +A simple DSL for gathering options and parsing the command line + + +### solve v1.2.1 +#### A Ruby version constraint solver implementing Semantic Versioning 2.0.0-rc.1 + +Apache 2.0 whitelisted + +A Ruby version constraint solver + + +### specinfra v2.5.0 +#### Common layer for serverspec and itamae + +MIT whitelisted + +Common layer for serverspec and itamae + + +### sqlite3 v1.3.10 +#### This module allows Ruby programs to interface with the SQLite3 database engine (http://www.sqlite.org) + +New BSD whitelisted + +This module allows Ruby programs to interface with the SQLite3 +database engine (http://www.sqlite.org). You must have the +SQLite engine installed in order to build this module. + +Note that this module is only compatible with SQLite 3.6.16 or newer. + + +### systemu v2.6.4 +#### systemu + +ruby whitelisted + +universal capture of stdout and stderr and handling of child process pid for windows, *nix, etc. + + +### test-kitchen v1.2.1 (integration) +#### Test Kitchen is an integration tool for developing and testing infrastructure code and software on isolated target platforms. + +Apache 2.0 whitelisted + +Test Kitchen is an integration tool for developing and testing infrastructure code and software on isolated target platforms. + + +### thor v0.19.1 +#### Thor is a toolkit for building powerful command-line interfaces. + +MIT whitelisted + +Thor is a toolkit for building powerful command-line interfaces. + + +### timers v4.0.1 +#### Schedule procs to run after a certain time, or at periodic intervals, using any API that accepts a timeout + +MIT whitelisted + +Pure Ruby one-shot and periodic timers + + +### treetop v1.5.3 +#### A Ruby-based text parsing and interpretation DSL + +MIT whitelisted + + + +### varia_model v0.4.0 +#### A mixin to provide objects with magic attribute reading and writing + +Apache 2.0 whitelisted + +A mixin to provide objects with magic attribute reading and writing + + +### wmi-lite v1.0.0 +#### A lightweight utility library for accessing basic WMI (Windows Management Instrumentation) functionality on Windows + +Apache 2.0 whitelisted + +A lightweight utility over win32ole for accessing basic WMI (Windows Management Instrumentation) functionality in the Microsoft Windows operating system. It has no runtime dependencies other than Ruby, so it can be used without concerns around dependency issues. + + +### xml-simple v1.1.4 +#### A simple API for XML processing. + +ruby whitelisted + + + +### yajl-ruby v1.2.1 +#### Ruby C bindings to the excellent Yajl JSON stream-based parser library. + +MIT whitelisted + diff --git a/cookbooks/redis/doc/license_finder/dependencies_detailed.csv b/cookbooks/redis/doc/license_finder/dependencies_detailed.csv new file mode 100644 index 0000000..b529837 --- /dev/null +++ b/cookbooks/redis/doc/license_finder/dependencies_detailed.csv @@ -0,0 +1,234 @@ +addressable,2.3.6,Apache 2.0,URI Implementation,"Addressable is a replacement for the URI implementation that is part of +Ruby's standard library. It more closely conforms to the relevant RFCs and +adds support for IRIs and URI templates." +ast,2.0.0,MIT,A library for working with Abstract Syntax Trees.,A library for working with Abstract Syntax Trees. +astrolabe,1.3.0,MIT,An object-oriented AST extension for Parser,An object-oriented AST extension for Parser +berkshelf,3.2.1,Apache 2.0,"Manages a Cookbook's, or an Application's, Cookbook dependencies","Manages a Cookbook's, or an Application's, Cookbook dependencies" +berkshelf-api-client,1.2.0,Apache 2.0,API Client for communicating with a Berkshelf API server,API Client for communicating with a Berkshelf API server +buff-config,1.0.1,Apache 2.0,A simple configuration class,A simple configuration class +buff-extensions,1.0.0,Apache 2.0,Extensions to Core Ruby classes,Extensions to Core Ruby classes +buff-ignore,1.1.1,Apache 2.0,A Ruby library for parsing lists of files and applying pattern matching exclusion (such as .gitignore),Parse ignore files with Ruby +buff-ruby_engine,0.1.0,Apache 2.0,Buff up your code with a mixin for querying the platform running Ruby,A mixin for querying the platform running Ruby +buff-shell_out,0.2.0,Apache 2.0,Buff up your code with a mixin for issuing shell commands and collecting the output,A mixin for issuing shell commands and collecting the output +bundler,1.7.6,MIT,"","" +busser,0.6.0,Apache 2.0,Kitchen Busser - Runs tests for projects in test-kitchen,Kitchen Busser - Runs tests for projects in test-kitchen +busser-serverspec,0.5.3,Apache 2.0,A Busser runner plugin for Serverspec,A Busser runner plugin for Serverspec +celluloid,0.16.0,MIT,Actor-based concurrent object framework for Ruby,Celluloid enables people to build concurrent programs out of concurrent objects just as easily as they build sequential programs out of sequential objects +celluloid-io,0.16.1,MIT,Celluloid::IO allows you to monitor multiple IO objects within a Celluloid actor,Evented IO for Celluloid actors +chef,11.16.4,Apache 2.0,"A systems integration framework, built to bring the benefits of configuration management to your entire infrastructure.","A systems integration framework, built to bring the benefits of configuration management to your entire infrastructure." +chef-zero,2.2.1,Apache 2.0,"Self-contained, easy-setup, fast-start in-memory Chef server for testing and solo setup purposes","Self-contained, easy-setup, fast-start in-memory Chef server for testing and solo setup purposes" +chefspec,4.1.1,MIT,Write RSpec examples and generate coverage reports for Chef recipes!,ChefSpec is a unit testing and resource coverage (code coverage) framework for testing Chef cookbooks ChefSpec makes it easy to write examples and get fast feedback on cookbook changes without the need for virtual machines or cloud servers. +cleanroom,1.0.0,Apache 2.0,(More) safely evaluate Ruby DSLs with cleanroom,"Ruby is an excellent programming language for creating and managing custom DSLs, but how can you securely evaluate a DSL while explicitly controlling the methods exposed to the user? Our good friends instance_eval and instance_exec are great, but they expose all methods - public, protected, and private - to the user. Even worse, they expose the ability to accidentally or intentionally alter the behavior of the system! The cleanroom pattern is a safer, more convenient, Ruby-like approach for limiting the information exposed by a DSL while giving users the ability to write awesome code!" +coderay,1.1.0,MIT,Fast syntax highlighting for selected languages.,"Fast and easy syntax highlighting for selected languages, written in Ruby. Comes with RedCloth integration and LOC counter." +dep-selector-libgecode,1.0.2,"MIT,Apache 2.0",Installs a vendored copy of Gecode suitable for use with dep-selector,Installs a vendored copy of Gecode suitable for use with dep-selector +dep_selector,1.0.3,Apache v2,"Given packages, versions, and a dependency graph, find a valid assignment of package versions","Given packages, versions, and a dependency graph, find a valid assignment of package versions" +diff-lcs,1.2.5,"MIT,Perl Artistic v2,GNU GPL v2",Diff::LCS computes the difference between two Enumerable sequences using the McIlroy-Hunt longest common subsequence (LCS) algorithm,"Diff::LCS computes the difference between two Enumerable sequences using the +McIlroy-Hunt longest common subsequence (LCS) algorithm. It includes utilities +to create a simple HTML diff output format and a standard diff-like tool. + +This is release 1.2.4, fixing a bug introduced after diff-lcs 1.1.3 that did +not properly prune common sequences at the beginning of a comparison set. +Thanks to Paul Kunysch for fixing this issue. + +Coincident with the release of diff-lcs 1.2.3, we reported an issue with +Rubinius in 1.9 mode +({rubinius/rubinius#2268}[https://github.com/rubinius/rubinius/issues/2268]). +We are happy to report that this issue has been resolved." +erubis,2.7.0,MIT,a fast and extensible eRuby implementation which supports multi-language,"Erubis is an implementation of eRuby and has the following features: + + * Very fast, almost three times faster than ERB and about 10% faster than eruby. + * Multi-language support (Ruby/PHP/C/Java/Scheme/Perl/Javascript) + * Auto escaping support + * Auto trimming spaces around '<% %>' + * Embedded pattern changeable (default '<% %>') + * Enable to handle Processing Instructions (PI) as embedded pattern (ex. '') + * Context object available and easy to combine eRuby template with YAML datafile + * Print statement available + * Easy to extend and customize in subclass + * Ruby on Rails support" +faraday,0.9.0,MIT,HTTP/REST API client library.,"" +fauxhai,2.2.0,MIT,Fauxhai provides an easy way to mock out your ohai data for testing with chefspec!,Easily mock out ohai data +ffi,1.9.6,BSD,Ruby FFI,Ruby FFI library +ffi-yajl,1.3.0,Apache 2.0,Ruby FFI wrapper around YAJL 2.x,Ruby FFI wrapper around YAJL 2.x +foodcritic,4.0.0,MIT,foodcritic-4.0.0,Lint tool for Opscode Chef cookbooks. +formatador,0.2.5,MIT,Ruby STDOUT text formatting,STDOUT text formatting +gherkin,2.12.2,MIT,gherkin-2.12.2,A fast Gherkin lexer/parser based on the Ragel State Machine Compiler. +guard,2.8.2,MIT,Guard keeps an eye on your file modifications,Guard is a command line tool to easily handle events on file system modifications. +guard-rspec,4.3.1,MIT,Guard gem for RSpec,Guard::RSpec automatically run your specs (much like autotest). +hashie,2.1.2,MIT,Your friendly neighborhood hash library.,Hashie is a collection of classes and mixins that make hashes more powerful. +highline,1.6.21,ruby,HighLine is a high-level command-line IO library.,"A high-level IO library that provides validation, type conversion, and more for +command-line interfaces. HighLine also includes a complete menu system that can +crank out anything from simple list selection to complete shells with just +minutes of work." +hitimes,1.2.2,ISC,"Hitimes is a fast, high resolution timer library for recording performance metrics. It uses the appropriate low method calls for each system to get the highest granularity time increments possible.","Hitimes is a fast, high resolution timer library for recording performance metrics. It uses the appropriate low method calls for each system to get the highest granularity time increments possible. It currently supports any of the following systems: * any system with the POSIX call `clock_gettime()` * Mac OS X * Windows * JRuby Using Hitimes can be faster than using a series of `Time.new` calls, and it will have a much higher granularity. It is definitely faster than using `Process.times`." +httparty,0.13.3,MIT,"Makes http fun! Also, makes consuming restful web services dead easy.","Makes http fun! Also, makes consuming restful web services dead easy." +ipaddress,0.8.0,MIT,IPv4/IPv6 addresses manipulation library,"IPAddress is a Ruby library designed to make manipulation + of IPv4 and IPv6 addresses both powerful and simple. It mantains + a layer of compatibility with Ruby's own IPAddr, while + addressing many of its issues." +json,1.8.1,ruby,This json is bundled with Ruby,"" +kitchen-vagrant,0.15.0,Apache 2.0,Kitchen::Driver::Vagrant - A Vagrant Driver for Test Kitchen.,Kitchen::Driver::Vagrant - A Vagrant Driver for Test Kitchen. +libyajl2,1.2.0,Apache 2.0,Installs a vendored copy of libyajl2 for distributions which lack it,Installs a vendored copy of libyajl2 for distributions which lack it +license_finder,1.2,MIT,Audit the OSS licenses of your application's dependencies.,"LicenseFinder works with your package managers to find + dependencies, detect the licenses of the packages in them, compare + those licenses against a user-defined whitelist, and give you an + actionable exception report." +listen,2.8.0,MIT,Listen to file modifications,The Listen gem listens to file modifications and notifies you about the changes. Works everywhere! +lumberjack,1.0.9,MIT,"A simple, powerful, and very fast logging utility that can be a drop in replacement for Logger or ActiveSupport::BufferedLogger.","A simple, powerful, and very fast logging utility that can be a drop in replacement for Logger or ActiveSupport::BufferedLogger. Provides support for automatically rolling log files even with multiple processes writing the same log file." +method_source,0.8.2,MIT,retrieve the sourcecode for a method,retrieve the sourcecode for a method +mime-types,1.25.1,"MIT,Artistic 2.0,GPL-2",This library allows for the identification of a file's likely MIME content type,"This library allows for the identification of a file's likely MIME content +type. This is release 1.25.1, fixing an issue with priority comparison for +mime-types 1.x. The current release is 2.0, which only supports Ruby 1.9 or +later. + +Release 1.25.1 contains all features of 1.25, including the experimental +caching and lazy loading functionality. The caching and lazy loading features +were initially implemented by Greg Brockman (gdb). As these features are +experimental, they are disabled by default and must be enabled through the use +of environment variables. The cache is invalidated on a per-version basis; the +cache for version 1.25 will not be reused for any later version. + +To use lazy loading, set the environment variable +RUBY_MIME_TYPES_LAZY_LOAD+ +to any value other than 'false'. When using lazy loading, the initial startup +of MIME::Types is around 12–25× faster than normal startup (on my system, +normal startup is about 90 ms; lazy startup is about 4 ms). This isn't +generally useful, however, as the MIME::Types database has not been loaded. +Lazy startup and load is just *slightly* faster—around 1 ms. The real advantage +comes from using the cache. + +To enable the cache, set the environment variable +RUBY_MIME_TYPES_CACHE+ to a +filename where MIME::Types will have read-write access. The first time a new +version of MIME::Types is run using this file, it will be created, taking a +little longer than normal. Subsequent loads using the same cache file will be +approximately 3½× faster (25 ms) than normal loads. This can be combined with ++RUBY_MIME_TYPES_LAZY_LOAD+, but this is *not* recommended in a multithreaded +or multiprocess environment where all threads or processes will be using the +same cache file. + +As the caching interface is still experimental, the only values cached are the +default MIME::Types database, not any custom MIME::Types added by users. + +MIME types are used in MIME-compliant communications, as in e-mail or HTTP +traffic, to indicate the type of content which is transmitted. MIME::Types +provides the ability for detailed information about MIME entities (provided as +a set of MIME::Type objects) to be determined and used programmatically. There +are many types defined by RFCs and vendors, so the list is long but not +complete; don't hesitate to ask to add additional information. This library +follows the IANA collection of MIME types (see below for reference). + +MIME::Types for Ruby was originally based on MIME::Types for Perl by Mark +Overmeer, copyright 2001 - 2009. + +MIME::Types is built to conform to the MIME types of RFCs 2045 and 2231. It +tracks the {IANA registry}[http://www.iana.org/assignments/media-types/] +({ftp}[ftp://ftp.iana.org/assignments/media-types]) with some unofficial types +added from the {LTSW collection}[http://www.ltsw.se/knbase/internet/mime.htp] +and added by the users of MIME::Types." +mini_portile,0.6.1,MIT,Simplistic port-like solution for developers,Simplistic port-like solution for developers. It provides a standard and simplified way to compile against dependency libraries without messing up your system. +minitar,0.5.4,ruby,Provides POSIX tarchive management from Ruby programs.,"Archive::Tar::Minitar is a pure-Ruby library and command-line utility that provides the ability to deal with POSIX tar(1) archive files. The implementation is based heavily on Mauricio Ferna'ndez's implementation in rpa-base, but has been reorganised to promote reuse in other projects. Antoine Toulme forked the original project on rubyforge to place it on github, under http://www.github.com/atoulme/minitar" +mixlib-authentication,1.3.0,Apache 2.0,Mixes in simple per-request authentication,Mixes in simple per-request authentication +mixlib-cli,1.5.0,Apache 2.0,"A simple mixin for CLI interfaces, including option parsing","A simple mixin for CLI interfaces, including option parsing" +mixlib-config,2.1.0,Apache 2.0,A class based configuration library,A class based configuration library +mixlib-log,1.6.0,Apache 2.0,A gem that provides a simple mixin for log functionality,"" +mixlib-shellout,1.6.0,Apache 2.0,Run external commands on Unix or Windows,Run external commands on Unix or Windows +multi_json,1.10.1,MIT,A common interface to multiple JSON libraries.,"A common interface to multiple JSON libraries, including Oj, Yajl, the JSON gem (with C-extensions), the pure-Ruby JSON gem, NSJSONSerialization, gson.rb, JrJackson, and OkJson." +multi_xml,0.5.5,MIT,A generic swappable back-end for XML parsing,"Provides swappable XML backends utilizing LibXML, Nokogiri, Ox, or REXML." +multipart-post,2.0.0,MIT,A multipart form post accessory for Net::HTTP.,"Use with Net::HTTP to do multipart form posts. IO values that have #content_type, #original_filename, and #local_path will be posted as a binary file." +net-http-persistent,2.9.4,MIT,Manages persistent connections using Net::HTTP plus a speed fix for Ruby 1.8,"Manages persistent connections using Net::HTTP plus a speed fix for Ruby 1.8. +It's thread-safe too! + +Using persistent HTTP connections can dramatically increase the speed of HTTP. +Creating a new HTTP connection for every request involves an extra TCP +round-trip and causes TCP congestion avoidance negotiation to start over. + +Net::HTTP supports persistent connections with some API methods but does not +handle reconnection gracefully. Net::HTTP::Persistent supports reconnection +and retry according to RFC 2616." +net-scp,1.2.1,MIT,A pure Ruby implementation of the SCP client protocol,A pure Ruby implementation of the SCP client protocol +net-ssh,2.9.1,MIT,Net::SSH: a pure-Ruby implementation of the SSH2 client protocol.,"Net::SSH: a pure-Ruby implementation of the SSH2 client protocol. It allows you to write programs that invoke and interact with processes on remote servers, via SSH2." +net-ssh-gateway,1.2.0,MIT,A simple library to assist in establishing tunneled Net::SSH connections,A simple library to assist in establishing tunneled Net::SSH connections +net-ssh-multi,1.2.0,MIT,Control multiple Net::SSH connections via a single interface.,Control multiple Net::SSH connections via a single interface. +nio4r,1.0.1,MIT,NIO provides a high performance selector API for monitoring IO objects,New IO for Ruby +nokogiri,1.6.4.1,MIT,"Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser","Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser. Among Nokogiri's +many features is the ability to search documents via XPath or CSS3 selectors. + +XML is like violence - if it doesn’t solve your problems, you are not using +enough of it." +octokit,3.5.2,MIT,Ruby toolkit for working with the GitHub API,Simple wrapper for the GitHub API +ohai,7.4.0,Apache 2.0,Ohai profiles your system and emits JSON,Ohai profiles your system and emits JSON +parser,2.2.0.pre.8,MIT,A Ruby parser written in pure Ruby.,A Ruby parser written in pure Ruby. +plist,3.1.0,MIT,All-purpose Property List manipulation library.,"Plist is a library to manipulate Property List files, also known as plists. It can parse plist files into native Ruby data structures as well as generating new plist files from your Ruby objects." +polyglot,0.3.5,MIT,Augment 'require' to load non-Ruby file types,"The Polyglot library allows a Ruby module to register a loader +for the file type associated with a filename extension, and it +augments 'require' to find and load matching files." +powerpack,0.0.9,MIT,A few useful extensions to core Ruby classes.,A few useful extensions to core Ruby classes. +pry,0.10.1,MIT,An IRB alternative and runtime developer console,An IRB alternative and runtime developer console +rack,1.5.2,MIT,a modular Ruby webserver interface,"Rack provides a minimal, modular and adaptable interface for developing +web applications in Ruby. By wrapping HTTP requests and responses in +the simplest way possible, it unifies and distills the API for web +servers, web frameworks, and software in between (the so-called +middleware) into a single method call. + +Also see http://rack.github.com/." +rainbow,2.0.0,MIT,Colorize printed text on ANSI terminals,Colorize printed text on ANSI terminals +rake,10.3.2,MIT,Rake is a Make-like program implemented in Ruby,"Rake is a Make-like program implemented in Ruby. Tasks and dependencies are +specified in standard Ruby syntax. + +Rake has the following features: + +* Rakefiles (rake's version of Makefiles) are completely defined in + standard Ruby syntax. No XML files to edit. No quirky Makefile + syntax to worry about (is that a tab or a space?) + +* Users can specify tasks with prerequisites. + +* Rake supports rule patterns to synthesize implicit tasks. + +* Flexible FileLists that act like arrays but know about manipulating + file names and paths. + +* A library of prepackaged tasks to make building rakefiles easier. For example, + tasks for building tarballs and publishing to FTP or SSH sites. (Formerly + tasks for building RDoc and Gems were included in rake but they're now + available in RDoc and RubyGems respectively.) + +* Supports parallel execution of tasks." +rb-fsevent,0.9.4,MIT,Very simple & usable FSEvents API,FSEvents API with Signals catching (without RubyCocoa) +rb-inotify,0.9.5,MIT,"A Ruby wrapper for Linux's inotify, using FFI","A Ruby wrapper for Linux's inotify, using FFI" +rest-client,1.6.7,MIT,"Simple HTTP and REST client for Ruby, inspired by microframework syntax for specifying actions.","A simple HTTP and REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete." +retryable,1.3.6,MIT,"Kernel#retryable, allow for retrying of code blocks.","Kernel#retryable, allow for retrying of code blocks." +ridley,4.1.0,Apache 2.0,A reliable Chef API client with a clean syntax,A reliable Chef API client with a clean syntax +rspec,3.1.0,MIT,rspec-3.1.0,BDD for Ruby +rspec-core,3.1.7,MIT,rspec-core-3.1.7,BDD for Ruby. RSpec runner and example groups. +rspec-expectations,3.1.2,MIT,rspec-expectations-3.1.2,"rspec-expectations provides a simple, readable API to express expected outcomes of a code example." +rspec-its,1.1.0,MIT,"Provides ""its"" method formally part of rspec-core",RSpec extension gem for attribute matching +rspec-mocks,3.1.3,MIT,rspec-mocks-3.1.3,"RSpec's 'test double' framework, with support for stubbing and mocking" +rspec-support,3.1.2,MIT,rspec-support-3.1.2,Support utilities for RSpec gems +rubocop,0.27.1,MIT,Automatic Ruby code style checking tool.,"Automatic Ruby code style checking tool. + Aims to enforce the community-driven Ruby Style Guide." +ruby-progressbar,1.7.0,MIT,Ruby/ProgressBar is a flexible text progress bar library for Ruby.,"Ruby/ProgressBar is an extremely flexible text progress bar library for Ruby. +The output can be customized with a flexible formatting system including: +percentage, bars of various formats, elapsed time and estimated time remaining." +rufus-lru,1.0.5,MIT,"A Hash with a max size, controlled by a LRU mechanism","LruHash class, a Hash with a max size, controlled by a LRU mechanism" +safe_yaml,1.0.4,MIT,SameYAML provides an alternative implementation of YAML.load suitable for accepting user input in Ruby applications.,Parse YAML safely +sawyer,0.5.5,MIT,Secret User Agent of HTTP,"" +semverse,1.2.1,Apache 2.0,An elegant library for representing and comparing SemVer versions and constraints,An elegant library for representing and comparing SemVer versions and constraints +sequel,4.16.0,MIT,The Database Toolkit for Ruby,The Database Toolkit for Ruby +serverspec,2.3.1,MIT,"RSpec tests for your servers configured by Puppet, Chef or anything else","RSpec tests for your servers configured by Puppet, Chef or anything else" +slop,3.6.0,MIT,Simple Lightweight Option Parsing,A simple DSL for gathering options and parsing the command line +solve,1.2.1,Apache 2.0,A Ruby version constraint solver implementing Semantic Versioning 2.0.0-rc.1,A Ruby version constraint solver +specinfra,2.5.0,MIT,Common layer for serverspec and itamae,Common layer for serverspec and itamae +sqlite3,1.3.10,New BSD,This module allows Ruby programs to interface with the SQLite3 database engine (http://www.sqlite.org),"This module allows Ruby programs to interface with the SQLite3 +database engine (http://www.sqlite.org). You must have the +SQLite engine installed in order to build this module. + +Note that this module is only compatible with SQLite 3.6.16 or newer." +systemu,2.6.4,ruby,systemu,"universal capture of stdout and stderr and handling of child process pid for windows, *nix, etc." +test-kitchen,1.2.1,Apache 2.0,Test Kitchen is an integration tool for developing and testing infrastructure code and software on isolated target platforms.,Test Kitchen is an integration tool for developing and testing infrastructure code and software on isolated target platforms. +thor,0.19.1,MIT,Thor is a toolkit for building powerful command-line interfaces.,Thor is a toolkit for building powerful command-line interfaces. +timers,4.0.1,MIT,"Schedule procs to run after a certain time, or at periodic intervals, using any API that accepts a timeout",Pure Ruby one-shot and periodic timers +treetop,1.5.3,MIT,A Ruby-based text parsing and interpretation DSL,"" +varia_model,0.4.0,Apache 2.0,A mixin to provide objects with magic attribute reading and writing,A mixin to provide objects with magic attribute reading and writing +wmi-lite,1.0.0,Apache 2.0,A lightweight utility library for accessing basic WMI (Windows Management Instrumentation) functionality on Windows,"A lightweight utility over win32ole for accessing basic WMI (Windows Management Instrumentation) functionality in the Microsoft Windows operating system. It has no runtime dependencies other than Ruby, so it can be used without concerns around dependency issues." +xml-simple,1.1.4,ruby,A simple API for XML processing.,"" +yajl-ruby,1.2.1,MIT,Ruby C bindings to the excellent Yajl JSON stream-based parser library.,"" diff --git a/cookbooks/redis/files/default/tests/minitest/client_test.rb b/cookbooks/redis/files/default/tests/minitest/client_test.rb new file mode 100644 index 0000000..8295e75 --- /dev/null +++ b/cookbooks/redis/files/default/tests/minitest/client_test.rb @@ -0,0 +1,7 @@ +require_relative "test_helper" + +describe_recipe "redis::client" do + it "installed the `redis-tools` package" do + package("redis-tools").must_be_installed + end +end diff --git a/cookbooks/redis/files/default/tests/minitest/default_test.rb b/cookbooks/redis/files/default/tests/minitest/default_test.rb new file mode 100644 index 0000000..7a8d743 --- /dev/null +++ b/cookbooks/redis/files/default/tests/minitest/default_test.rb @@ -0,0 +1,19 @@ +require_relative "test_helper" + +describe_recipe "redis::default" do + let(:apt_path) do + "/etc/apt/sources.list.d" + end + + it "set up an apt repository" do + repo = { + "debian" => { name: "dotdeb", content: "dotdeb" }, + "ubuntu" => { name: "chris-lea-redis-server", content: "chris-lea" } + }[node["platform"]] + + apt_file = "#{apt_path}/#{repo[:name]}.list" + + file(apt_file).must_exist + file(apt_file).must_include repo[:content] + end +end diff --git a/cookbooks/redis/files/default/tests/minitest/server_test.rb b/cookbooks/redis/files/default/tests/minitest/server_test.rb new file mode 100644 index 0000000..d6a085b --- /dev/null +++ b/cookbooks/redis/files/default/tests/minitest/server_test.rb @@ -0,0 +1,48 @@ +require_relative "test_helper" + +describe_recipe "redis::server" do + it "installed the `redis-server` package" do + package("redis-server").must_be_installed + end + + it "enabled the `redis-server` service" do + service("redis-server").must_be_enabled + end + + it "started the `redis-server` service" do + service("redis-server").must_be_running + end + + describe "data directory" do + let(:dir) do + directory node["redis"]["dir"] + end + + it { dir.must_exist } + it { dir.must_have :owner, "redis" } + it { dir.must_have :group, "redis" } + it { dir.must_have :mode, "750" } + end + + describe "redis.conf" do + let(:conf) do + file "/etc/redis/redis.conf" + end + + it { conf.must_exist } + it { conf.must_have :owner, "root" } + it { conf.must_have :group, "root" } + it { conf.must_have :mode, "644" } + end + + describe "`default` file" do + let(:default) do + file "/etc/default/redis-server" + end + + it { default.must_exist } + it { default.must_have :owner, "root" } + it { default.must_have :group, "root" } + it { default.must_have :mode, "644" } + end +end diff --git a/cookbooks/redis/files/default/tests/minitest/test_helper.rb b/cookbooks/redis/files/default/tests/minitest/test_helper.rb new file mode 100644 index 0000000..2b82ba7 --- /dev/null +++ b/cookbooks/redis/files/default/tests/minitest/test_helper.rb @@ -0,0 +1,5 @@ +require "minitest/spec" + +include MiniTest::Chef::Assertions +include MiniTest::Chef::Context +include MiniTest::Chef::Resources diff --git a/cookbooks/redis/metadata.rb b/cookbooks/redis/metadata.rb new file mode 100644 index 0000000..d67a2c7 --- /dev/null +++ b/cookbooks/redis/metadata.rb @@ -0,0 +1,15 @@ +name "redis" +maintainer "Phil Cohen" +maintainer_email "github@phlippers.net" +license "MIT" +description "Installs and configures Redis" +version "0.5.6" + +recipe "redis::default", "Sets up ppa apt repository" +recipe "redis::server", "Installs redis server" +recipe "redis::client", "Installs redis client" + +supports "debian" +supports "ubuntu" + +depends "apt" diff --git a/cookbooks/redis/recipes/client.rb b/cookbooks/redis/recipes/client.rb new file mode 100644 index 0000000..f80ac1e --- /dev/null +++ b/cookbooks/redis/recipes/client.rb @@ -0,0 +1,10 @@ +# +# Cookbook Name:: redis +# Recipe:: client +# + +include_recipe "redis::default" + +package "redis-tools" do + action node["redis"]["auto_upgrade"] ? :upgrade : :install +end diff --git a/cookbooks/redis/recipes/default.rb b/cookbooks/redis/recipes/default.rb new file mode 100644 index 0000000..496ec29 --- /dev/null +++ b/cookbooks/redis/recipes/default.rb @@ -0,0 +1,11 @@ +# +# Cookbook Name:: redis +# Recipe:: default +# +apt_repository node["redis"]["apt_repository"] do + uri node["redis"]["apt_uri"] + distribution node["redis"]["apt_distribution"] + components node["redis"]["apt_components"] + keyserver node["redis"]["apt_keyserver"] + key node["redis"]["apt_key"] +end diff --git a/cookbooks/redis/recipes/server.rb b/cookbooks/redis/recipes/server.rb new file mode 100644 index 0000000..7d22d4b --- /dev/null +++ b/cookbooks/redis/recipes/server.rb @@ -0,0 +1,38 @@ +# +# Cookbook Name:: redis +# Recipe:: server +# + +include_recipe "redis::default" + +package "redis-server" do + action node["redis"]["auto_upgrade"] ? :upgrade : :install +end + +directory node["redis"]["dir"] do + owner "redis" + group "redis" + mode "0750" + recursive true +end + +service "redis-server" do + supports restart: true + action [:enable, :start] +end + +template "/etc/redis/redis.conf" do + source "redis.conf.erb" + owner "root" + group "root" + mode "0644" + notifies :restart, "service[redis-server]" +end + +template "/etc/default/redis-server" do + source "default_redis-server.erb" + owner "root" + group "root" + mode "0644" + notifies :restart, "service[redis-server]" +end diff --git a/cookbooks/redis/spec/client_spec.rb b/cookbooks/redis/spec/client_spec.rb new file mode 100644 index 0000000..fda98a4 --- /dev/null +++ b/cookbooks/redis/spec/client_spec.rb @@ -0,0 +1,27 @@ +require "spec_helper" + +describe "redis::client" do + let(:chef_run) do + ChefSpec::SoloRunner.new.converge(described_recipe) + end + + it { expect(chef_run).to include_recipe("redis::default") } + + describe "package installation" do + describe "default action" do + it { expect(chef_run).to install_package("redis-tools") } + it { expect(chef_run).to_not upgrade_package("redis-tools") } + end + + describe "when `auto_upgrade` is `true`" do + let(:chef_run) do + ChefSpec::SoloRunner.new do |node| + node.set["redis"]["auto_upgrade"] = true + end.converge(described_recipe) + end + + it { expect(chef_run).to_not install_package("redis-tools") } + it { expect(chef_run).to upgrade_package("redis-tools") } + end + end +end diff --git a/cookbooks/redis/spec/default_spec.rb b/cookbooks/redis/spec/default_spec.rb new file mode 100644 index 0000000..e228887 --- /dev/null +++ b/cookbooks/redis/spec/default_spec.rb @@ -0,0 +1,19 @@ +require "spec_helper" + +describe "redis::default" do + let(:chef_run) do + ChefSpec::SoloRunner.new.converge(described_recipe) + end + + it { expect(chef_run).to add_apt_repository("chris-lea-redis-server") } + + # debian family setup + context "using debian platform" do + let(:chef_run) do + env_options = { platform: "debian", version: "6.0.5" } + ChefSpec::SoloRunner.new(env_options).converge(described_recipe) + end + + it { expect(chef_run).to add_apt_repository("dotdeb") } + end +end diff --git a/cookbooks/redis/spec/server_spec.rb b/cookbooks/redis/spec/server_spec.rb new file mode 100644 index 0000000..7e0dfd3 --- /dev/null +++ b/cookbooks/redis/spec/server_spec.rb @@ -0,0 +1,69 @@ +require "spec_helper" + +describe "redis::server" do + let(:chef_run) do + ChefSpec::SoloRunner.new.converge(described_recipe) + end + + it { expect(chef_run).to include_recipe("redis::default") } + + describe "package installation" do + describe "default action" do + it { expect(chef_run).to install_package("redis-server") } + it { expect(chef_run).to_not upgrade_package("redis-server") } + end + + describe "when `auto_upgrade` is `true`" do + let(:chef_run) do + ChefSpec::SoloRunner.new do |node| + node.set["redis"]["auto_upgrade"] = true + end.converge(described_recipe) + end + + it { expect(chef_run).to_not install_package("redis-server") } + it { expect(chef_run).to upgrade_package("redis-server") } + end + end + + it "creates the data directory" do + expect(chef_run).to create_directory("/var/lib/redis").with( + owner: "redis", + group: "redis", + mode: "0750", + recursive: true + ) + end + + it { expect(chef_run).to enable_service("redis-server") } + it { expect(chef_run).to start_service("redis-server") } + + it "creates `/etc/redis/redis.conf`" do + path = "/etc/redis/redis.conf" + + expect(chef_run).to create_template(path).with( + source: "redis.conf.erb", + owner: "root", + group: "root", + mode: "0644" + ) + + expect(chef_run.template(path)).to( + notify("service[redis-server]").to(:restart) + ) + end + + it "creates `/etc/default/redis-server`" do + path = "/etc/default/redis-server" + + expect(chef_run).to create_template(path).with( + source: "default_redis-server.erb", + owner: "root", + group: "root", + mode: "0644" + ) + + expect(chef_run.template(path)).to( + notify("service[redis-server]").to(:restart) + ) + end +end diff --git a/cookbooks/redis/spec/spec_helper.rb b/cookbooks/redis/spec/spec_helper.rb new file mode 100644 index 0000000..75a73c8 --- /dev/null +++ b/cookbooks/redis/spec/spec_helper.rb @@ -0,0 +1,20 @@ +begin + require "chefspec" + require "chefspec/berkshelf" +rescue LoadError + puts "Unable to run `chefspec`" + exit +end + +RSpec.configure do |config| + config.platform = "ubuntu" + config.version = "12.04" + config.log_level = :error + config.raise_errors_for_deprecations! +end + +def add_apt_repository(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:apt_repository, :add, resource_name) +end + +at_exit { ChefSpec::Coverage.report! } diff --git a/cookbooks/redis/templates/default/default_redis-server.erb b/cookbooks/redis/templates/default/default_redis-server.erb new file mode 100644 index 0000000..9a24ec7 --- /dev/null +++ b/cookbooks/redis/templates/default/default_redis-server.erb @@ -0,0 +1,12 @@ +# redis-server configure options + +# ULIMIT: Call ulimit -n with this argument prior to invoking Redis itself. +# This may be required for high-concurrency environments. Redis itself cannot +# alter its limits as it is not being run as root. (default: do not call +# ulimit) +# +<% if node["redis"]["ulimit"] && !node["redis"]["ulimit"].empty? %> +ULIMIT=<%= node["redis"]["ulimit"] %> +<% else %> +# ULIMIT=65536 +<% end %> diff --git a/cookbooks/redis/templates/default/redis.conf.erb b/cookbooks/redis/templates/default/redis.conf.erb new file mode 100644 index 0000000..474e881 --- /dev/null +++ b/cookbooks/redis/templates/default/redis.conf.erb @@ -0,0 +1,556 @@ +# Redis configuration file example + +# Note on units: when memory size is needed, it is possible to specifiy +# it in the usual form of 1k 5GB 4M and so forth: +# +# 1k => 1000 bytes +# 1kb => 1024 bytes +# 1m => 1000000 bytes +# 1mb => 1024*1024 bytes +# 1g => 1000000000 bytes +# 1gb => 1024*1024*1024 bytes +# +# units are case insensitive so 1GB 1Gb 1gB are all the same. + +# By default Redis does not run as a daemon. Use 'yes' if you need it. +# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. +daemonize <%= node["redis"]["daemonize"] %> + +# When running daemonized, Redis writes a pid file in /var/run/redis.pid by +# default. You can specify a custom pid file location here. +pidfile <%= node["redis"]["pidfile"] %> + +# Accept connections on the specified port, default is 6379. +# If port 0 is specified Redis will not listen on a TCP socket. +port <%= node["redis"]["port"] %> + +# If you want you can bind a single interface, if the bind option is not +# specified all the interfaces will listen for incoming connections. +# +<% unless node["redis"]["bind"].empty? %> +bind <%= node["redis"]["bind"] %> +<% end %> + +# Specify the path for the unix socket that will be used to listen for +# incoming connections. There is no default, so Redis will not listen +# on a unix socket when not specified. +# +<% unless node["redis"]["unixsocket"].empty? %> +unixsocket <%= node["redis"]["unixsocket"] %> +unixsocketperm <%= node["redis"]["unixsocketperm"] %> +<% end %> + +# Close the connection after a client is idle for N seconds (0 to disable) +timeout <%= node["redis"]["timeout"] %> + +# Set server verbosity to 'debug' +# it can be one of: +# debug (a lot of information, useful for development/testing) +# verbose (many rarely useful info, but not a mess like the debug level) +# notice (moderately verbose, what you want in production probably) +# warning (only very important / critical messages are logged) +loglevel <%= node["redis"]["loglevel"] %> + +# Specify the log file name. Also 'stdout' can be used to force +# Redis to log on the standard output. Note that if you use standard +# output for logging but daemonize, logs will be sent to /dev/null +logfile <%= node["redis"]["logfile"] %> + +# To enable logging to the system logger, just set 'syslog-enabled' to yes, +# and optionally update the other syslog parameters to suit your needs. +syslog-enabled <%= node["redis"]["syslog_enabled"] %> + +<% if node["redis"]["syslog_enabled"] == "yes" %> +# Specify the syslog identity. +syslog-ident <%= node["redis"]["syslog_ident"] %> + +# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. +syslog-facility <%= node["redis"]["syslog_facility"] %> +<% end %> + +# Set the number of databases. The default database is DB 0, you can select +# a different one on a per-connection basis using SELECT where +# dbid is a number between 0 and 'databases'-1 +databases <%= node["redis"]["databases"] %> + +################################ SNAPSHOTTING ################################# +# +# Save the DB on disk: +# +# save +# +# Will save the DB if both the given number of seconds and the given +# number of write operations against the DB occurred. +# +# In the example below the behaviour will be to save: +# after 900 sec (15 min) if at least 1 key changed +# after 300 sec (5 min) if at least 10 keys changed +# after 60 sec if at least 10000 keys changed +# +# Note: you can disable saving at all commenting all the "save" lines. +# +# It is also possible to remove all the previously configured save +# points by adding a save directive with a single empty string argument +# like in the following example: +# +# save "" + +<% node["redis"]["snapshots"].each do |interval, keys| %> +save <%= "#{interval} #{keys}" %> +<% end %> + +# By default Redis will stop accepting writes if RDB snapshots are enabled +# (at least one save point) and the latest background save failed. +# This will make the user aware (in an hard way) that data is not persisting +# on disk properly, otherwise chances are that no one will notice and some +# distater will happen. +# +# If the background saving process will start working again Redis will +# automatically allow writes again. +# +# However if you have setup your proper monitoring of the Redis server +# and persistence, you may want to disable this feature so that Redis will +# continue to work as usually even if there are problems with disk, +# permissions, and so forth. +stop-writes-on-bgsave-error <%= node["redis"]["stop_writes_on_bgsave_error"] %> + +# Compress string objects using LZF when dump .rdb databases? +# For default that's set to 'yes' as it's almost always a win. +# If you want to save some CPU in the saving child set it to 'no' but +# the dataset will likely be bigger if you have compressible values or keys. +rdbcompression <%= node["redis"]["rdbcompression"] %> + +# Since verison 5 of RDB a CRC64 checksum is placed at the end of the file. +# This makes the format more resistant to corruption but there is a performance +# hit to pay (around 10%) when saving and loading RDB files, so you can disable it +# for maximum performances. +# +# RDB files created with checksum disabled have a checksum of zero that will +# tell the loading code to skip the check. +rdbchecksum <%= node["redis"]["rdbchecksum"] %> + +# The filename where to dump the DB +dbfilename <%= node["redis"]["dbfilename"] %> + +# The working directory. +# +# The DB will be written inside this directory, with the filename specified +# above using the 'dbfilename' configuration directive. +# +# Also the Append Only File will be created inside this directory. +# +# Note that you must specify a directory here, not a file name. +dir <%= node["redis"]["dir"] %> + +################################# REPLICATION ################################# + +# Master-Slave replication. Use slaveof to make a Redis instance a copy of +# another Redis server. Note that the configuration is local to the slave +# so for example it is possible to configure the slave to save the DB with a +# different interval, or to listen to another port, and so on. +# +# slaveof +<% if node["redis"]["slaveof"] && !node["redis"]["slaveof"].empty? %> +slaveof <%= node["redis"]["slaveof"] %> +<% end %> + +# If the master is password protected (using the "requirepass" configuration +# directive below) it is possible to tell the slave to authenticate before +# starting the replication synchronization process, otherwise the master will +# refuse the slave request. +# +# masterauth +<% if node["redis"]["masterauth"] && !node["redis"]["masterauth"].empty? %> +masterauth <%= node["redis"]["masterauth"] %> +<% end %> + +# When a slave lost the connection with the master, or when the replication +# is still in progress, the slave can act in two different ways: +# +# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will +# still reply to client requests, possibly with out of data data, or the +# data set may just be empty if this is the first synchronization. +# +# 2) if slave-serve-stale data is set to 'no' the slave will reply with +# an error "SYNC with master in progress" to all the kind of commands +# but to INFO and SLAVEOF. +# +slave-serve-stale-data <%= node["redis"]["slave_serve_stale_data"] %> + +# You can configure a slave instance to accept writes or not. Writing against +# a slave instance may be useful to store some ephemeral data (because data +# written on a slave will be easily deleted after resync with the master) but +# may also cause problems if clients are writing to it because of a +# misconfiguration. +# +# Since Redis 2.6 by default slaves are read-only. +# +# Note: read only slaves are not designed to be exposed to untrusted clients +# on the internet. It's just a protection layer against misuse of the instance. +# Still a read only slave exports by default all the administrative commands +# such as CONFIG, DEBUG, and so forth. To a limited extend you can improve +# security of read only slaves using 'rename-command' to shadow all the +# administrative / dangerous commands. +slave-read-only <%= node["redis"]["slave_read_only"] %> + +# Slaves send PINGs to server in a predefined interval. It's possible to change +# this interval with the repl_ping_slave_period option. The default value is 10 +# seconds. +# +repl-ping-slave-period <%= node["redis"]["repl_ping_slave_period"] %> + +# The following option sets a timeout for both Bulk transfer I/O timeout and +# master data or ping response timeout. The default value is 60 seconds. +# +# It is important to make sure that this value is greater than the value +# specified for repl-ping-slave-period otherwise a timeout will be detected +# every time there is low traffic between the master and the slave. +# +repl-timeout <%= node["redis"]["repl_timeout"] %> + +# The slave priority is an integer number published by Redis in the INFO output. +# It is used by Redis Sentinel in order to select a slave to promote into a +# master if the master is no longer working correctly. +# +# A slave with a low priority number is considered better for promotion, so +# for instance if there are three slaves with priority 10, 100, 25 Sentinel will +# pick the one wtih priority 10, that is the lowest. +# +# However a special priority of 0 marks the slave as not able to perform the +# role of master, so a slave with priority of 0 will never be selected by +# Redis Sentinel for promotion. +# +# By default the priority is 100. +slave-priority <%= node["redis"]["slave_priority"] %> + +################################## SECURITY ################################### + +# Require clients to issue AUTH before processing any other +# commands. This might be useful in environments in which you do not trust +# others with access to the host running redis-server. +# +# This should stay commented out for backward compatibility and because most +# people do not need auth (e.g. they run their own servers). +# +# Warning: since Redis is pretty fast an outside user can try up to +# 150k passwords per second against a good box. This means that you should +# use a very strong password otherwise it will be very easy to break. +# +# requirepass foobared +<% if node["redis"]["requirepass"] && !node["redis"]["requirepass"].empty? %> +requirepass <%= node["redis"]["requirepass"] %> +<% end %> + +# Command renaming. +# +# It is possilbe to change the name of dangerous commands in a shared +# environment. For instance the CONFIG command may be renamed into something +# of hard to guess so that it will be still available for internal-use +# tools but not available for general clients. +# +# Example: +# +# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 +# +# It is also possilbe to completely kill a command renaming it into +# an empty string: +# +# rename-command CONFIG "" +<% node["redis"]["rename_commands"].each do |command| %> +rename-command <%= command %> +<% end %> + +################################### LIMITS #################################### + +# Set the max number of connected clients at the same time. By default there +# is no limit, and it's up to the number of file descriptors the Redis process +# is able to open. The special value '0' means no limits. +# Once the limit is reached Redis will close all the new connections sending +# an error 'max number of clients reached'. +# +maxclients <%= node["redis"]["maxclients"] %> + +# Don't use more memory than the specified amount of bytes. +# When the memory limit is reached Redis will try to remove keys +# accordingly to the eviction policy selected (see maxmemmory-policy). +# +# If Redis can't remove keys according to the policy, or if the policy is +# set to 'noeviction', Redis will start to reply with errors to commands +# that would use more memory, like SET, LPUSH, and so on, and will continue +# to reply to read-only commands like GET. +# +# This option is usually useful when using Redis as an LRU cache, or to set +# an hard memory limit for an instance (using the 'noeviction' policy). +# +# WARNING: If you have slaves attached to an instance with maxmemory on, +# the size of the output buffers needed to feed the slaves are subtracted +# from the used memory count, so that network problems / resyncs will +# not trigger a loop where keys are evicted, and in turn the output +# buffer of slaves is full with DELs of keys evicted triggering the deletion +# of more keys, and so forth until the database is completely emptied. +# +# In short... if you have slaves attached it is suggested that you set a lower +# limit for maxmemory so that there is some free RAM on the system for slave +# output buffers (but this is not needed if the policy is 'noeviction'). +# +maxmemory <%= node["redis"]["maxmemory"] %> + +# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory +# is reached? You can select among five behavior: +# +# volatile-lru -> remove the key with an expire set using an LRU algorithm +# allkeys-lru -> remove any key accordingly to the LRU algorithm +# volatile-random -> remove a random key with an expire set +# allkeys->random -> remove a random key, any key +# volatile-ttl -> remove the key with the nearest expire time (minor TTL) +# noeviction -> don't expire at all, just return an error on write operations +# +# Note: with all the kind of policies, Redis will return an error on write +# operations, when there are not suitable keys for eviction. +# +# At the date of writing this commands are: set setnx setex append +# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd +# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby +# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby +# getset mset msetnx exec sort +# +# The default is: +# +# maxmemory-policy volatile-lru +maxmemory-policy <%= node["redis"]["maxmemory_policy"] %> + +# LRU and minimal TTL algorithms are not precise algorithms but approximated +# algorithms (in order to save memory), so you can select as well the sample +# size to check. For instance for default Redis will check three keys and +# pick the one that was used less recently, you can change the sample size +# using the following configuration directive. +# +# maxmemory-samples 3 +maxmemory-samples <%= node["redis"]["maxmemory_samples"] %> + +############################## APPEND ONLY MODE ############################### + +# By default Redis asynchronously dumps the dataset on disk. If you can live +# with the idea that the latest records will be lost if something like a crash +# happens this is the preferred way to run Redis. If instead you care a lot +# about your data and don't want to that a single record can get lost you should +# enable the append only mode: when this mode is enabled Redis will append +# every write operation received in the file appendonly.aof. This file will +# be read on startup in order to rebuild the full dataset in memory. +# +# Note that you can have both the async dumps and the append only file if you +# like (you have to comment the "save" statements above to disable the dumps). +# Still if append only mode is enabled Redis will load the data from the +# log file at startup ignoring the dump.rdb file. +# +# IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append +# log file in background when it gets too big. + +appendonly <%= node["redis"]["appendonly"] %> + +# The name of the append only file (default: "appendonly.aof") +appendfilename <%= node["redis"]["appendfilename"] %> + +# The fsync() call tells the Operating System to actually write data on disk +# instead to wait for more data in the output buffer. Some OS will really flush +# data on disk, some other OS will just try to do it ASAP. +# +# Redis supports three different modes: +# +# no: don't fsync, just let the OS flush the data when it wants. Faster. +# always: fsync after every write to the append only log . Slow, Safest. +# everysec: fsync only if one second passed since the last fsync. Compromise. +# +# The default is "everysec" that's usually the right compromise between +# speed and data safety. It's up to you to understand if you can relax this to +# "no" that will will let the operating system flush the output buffer when +# it wants, for better performances (but if you can live with the idea of +# some data loss consider the default persistence mode that's snapshotting), +# or on the contrary, use "always" that's very slow but a bit safer than +# everysec. +# +# If unsure, use "everysec". + +# appendfsync always +appendfsync <%= node["redis"]["appendfsync"] %> +# appendfsync no + +# When the AOF fsync policy is set to always or everysec, and a background +# saving process (a background save or AOF log background rewriting) is +# performing a lot of I/O against the disk, in some Linux configurations +# Redis may block too long on the fsync() call. Note that there is no fix for +# this currently, as even performing fsync in a different thread will block +# our synchronous write(2) call. +# +# In order to mitigate this problem it's possible to use the following option +# that will prevent fsync() from being called in the main process while a +# BGSAVE or BGREWRITEAOF is in progress. +# +# This means that while another child is saving the durability of Redis is +# the same as "appendfsync none", that in pratical terms means that it is +# possible to lost up to 30 seconds of log in the worst scenario (with the +# default Linux settings). +# +# If you have latency problems turn this to "yes". Otherwise leave it as +# "no" that is the safest pick from the point of view of durability. +no-appendfsync-on-rewrite <%= node["redis"]["no_appendfsync_on_rewrite"] %> + +# Automatic rewrite of the append only file. +# Redis is able to automatically rewrite the log file implicitly calling +# BGREWRITEAOF when the AOF log size will growth by the specified percentage. +# +# This is how it works: Redis remembers the size of the AOF file after the +# latest rewrite (or if no rewrite happened since the restart, the size of +# the AOF at startup is used). +# +# This base size is compared to the current size. If the current size is +# bigger than the specified percentage, the rewrite is triggered. Also +# you need to specify a minimal size for the AOF file to be rewritten, this +# is useful to avoid rewriting the AOF file even if the percentage increase +# is reached but it is still pretty small. +# +# Specify a precentage of zero in order to disable the automatic AOF +# rewrite feature. + +auto-aof-rewrite-percentage <%= node["redis"]["auto_aof_rewrite_percentage"] %> +auto-aof-rewrite-min-size <%= node["redis"]["auto_aof_rewrite_min_size"] %> + +################################ LUA SCRIPTING ############################### + +# Max execution time of a Lua script in milliseconds. +# +# If the maximum execution time is reached Redis will log that a script is +# still in execution after the maximum allowed time and will start to +# reply to queries with an error. +# +# When a long running script exceed the maximum execution time only the +# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be +# used to stop a script that did not yet called write commands. The second +# is the only way to shut down the server in the case a write commands was +# already issue by the script but the user don't want to wait for the natural +# termination of the script. +# +# Set it to 0 or a negative value for unlimited execution without warnings. +lua-time-limit <%= node["redis"]["lua_time_limit"] %> + +################################## SLOW LOG ################################### + +# The Redis Slow Log is a system to log queries that exceeded a specified +# execution time. The execution time does not include the I/O operations +# like talking with the client, sending the reply and so forth, +# but just the time needed to actually execute the command (this is the only +# stage of command execution where the thread is blocked and can not serve +# other requests in the meantime). +# +# You can configure the slow log with two parameters: one tells Redis +# what is the execution time, in microseconds, to exceed in order for the +# command to get logged, and the other parameter is the length of the +# slow log. When a new command is logged the oldest one is removed from the +# queue of logged commands. + +# The following time is expressed in microseconds, so 1000000 is equivalent +# to one second. Note that a negative number disables the slow log, while +# a value of zero forces the logging of every command. +slowlog-log-slower-than <%= node["redis"]["slowlog_log_slower_than"] %> + +# There is no limit to this length. Just be aware that it will consume memory. +# You can reclaim memory used by the slow log with SLOWLOG RESET. +slowlog-max-len <%= node["redis"]["slowlog_max_len"] %> + + +############################### ADVANCED CONFIG ############################### + +# Hashes are encoded using a memory efficient data structure when they have a +# small number of entries, and the biggest entry does not exceed a given +# threshold. These thresholds can be configured using the following directives. +hash-max-ziplist-entries <%= node["redis"]["hash_max_ziplist_entries"] %> +hash-max-ziplist-value <%= node["redis"]["hash_max_ziplist_value"] %> + +# Similarly to hashes, small lists are also encoded in a special way in order +# to save a lot of space. The special representation is only used when +# you are under the following limits: +list-max-ziplist-entries <%= node["redis"]["list_max_ziplist_entries"] %> +list-max-ziplist-value <%= node["redis"]["list_max_ziplist_value"] %> + +# Sets have a special encoding in just one case: when a set is composed +# of just strings that happens to be integers in radix 10 in the range +# of 64 bit signed integers. +# The following configuration setting sets the limit in the size of the +# set in order to use this special memory saving encoding. +set-max-intset-entries <%= node["redis"]["set_max_intset_entries"] %> + +# Similarly to hashes and lists, sorted sets are also specially encoded in +# order to save a lot of space. This encoding is only used when the length and +# elements of a sorted set are below the following limits: +zset-max-ziplist-entries <%= node["redis"]["zset_max_ziplist_entries"] %> +zset-max-ziplist-value <%= node["redis"]["zset_max_ziplist_value"] %> + +# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in +# order to help rehashing the main Redis hash table (the one mapping top-level +# keys to values). The hash table implementation redis uses (see dict.c) +# performs a lazy rehashing: the more operation you run into an hash table +# that is rhashing, the more rehashing "steps" are performed, so if the +# server is idle the rehashing is never complete and some more memory is used +# by the hash table. +# +# The default is to use this millisecond 10 times every second in order to +# active rehashing the main dictionaries, freeing memory when possible. +# +# If unsure: +# use "activerehashing no" if you have hard latency requirements and it is +# not a good thing in your environment that Redis can reply form time to time +# to queries with 2 milliseconds delay. +# +# use "activerehashing yes" if you don't have such hard requirements but +# want to free memory asap when possible. +activerehashing <%= node["redis"]["activerehashing"] %> + +# The client output buffer limits can be used to force disconnection of clients +# that are not reading data from the server fast enough for some reason (a +# common reason is that a Pub/Sub client can't consume messages as fast as the +# publisher can produce them). +# +# The limit can be set differently for the three different classes of clients: +# +# normal -> normal clients +# slave -> slave clients and MONITOR clients +# pubsub -> clients subcribed to at least one pubsub channel or pattern +# +# The syntax of every client-output-buffer-limit directive is the following: +# +# client-output-buffer-limit +# +# A client is immediately disconnected once the hard limit is reached, or if +# the soft limit is reached and remains reached for the specified number of +# seconds (continuously). +# So for instance if the hard limit is 32 megabytes and the soft limit is +# 16 megabytes / 10 seconds, the client will get disconnected immediately +# if the size of the output buffers reach 32 megabytes, but will also get +# disconnected if the client reaches 16 megabytes and continuously overcomes +# the limit for 10 seconds. +# +# By default normal clients are not limited because they don't receive data +# without asking (in a push way), but just after a request, so only +# asynchronous clients may create a scenario where data is requested faster +# than it can read. +# +# Instead there is a default limit for pubsub and slave clients, since +# subscribers and slaves receive data in a push fashion. +# +# Both the hard or the soft limit can be disabled just setting it to zero. +client-output-buffer-limit normal <%= node["redis"]["client_output_buffer_limit"]["normal"] %> +client-output-buffer-limit slave <%= node["redis"]["client_output_buffer_limit"]["slave"] %> +client-output-buffer-limit pubsub <%= node["redis"]["client_output_buffer_limit"]["pubsub"] %> + +################################## INCLUDES ################################### + +# Include one or more other config files here. This is useful if you +# have a standard template that goes to all redis server but also need +# to customize a few per-server settings. Include files can include +# other files, so use this wisely. +# +# include /path/to/local.conf +# include /path/to/other.conf +<% node["redis"]["include_config_files"].each do |file| %> +include <%= file %> +<% end %> diff --git a/cookbooks/redis/test/.chef/knife.rb b/cookbooks/redis/test/.chef/knife.rb new file mode 100644 index 0000000..a0fd5a0 --- /dev/null +++ b/cookbooks/redis/test/.chef/knife.rb @@ -0,0 +1,2 @@ +cache_type "BasicFile" +cache_options(path: "#{ENV["HOME"]}/.chef/checksums") diff --git a/cookbooks/redis/test/integration/default/serverspec/default_spec.rb b/cookbooks/redis/test/integration/default/serverspec/default_spec.rb new file mode 100644 index 0000000..395eddf --- /dev/null +++ b/cookbooks/redis/test/integration/default/serverspec/default_spec.rb @@ -0,0 +1,41 @@ +require "serverspec" + +set :backend, :exec + +describe "Redis client installation" do + describe package("redis-tools") do + it { should be_installed } + end +end + +describe "Redis server installation" do + describe package("redis-server") do + it { should be_installed } + end + + describe file("/var/lib/redis") do + it { should be_a_directory } + it { should be_owned_by "redis" } + it { should be_grouped_into "redis" } + it { should be_mode 750 } + end + + describe service("redis-server") do + it { should be_enabled } + it { should be_running } + end + + describe file("/etc/redis/redis.conf") do + it { should be_a_file } + it { should be_owned_by "root" } + it { should be_grouped_into "root" } + it { should be_mode 644 } + end + + describe file("/etc/default/redis-server") do + it { should be_a_file } + it { should be_owned_by "root" } + it { should be_grouped_into "root" } + it { should be_mode 644 } + end +end diff --git a/cookbooks/redis/test/support/keys/README.md b/cookbooks/redis/test/support/keys/README.md new file mode 100644 index 0000000..47c0ccf --- /dev/null +++ b/cookbooks/redis/test/support/keys/README.md @@ -0,0 +1,17 @@ +# Insecure Keypair + +These keys are the "insecure" public/private keypair we offer to +[base box creators](http://docs.vagrantup.com/v1/docs/base_boxes.html) for use in their base boxes so that +vagrant installations can automatically SSH into the boxes. + +If you're working with a team or company or with a custom box and +you want more secure SSH, you should create your own keypair +and configure the private key in the Vagrantfile with +`config.ssh.private_key_path` + +# Putty + +If you are using Vagrant on windows, the .ppk file contained here, in the keys directory, +has been generated from the private key and should be used to connect Putty to any VMs that +are leveraging the default key pair. See [guide](http://docs.vagrantup.com/v1/docs/getting-started/ssh.html) +in the documentation for more details on using Putty with Vagrant. diff --git a/cookbooks/redis/test/support/keys/vagrant b/cookbooks/redis/test/support/keys/vagrant new file mode 100644 index 0000000..7d6a083 --- /dev/null +++ b/cookbooks/redis/test/support/keys/vagrant @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI +w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP +kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 +hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO +Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW +yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd +ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 +Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf +TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK +iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A +sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf +4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP +cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk +EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN +CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX +3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG +YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj +3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ +dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz +6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC +P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF +llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ +kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH ++vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ +NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= +-----END RSA PRIVATE KEY----- diff --git a/cookbooks/redis/test/support/keys/vagrant.pub b/cookbooks/redis/test/support/keys/vagrant.pub new file mode 100644 index 0000000..18a9c00 --- /dev/null +++ b/cookbooks/redis/test/support/keys/vagrant.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key diff --git a/cookbooks/redis/test/support/rubocop/disabled.yml b/cookbooks/redis/test/support/rubocop/disabled.yml new file mode 100644 index 0000000..b4fd70a --- /dev/null +++ b/cookbooks/redis/test/support/rubocop/disabled.yml @@ -0,0 +1,25 @@ +Encoding: + Description: 'Use UTF-8 as the source file encoding.' + Enabled: false + +SymbolArray: + Description: 'Use %i or %I for arrays of symbols.' + Enabled: false + +##################### Rails ################################## + +DefaultScope: + Description: 'Checks if the argument passed to default_scope is a block.' + Enabled: false + +HasAndBelongsToMany: + Description: 'Prefer has_many :through to has_and_belongs_to_many.' + Enabled: false + +Output: + Description: 'Checks for calls to puts, print, etc.' + Enabled: false + +Validation: + Description: 'Use sexy validations.' + Enabled: false diff --git a/cookbooks/redis/test/support/rubocop/enabled.yml b/cookbooks/redis/test/support/rubocop/enabled.yml new file mode 100644 index 0000000..df712be --- /dev/null +++ b/cookbooks/redis/test/support/rubocop/enabled.yml @@ -0,0 +1,652 @@ +# These are all the cops that are enabled in the default configuration. + +AccessModifierIndentation: + Description: Check indentation of private/protected visibility modifiers. + Enabled: true + +AccessorMethodName: + Description: Check the naming of accessor methods for get_/set_. + Enabled: true + +Alias: + Description: 'Use alias_method instead of alias.' + Enabled: true + +AlignArray: + Description: >- + Align the elements of an array literal if they span more than + one line. + Enabled: true + +AlignHash: + Description: >- + Align the elements of a hash literal if they span more than + one line. + Enabled: true + +AlignParameters: + Description: >- + Align the parameters of a method call if they span more + than one line. + Enabled: true + +AndOr: + Description: 'Use &&/|| instead of and/or.' + Enabled: true + +AsciiComments: + Description: 'Use only ascii symbols in comments.' + Enabled: true + +AsciiIdentifiers: + Description: 'Use only ascii symbols in identifiers.' + Enabled: true + +Attr: + Description: 'Checks for uses of Module#attr.' + Enabled: true + +BeginBlock: + Description: 'Avoid the use of BEGIN blocks.' + Enabled: true + +BlockComments: + Description: 'Do not use block comments.' + Enabled: true + +BlockNesting: + Description: 'Avoid excessive block nesting' + Enabled: true + +Blocks: + Description: >- + Avoid using {...} for multi-line blocks (multiline chaining is + always ugly). + Prefer {...} over do...end for single-line blocks. + Enabled: true + +BracesAroundHashParameters: + Description: 'Enforce braces style inside hash parameters.' + Enabled: true + +CaseEquality: + Description: 'Avoid explicit use of the case equality operator(===).' + Enabled: true + +CaseIndentation: + Description: 'Indentation of when in a case/when/[else/]end.' + Enabled: true + +CharacterLiteral: + Description: 'Checks for uses of character literals.' + Enabled: true + +ClassAndModuleCamelCase: + Description: 'Use CamelCase for classes and modules.' + Enabled: true + +ClassLength: + Description: 'Avoid classes longer than 100 lines of code.' + Enabled: true + +ClassMethods: + Description: 'Use self when defining module/class methods.' + Enabled: true + +ClassVars: + Description: 'Avoid the use of class variables.' + Enabled: true + +CollectionMethods: + Description: 'Preferred collection methods.' + Enabled: true + +ColonMethodCall: + Description: 'Do not use :: for method call.' + Enabled: true + +CommentAnnotation: + Description: >- + Checks formatting of special comments + (TODO, FIXME, OPTIMIZE, HACK, REVIEW). + Enabled: true + +ConstantName: + Description: 'Constants should use SCREAMING_SNAKE_CASE.' + Enabled: true + +CyclomaticComplexity: + Description: 'Avoid complex methods.' + Enabled: true + +DefWithParentheses: + Description: 'Use def with parentheses when there are arguments.' + Enabled: true + +Documentation: + Description: 'Document classes and non-namespace modules.' + Enabled: true + +DotPosition: + Description: 'Checks the position of the dot in multi-line method calls.' + Enabled: true + +EmptyLineBetweenDefs: + Description: 'Use empty lines between defs.' + Enabled: true + +EmptyLines: + Description: "Don't use several empty lines in a row." + Enabled: true + +EmptyLinesAroundAccessModifier: + Description: "Keep blank lines around access modifiers." + Enabled: true + +EmptyLinesAroundBody: + Description: "Keeps track of empty lines around expression bodies." + Enabled: true + +EmptyLiteral: + Description: 'Prefer literals to Array.new/Hash.new/String.new.' + Enabled: true + +EndBlock: + Description: 'Avoid the use of END blocks.' + Enabled: true + +EndOfLine: + Description: 'Use Unix-style line endings.' + Enabled: true + +EvenOdd: + Description: 'Favor the use of Fixnum#even? && Fixnum#odd?' + Enabled: true + +FavorJoin: + Description: 'Use Array#join instead of Array#*.' + Enabled: true + +FavorUnlessOverNegatedIf: + Description: >- + Favor unless over if for negative conditions + (or control flow or). + Enabled: true + +FavorUntilOverNegatedWhile: + Description: 'Favor until over while for negative conditions.' + Enabled: true + +FileName: + Description: 'Use snake_case for source file names.' + Enabled: true + +FinalNewline: + Description: 'Checks for a final newline in a source file.' + Enabled: true + +FlipFlop: + Description: 'Checks for flip flops' + Enabled: true + +For: + Description: 'Checks use of for or each in multiline loops.' + Enabled: true + +FormatString: + Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' + Enabled: true + +GlobalVars: + Description: 'Do not introduce global variables.' + Enabled: true + +HashMethods: + Description: 'Checks for use of deprecated Hash methods.' + Enabled: true + +HashSyntax: + Description: >- + Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax + { :a => 1, :b => 2 }. + Enabled: true + +IfUnlessModifier: + Description: >- + Favor modifier if/unless usage when you have a + single-line body. + Enabled: true + +IfWithSemicolon: + Description: 'Never use if x; .... Use the ternary operator instead.' + Enabled: true + +IndentationConsistency: + Description: 'Keep indentation straight.' + Enabled: true + +IndentationWidth: + Description: 'Use 2 spaces for indentation.' + Enabled: true + +IndentArray: + Description: >- + Checks the indentation of the first element in an array + literal. + Enabled: true + +IndentHash: + Description: 'Checks the indentation of the first key in a hash literal.' + Enabled: true + +Lambda: + Description: 'Use the new lambda literal syntax for single-line blocks.' + Enabled: true + +LambdaCall: + Description: 'Use lambda.call(...) instead of lambda.(...).' + Enabled: true + +LeadingCommentSpace: + Description: 'Comments should start with a space.' + Enabled: true + +LineEndConcatenation: + Description: 'Use \\ instead of + to concatenate two string literals at line end.' + Enabled: true + +LineLength: + Description: 'Limit lines to 79 characters.' + Enabled: true + +MethodCalledOnDoEndBlock: + Description: 'Avoid chaining a method call on a do...end block.' + Enabled: true + +MethodCallParentheses: + Description: 'Do not use parentheses for method calls with no arguments.' + Enabled: true + +MethodDefParentheses: + Description: >- + Checks if the method definitions have or don't have + parentheses. + Enabled: true + +MethodLength: + Description: 'Avoid methods longer than 10 lines of code.' + Enabled: true + +MethodName: + Description: 'Use the configured style when naming methods.' + Enabled: true + +ModuleFunction: + Description: 'Checks for usage of `extend self` in modules.' + Enabled: true + +MultilineBlockChain: + Description: 'Avoid multi-line chains of blocks.' + Enabled: true + +MultilineIfThen: + Description: 'Never use then for multi-line if/unless.' + Enabled: true + +MultilineTernaryOperator: + Description: >- + Avoid multi-line ?: (the ternary operator); + use if/unless instead. + Enabled: true + +NestedTernaryOperator: + Description: 'Use one expression per branch in a ternary operator.' + Enabled: true + +NilComparison: + Description: 'Prefer x.nil? to x == nil.' + Enabled: true + +Not: + Description: 'Use ! instead of not.' + Enabled: true + +NumericLiterals: + Description: >- + Add underscores to large numeric literals to improve their + readability. + Enabled: true + +OneLineConditional: + Description: >- + Favor the ternary operator(?:) over + if/then/else/end constructs. + Enabled: true + +OpMethod: + Description: 'When defining binary operators, name the argument other.' + Enabled: true + +ParameterLists: + Description: 'Avoid parameter lists longer than three or four parameters.' + Enabled: true + +ParenthesesAroundCondition: + Description: >- + Don't use parentheses around the condition of an + if/unless/while. + Enabled: true + +PerlBackrefs: + Description: 'Avoid Perl-style regex back references.' + Enabled: true + +PredicateName: + Description: 'Check the names of predicate methods.' + Enabled: true + +Proc: + Description: 'Use proc instead of Proc.new.' + Enabled: true + +RaiseArgs: + Description: 'Checks the arguments passed to raise/fail.' + Enabled: true + +RedundantBegin: + Description: "Don't use begin blocks when they are not needed." + Enabled: true + +RedundantException: + Description: "Checks for an obsolete RuntimeException argument in raise/fail." + Enabled: true + +RedundantReturn: + Description: "Don't use return where it's not required." + Enabled: true + +RedundantSelf: + Description: "Don't use self where it's not needed." + Enabled: true + +RegexpLiteral: + Description: >- + Use %r for regular expressions matching more than + `MaxSlashes` '/' characters. + Use %r only for regular expressions matching more than + `MaxSlashes` '/' character. + Enabled: true + +RescueModifier: + Description: 'Avoid using rescue in its modifier form.' + Enabled: true + +Semicolon: + Description: "Don't use semicolons to terminate expressions." + Enabled: true + +SignalException: + Description: 'Checks for proper usage of fail and raise.' + Enabled: true + +SingleLineBlockParams: + Description: 'Enforces the names of some block params.' + Enabled: true + +SingleLineMethods: + Description: 'Avoid single-line methods.' + Enabled: true + +SpaceAfterColon: + Description: 'Use spaces after colons.' + Enabled: true + +SpaceAfterComma: + Description: 'Use spaces after commas.' + Enabled: true + +SpaceAfterControlKeyword: + Description: 'Use spaces after if/elsif/unless/while/until/case/when.' + Enabled: true + +SpaceAfterMethodName: + Description: >- + Never put a space between a method name and the opening + parenthesis. + Enabled: true + +SpaceAfterNot: + Description: Tracks redundant space after the ! operator. + Enabled: true + +SpaceAfterSemicolon: + Description: 'Use spaces after semicolons.' + Enabled: true + +SpaceAroundBlockBraces: + Description: >- + Checks that block braces have or don't have surrounding space. + For blocks taking parameters, checks that the left brace has + or doesn't have trailing space. + Enabled: true + +SpaceAroundEqualsInParameterDefault: + Description: >- + Use spaces around the = operator when assigning default + values in def params. + Enabled: true + +SpaceAroundOperators: + Description: 'Use spaces around operators.' + Enabled: true + +SpaceBeforeModifierKeyword: + Description: 'Put a space before the modifier keyword.' + Enabled: true + +SpaceInsideBrackets: + Description: 'No spaces after [ or before ].' + Enabled: true + +SpaceInsideHashLiteralBraces: + Description: "Use spaces inside hash literal braces - or don't." + Enabled: true + +SpaceInsideParens: + Description: 'No spaces after ( or before ).' + Enabled: true + +SpecialGlobalVars: + Description: 'Avoid Perl-style global variables.' + Enabled: true + +StringLiterals: + Description: 'Checks if uses of quotes match the configured preference.' + Enabled: true + +Tab: + Description: 'No hard tabs.' + Enabled: true + +TrailingBlankLines: + Description: 'Checks for superfluous trailing blank lines.' + Enabled: true + +TrailingComma: + Description: 'Checks for trailing comma in parameter lists and literals.' + Enabled: true + +TrailingWhitespace: + Description: 'Avoid trailing whitespace.' + Enabled: true + +TrivialAccessors: + Description: 'Prefer attr_* methods to trivial readers/writers.' + Enabled: true + +UnlessElse: + Description: >- + Never use unless with else. Rewrite these with the positive + case first. + Enabled: true + +VariableInterpolation: + Description: >- + Don't interpolate global, instance and class variables + directly in strings. + Enabled: true + +VariableName: + Description: 'Use the configured style when naming variables.' + Enabled: true + +WhenThen: + Description: 'Use when x then ... for one-line cases.' + Enabled: true + +WhileUntilDo: + Description: 'Checks for redundant do after while or until.' + Enabled: true + +WhileUntilModifier: + Description: >- + Favor modifier while/until usage when you have a + single-line body. + Enabled: true + +WordArray: + Description: 'Use %w or %W for arrays of words.' + Enabled: true + +#################### Lint ################################ +### Warnings + +AmbiguousOperator: + Description: >- + Checks for ambiguous operators in the first argument of a + method invocation without parentheses. + Enabled: true + +AmbiguousRegexpLiteral: + Description: >- + Checks for ambiguous regexp literals in the first argument of + a method invocation without parenthesis. + Enabled: true + +AssignmentInCondition: + Description: "Don't use assignment in conditions." + Enabled: true + +BlockAlignment: + Description: 'Align block ends correctly.' + Enabled: true + +ConditionPosition: + Description: 'Checks for condition placed in a confusing position relative to the keyword.' + Enabled: true + +Debugger: + Description: 'Check for debugger calls.' + Enabled: true + +DeprecatedClassMethods: + Description: 'Check for deprecated class method calls.' + Enabled: true + +ElseLayout: + Description: 'Check for odd code arrangement in an else block.' + Enabled: true + +EmptyEnsure: + Description: 'Checks for empty ensure block.' + Enabled: true + +EndAlignment: + Description: 'Align ends correctly.' + Enabled: true + +EndInMethod: + Description: 'END blocks should not be placed inside method definitions.' + Enabled: true + +EnsureReturn: + Description: 'Never use return in an ensure block.' + Enabled: true + +Eval: + Description: 'The use of eval represents a serious security risk.' + Enabled: true + +HandleExceptions: + Description: "Don't suppress exception." + Enabled: true + +InvalidCharacterLiteral: + Description: >- + Checks for invalid character literals with a non-escaped + whitespace character. + Enabled: true + +LiteralInCondition: + Description: 'Checks of literals used in conditions.' + Enabled: true + +LiteralInInterpolation: + Description: 'Checks for literals used in interpolation.' + Enabled: true + +Loop: + Description: >- + Use Kernel#loop with break rather than begin/end/until or + begin/end/while for post-loop tests. + Enabled: true + +ParenthesesAsGroupedExpression: + Description: >- + Checks for method calls with a space before the opening + parenthesis. + Enabled: true + +RequireParentheses: + Description: >- + Use parentheses in the method call to avoid confusion + about precedence. + Enabled: true + +RescueException: + Description: 'Avoid rescuing the Exception class.' + Enabled: true + +ShadowingOuterLocalVariable: + Description: >- + Do not use the same name as outer local variable + for block arguments or block local variables. + Enabled: true + +StringConversionInInterpolation: + Description: 'Checks for Object#to_s usage in string interpolation.' + Enabled: true + +UnreachableCode: + Description: 'Unreachable code.' + Enabled: true + +UselessAssignment: + Description: 'Checks for useless assignment to a local variable.' + Enabled: true + +UselessComparison: + Description: 'Checks for comparison of something with itself.' + Enabled: true + +UselessElseWithoutRescue: + Description: 'Checks for useless `else` in `begin..end` without `rescue`.' + Enabled: true + +UselessSetterCall: + Description: 'Checks for useless setter call to a local variable.' + Enabled: true + +Void: + Description: 'Possible use of operator/literal/variable in void context.' + Enabled: true diff --git a/cookbooks/rsyslog/.gitignore b/cookbooks/rsyslog/.gitignore new file mode 100644 index 0000000..e618bbe --- /dev/null +++ b/cookbooks/rsyslog/.gitignore @@ -0,0 +1,41 @@ +*.gem +.zero-knife.rb +*.rbc +.bundle +.config +coverage +InstalledFiles +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp +Gemfile.lock +_Store +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ +*.tmp +*.bk +*.bkup + +# YARD artifacts +.yardoc +_yardoc +doc/ + +#chef stuff +Berksfile.lock +.kitchen +.kitchen.local.yml +vendor/ +.coverage/ + +#vagrant stuff +.vagrant/ +.vagrant.d/ diff --git a/cookbooks/rsyslog/.kitchen.busted.yml b/cookbooks/rsyslog/.kitchen.busted.yml new file mode 100644 index 0000000..0e42128 --- /dev/null +++ b/cookbooks/rsyslog/.kitchen.busted.yml @@ -0,0 +1,86 @@ +--- +driver_plugin: vagrant +driver_plugin: digitalocean +driver_config: + digitalocean_client_id: <%= ENV['DIGITAL_OCEAN_CLIENT_ID'] %> + digitalocean_api_key: <%= ENV['DIGITAL_OCEAN_API_KEY'] %> + aws_access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %> + aws_secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> + aws_ssh_key_id: <%= ENV['AWS_KEYPAIR_NAME'] %> + ssh_key: <%= ENV['AWS_PRIVATE_KEY_PATH'] %> + rackspace_username: <%= ENV['RACKSPACE_USERNAME'] %> + rackspace_api_key: <%= ENV['RACKSPACE_API_KEY'] %> + require_chef_omnibus: latest + +platforms: +# - name: omnios-r151006c +# driver_plugin: ec2 +# driver_config: +# image_id: ami-35eb835c +# username: root + +- name: centos-5.8 + driver_plugin: digitalocean + driver_config: + image_id: 1601 + flavor_id: 63 + region_id: 4 + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + +- name: centos-6.4 + driver_plugin: digitalocean + driver_config: + image_id: 562354 + flavor_id: 63 + region_id: 4 + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + +# - name: amazon-2013.09 +# driver_plugin: ec2 +# driver_config: +# image_id: ami-3be4bc52 +# username: ec2-user + +- name: ubuntu-1004 + driver_plugin: digitalocean + driver_config: + image_id: 14097 + flavor_id: 63 + region_id: 4 + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + run_list: + - recipe[apt] + +- name: ubuntu-1204 + driver_plugin: digitalocean + driver_config: + image_id: 1505447 + flavor_id: 63 + region_id: 4 + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + run_list: + - recipe[apt] + +suites: + - name: default + run_list: + - recipe[rsyslog::default] + - name: relp + run_list: + - recipe[rsyslog::default] + attributes: + rsyslog: + use_relp: true + # CentOS and OmniOS do not support relp + excludes: + - centos-5.8 + - omnios-r151006c + - name: client + run_list: + - recipe[rsyslog::client] + attributes: + rsyslog: + server_ip: 10.0.0.50 + - name: server + run_list: + - recipe[rsyslog::server] \ No newline at end of file diff --git a/cookbooks/rsyslog/.kitchen.cloud.yml b/cookbooks/rsyslog/.kitchen.cloud.yml new file mode 100644 index 0000000..44825bd --- /dev/null +++ b/cookbooks/rsyslog/.kitchen.cloud.yml @@ -0,0 +1,92 @@ +--- +driver_config: + digitalocean_client_id: <%= ENV['DIGITAL_OCEAN_CLIENT_ID'] %> + digitalocean_api_key: <%= ENV['DIGITAL_OCEAN_API_KEY'] %> + aws_access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %> + aws_secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> + aws_ssh_key_id: <%= ENV['AWS_KEYPAIR_NAME'] %> + +provisioner: + name: chef_zero + require_chef_omnibus: latest + +platforms: +- name: centos-5.8 + driver_plugin: digitalocean + driver_config: + image_id: 1601 + flavor_id: 63 + region_id: 4 + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + +- name: centos-6.4 + driver_plugin: digitalocean + driver_config: + image_id: 562354 + flavor_id: 63 + region_id: 4 + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + +# - name: amazon-2013.09 +# driver_plugin: ec2 +# driver_config: +# image_id: ami-3be4bc52 +# username: ec2-user +# ssh_key: <%= ENV['EC2_SSH_KEY_PATH'] %> + +- name: fedora-19 + driver_plugin: digitalocean + driver_config: + image_id: 696598 + flavor_id: 63 + region_id: 4 + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + +- name: ubuntu-1004 + driver_plugin: digitalocean + driver_config: + image_id: 14097 + flavor_id: 63 + region_id: 4 + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + run_list: + - recipe[apt] + +- name: ubuntu-1204 + driver_plugin: digitalocean + driver_config: + image_id: 1505447 + flavor_id: 63 + region_id: 4 + ssh_key_ids: <%= ENV['DIGITAL_OCEAN_SSH_KEY_IDS'] %> + ssh_key: <%= ENV['DIGITAL_OCEAN_SSH_KEY_PATH'] %> + run_list: + - recipe[apt] + +suites: + - name: default + run_list: + - recipe[rsyslog::default] + - name: relp + run_list: + - recipe[rsyslog::default] + attributes: + rsyslog: + use_relp: true + # CentOS and OmniOS do not support relp + excludes: + - centos-5.8 + - omnios-r151006c + - name: client + run_list: + - recipe[rsyslog::client] + attributes: + rsyslog: + server_ip: 10.0.0.50 + - name: server + run_list: + - recipe[rsyslog::server] diff --git a/cookbooks/rsyslog/.kitchen.yml b/cookbooks/rsyslog/.kitchen.yml new file mode 100644 index 0000000..7ede9ca --- /dev/null +++ b/cookbooks/rsyslog/.kitchen.yml @@ -0,0 +1,44 @@ +driver: + name: vagrant + +provisioner: + name: chef_zero + +platforms: + - name: centos-5.10 + - name: centos-6.5 + - name: centos-7.0 + - name: debian-6.0.10 + - name: debian-7.7 + - name: fedora-20 + - name: fedora-21 + - name: ubuntu-10.04 + - name: ubuntu-12.04 + - name: ubuntu-14.04 + +suites: + - name: default + run_list: + - recipe[rsyslog::default] + - name: relp + run_list: + - recipe[rsyslog::default] + attributes: + rsyslog: + use_relp: true + # CentOS and OmniOS do not support relp + excludes: + - centos-5.10 + - omnios-r151006c + - name: client + run_list: + - recipe[rsyslog_test::client] + attributes: + rsyslog: + server_ip: 10.0.0.50 + - name: server + run_list: + - recipe[rsyslog_test::server] + - name: input_file_provider + run_list: + - recipe[rsyslog_test::input_file_provider] diff --git a/cookbooks/rsyslog/.rubocop.yml b/cookbooks/rsyslog/.rubocop.yml new file mode 100644 index 0000000..095b019 --- /dev/null +++ b/cookbooks/rsyslog/.rubocop.yml @@ -0,0 +1,17 @@ +AllCops: + Exclude: + - vendor/**/* + - Guardfile + +AlignParameters: + Enabled: false +Encoding: + Enabled: false +HashSyntax: + Enabled: false +LineLength: + Enabled: false +MethodLength: + Max: 30 +SingleSpaceBeforeFirstArg: + Enabled: false diff --git a/cookbooks/rsyslog/.travis.yml b/cookbooks/rsyslog/.travis.yml new file mode 100644 index 0000000..3834d06 --- /dev/null +++ b/cookbooks/rsyslog/.travis.yml @@ -0,0 +1,75 @@ +language: ruby +bundler_args: --without kitchen_vagrant --without development +rvm: +- 2.1.1 +before_install: +- echo -n $DO_KEY_CHUNK_{0..30} >> ~/.ssh/id_do.base64 +- cat ~/.ssh/id_do.base64 | tr -d ' ' | base64 --decode > ~/.ssh/id_do.pem +- echo -n $EC2_KEY_CHUNK_{0..30} >> ~/.ssh/id_ec2.base64 +- cat ~/.ssh/id_ec2.base64 | tr -d ' ' | base64 --decode > ~/.ssh/id_ec2.pem +script: +- bundle exec rake travis +after_script: +- bundle exec kitchen destroy +env: + global: + - secure: VTMb359XIsFfszhrq1znF2ANHITta2gyrOloF5GXEColSH1+XB1XikcTyTVeeloHLcLAjmID2LZSbTrhfXu7gT25uKk6AKFCYFo47kIqbvjR/2hChxsZPZJLspEOKl2HyPZvA8QGBJxbTVOVXs46wt1kOui8Hqr3nLbYlKRxnjs= + - secure: bLz73tc0pbS3htWCk6O3dxMfAxQys7RCwvXtc+z9vyLry+nXmHMNBj2irLoyi6ESKdN25LvvbGYtxevwr3e3MtyUNFXhrED4DqYZ6h2PWFY7x7V9ALXdmBXpWSXiycnE9aKbWA01QMSwta8mPHRk7viscXUDX1gab93fQbKG24w= + - secure: FMBQGoAQ3voqsse/tVJHITraljEmpLb+Nz5SDWQsUWmt2aE9yREOjlaoYOHlXY6O+1TwY+Houdb8MVn9oLv4G8nEPzo1f7fS6ddhIZCFZFo6ISGyd4FTw+Ym3401n+dzGFYuZ3JS0oLCZeO5Svvww6mI9eghz6tdXHkODUsUJI0= + - secure: jjI+SZKkwG5ckifd8hgi4gFVj/mOGj4eWK/Y0fpuwu4ycxj5pg2Fa2JgqJXJrmdd4DJMiN1FAPuEftSxErq/6v2doP7knmN+7QoCwsDTsVHlAAHvitL450/PO0dkr9+AY6EnKBu/ablppnNnCxsQqjuxXXfgT1lRVKHFgp4+J9E= + - secure: T2LMLomotIluZ4o/RWxQu4MYuXhHeHkaaHHjvJ1kLdbGcc7HsO0oHSuzHdtZNgPEfHjIUOpj8U5RbMZCCwKNJPJqHlQ3HykmzmNU669NAyTaxCfXDMfdoz3un4vx13FVB0SvX3YgU9J3FQ5P6oEXJynVHoGwlV2QVZ/Hhe0oA6k= + - secure: NrL2uUpo5B39ldZXgL/nmO+DsgciVhxq3WYbX817e54/wUBBJ3KZhk995G7WpnGDaW6EGtJMfpgnIMnqpEyeknW/oi0PTe0mcYd1x1zaijZNRZ97irkb0vHcpdJOyqVpPVyFV8G/ltItRCxhotzLAu5btq0MKD93V/hAcsWD4SQ= + - secure: K1Qjt6eF7ai3362EzShsWWGSq2xG22zj4idjOf1k8pJHrJ/7g/gMaN/bDpqYkksi7R/uD8dbqMTU6l/MBjRV2jzJ7kbG359OxVzXJoFUSxKkJX8TbLoX1P/dyt82XdVyU/j6SprNkhxy2/4uYvbF31ZAsEmiegdkqDbhBxBgmBA= + - secure: DBNraE+2aj4WTBkFbGtSIsxfl0stIAuRDH4ToDMEA6S54zvAIlFYdzjRcYRSOznk9HSk9lRIF/E4Q0RiF6WqZfp7uoHBbhKMNh+SD/anQ4lTyWhxKVTs/2onbr02aP9ztM/GIq3HqvZoLKrMFp2xBj88yOzblz8DZlkNMDPve24= + - secure: KUJpq3781JaY06GVy16L9dx8zFoOANN7I5Ar3juRoKzQztgSuKsqOhFlmZZpj1xX1IJsXEYkFYCvXB72bEBIvwGQnp4TV3LJPGvB1eePrNlheu0mXcxYQSSuAzXN1cg1ok5pgx/zSQovbChEVbQBRyYNnnRkJ41PaUmG8pWaM/I= + - secure: TN+FOCJkYo5tIGp35yqbYadTyIFK10TPqNMCpsfDNK/FXTRjp7GyqhUpTVlYvtDWaOwjqQLjart+ShTnZ8SOrk19znFDZm6PGk/2zVXirtU2zvvJMqqslxrMiVXMUE1ldH4d6U7cFT1ODuvWoUv41uKnbpygGZ+0Z8DMSSRX/x0= + - secure: RkynljZ2W5q4iqvsVkG+pepauzjUZomgf/4JTeRJiekGtvcrFnq+S0YJg9J7Ey9Iry8mHb034lS5OnIHyj8NaCL0vqDJEMVblasM1pVuLc65gv7rAP+5q5pHpwYVhUuerypGJFYKTd9ynhKkTvHOV6CYcqzOEsJGls8+vgvTfwU= + - secure: RsiAYfDJFsqEkJidJEzM4BGaqUwxy2avPSw1y4yh6XXWbgbOdx/eqSPf9ewHwIXPUYdR84sbZkn6PeYWUwTEtN7k1DrE2RXDjapDW8Oxh1U3s/pCvSnh2u0e3DhgD1DBnzN8foB1MV0kseilmx0vMEyDAj8ElhYoV+wvTO1Loxc= + - secure: Equh+0TdmCt1SjnYVJbDtNPBhK5P3/XuCyvU/tgRaGrIsf5gvCfq3tNNlkrdelmJK0UvGICoCKsOTcZDpbQ3jteoBoI3+NFrwVTe10OQ5XRc+/U8duYhS0D6UKzFMWYxWAdZtfmyWOJ5xYrNaV4JRdJgTSTjXk0gOSsPluSK0IY= + - secure: WEZJ1Z51wjYZfhP11tLxWuVZu6N4ZXyoTAb9AFgMzOaULvMOZusXF6o+aZSCr07rUnjlQAg2rE+E800FRUqO3UkL18kxALvtwKmAlyxVT85NcuWbMuaSNMeZwvqGVKJuNNSe3huU9uHePbsUt4trVKA9O1n3Bb4dRJl9wmdcgoU= + - secure: bGtuK+n8hVIibGxOIE/eOCRJHlgaqp2X9KObcBNFCTxKleRm2fDnCPEMVHQJqrX+jlqMGjzGygTnZ8wA1/ZdBwjOOqi3/jaUZ25+r0owaUjS1zaTudTBC61Qcmx+N7bXc3ku8fMWo6WcByTqnqU0oQseoli75KyIRoubIIgwgcM= + - secure: AMYMIfIGAJbPqSRv4ocdsxRWwDr81Wp7hiYjgpzUMFh3SzixKzjgtQ72T9S26PnvhkFjk0QvGixb26IXS8vU0ZtFBtLblUpKvXSd4EafvQ1x/fdN5WFaBlMs01iT4jVMH+wjnSJssuyw+nC5+5/HqeONdZcRm3Hj8uqE/24Qih0= + - secure: UrmjQ1/1OCENKLDezDbh6x4/6P37ybFlyb/gz6vzm2sIpvWeL+1+SvF92xYeeAhCYAtoXU9nAvcMgwqeJIi9P2q7aoTqv9VoK+lq7L1cPqpAjcqJCAdSAH/bYzARu4I0vDbZUhTaZC8+lw9xfl701WHWasyNgcYG3HFULsWQuwI= + - secure: cRhO5SJ0cInh1yYxDKcfymI3dt3PGfPJXCan+QzLoLg7VbJ3zfRKBhjjN2NjdcLxgHUNk5//paooOPoiiOMzwo2GdEDFJjtzL7TC6sQUxP6zN1RAiAIPqjHpuCKv3IQ4hLgTiRaH92I8lpIvst7hEHmPYS6+ItEgJug+tyluaX0= + - secure: LXJ150pAwEHLSMGGxSXs5pA49SwPYsV4Phsi7+ZqZ93uAprO8yw8Pxmhaaz9Y0BSLYVzTtdJI5VbW3XDzwo2tJeMfDz1Oxzqsi5kAXt8DTQRxoVX8FeMyVNLe8TbrI6cGnJrO37u+MrsFz9BI4+TsPoLYcc6Y/zqB2FFan5RMGM= + - secure: ewFdUlqrLoFrBoSx/ikqDgTsASXPZDNjn/nHsC2nv0A5VJDHZb98AwLOnKAFUwxmOkbq4LKoPR1ltQ4KigjpFIIYrkHfRv6izo08cgyd98QVKZSdBJfNjHTe+blwVgh3t/yODxJMPmhE5/lPIC3UnwmGW933UszvkozyPkyE0ag= + - secure: Dt4Kdi/KiydSYcgCUfGQkrFJ4NBNqtNrc1Xi4gdwl+xSpEKEDFFwrGkCaTdWCzB9SKH8Cy+KoNQiToMEp6SFFZGzEAYlN0zITqObyADocmZieEpV44nj+DBneI9h89aGP6XSTYXOwIDluuchR4sD6I5eHUuD3cHTHPbzI7sm3N8= + - secure: BBs+tpDN6MQv3BlnANh/nGzqWCzRwAKmFf8M0/XgidrqXHBxsgAXmAd+jdXQlqBs7bRjT3gNp3xGf3mIRW2gV9+6YNTiFFp8YGxFRxSd7A4x7obcb8HFIyBIqQ9ZgA7R0u3dsbgtAnRclK3+9ZHoZ41e1Qd/8S0PGsry4iTMAgc= + - secure: I3SG0cro9MmyHAASsWqachUWE0kshWtxRcBVgjJHZdvP1RBT2q5KD7p46o7yKP3PD9Kv2qt6DfpSTJJhXA2leIhzUMzRu7fpPu/Llc5k+Ik74tWXdYQMJ4tf5nEtq+A/D8odKax1HuM5L7xpz53Yn3M4Haf2udDm+owk9y/tRHs= + - secure: eNeHY7wbsS8GVhONSEq1crZMdo1fdW30ezoI9xJ4LvZJmYdcMvyR590r2LoV9OZXhYTQaJM+ldWW3rluPFm0oUNoPFdbl0+KuJsTldVhVp7BEQ3rwmoFfUIS+lxWM9qH/iNkOY7A+P9InBbFPbg7WUJ+OaqrQEo7t8muwJxlodc= + - secure: OfngkucaRoVa73QoGIheD7o5DzvWgU27p7wNIsW9bSoiwraOo81E9WiZdFbtwBQXRBy/hIw3TZHrg/BqSAx6HxnubK29IhsxoJ5lkz1Nl5/yxkT/+mqecfji8zu2p1UMnV5+SGUG7df+XWrVcvCJvO4NJyRI1cTIeI79TbOvcBw= + - secure: Fjc0htSGbmkDNFZvt2s89qMlsWGBtHDjewLQDxDX+TpRNk7lc6N43U1TZ7b3G1kzX6gmsST8syplhdo8EfxqBy5thgsNyu9So8CI5/LidOneauBVH8usHIUc7DvXlIxttsC9syjIresfseBrim+aI1HzENMpcWHHcn3UIdQ8yLQ= + - secure: dFD+09BCGyZNe44kZgTF+1rIYLFJQlpx47FctopcXM6Wl/moxVJF8c7e5iaLq34J4L0SvCUHHAF7WyxUjJYgUmxT4GrdET0vDtqqurz5TWxZ+daiJQhA6trcgo/WRQxhEO8qmxgJ6x4kNDyKfhqwVs40R3ThkhEGpNGgQ3jTtoI= + - secure: ch66LvlMlLDSteEkFAykn8dYEd9dEugMiCw/cncUDE6XU8eXusbNtXaCds3kb/rAYHHpGD2IDr+ZjvnoxRdRdh+d/EOBm/9iy0ii26O8iLZdLXqpqxFqiC3rtaoN4fYe1GqAZ03QNYja+XTbNEqUIxb3aozABZLY+wtcGgvtRhE= + - secure: XtH3hwnTIVZmigTJTsNH6flmEsb7Pm2XjzPvLcCNnV6qtwCvR3cgOPiu0Vs+p5s3QGIN8SNnN+mxUEDgcfnKMn2skUalSBgFKVaQg9NiE956dUkytnTDb0KxGoMFJBzMUhEKwSWl3p5+32M0APtzwqQOSP7sYiT+u4ICz5s4aK0= + - secure: J309x5rZal7gJt7qJqFTsj4/y8vF7k4qEpd5KWMs6vjf+hDlmFVyBu3zIDo6NgFAsSazGswwK0SwzAhY20DBcvppwdvCwt2/3wz1ob/fWhNxbEVW0afBGPBXSy/XG38Ag/xZpCRfAnOI9aFzkdKPOsYw6852Vk6exEU73Vxl8OY= + - secure: Bg9S9uiL9Wn5mDIq4BZRAGjWfNJu/b1Ksj8AmobljuASQmRaPvRHrRDJNzOI+TuPb56+ihWSmijGx+sagUyxwSXTmo3wk409rCXU9uaJxQU8nVIrw6nlf5js/j+BgKNtHnYNlcGl6sopYRRS4U0wHLJEZI3Gbvw039VpcStEjEU= + - secure: O7hct2KQBJyBGaHDeA6koGfmRvziI21rIa3cLXYzYK9MfSr5WbYDPI87HnxSxwaBP+Thl0tWxmgEbtVaTmkb2OdhW0JYXqVfLTt1Vbd411/O1xLSkP1INSSNSWJpD9K2/U8nLE3aZcedj/Cosi8GMjhl7wUP07/zTCzYxpnGp4o= + - secure: RNKtHWZEHCLm/2/iqwRUH/iiX9NDxgvHyg4oWnf/Y6oZXM6LEjGRURkouH1dexzZi35ssKdkbnfW/OD/N8jAxyt6KqlvWEPLLMJrrx/TCufdy5SljMH9PfHBSdxZn+U8qeVnufmdPPA3AoxhPabhF8voYaroWAq+oWpNh9sj+3c= + - secure: dkbJ4IGwBa2Z6LVsTyukSaACuHEV6QKyXuO1CHaxI8LRwrZbRee0RTdF4fom83IUn+DQ5LqCnMI2xk4NUVOeMCaY4LthC7f/ImgIxMGCnAq74OxnxCm0CnUhb3E1spPLCa4hgjyBMxp81w/M6ga/qKAIUDLqvqHlbG+Ezf8L+yE= + - secure: g4vz9hPqJhXTbe/zJWsoQ1Rh+Ay+PC7BPkbmqYX2VJPWSsesGQgnY5TrZzEzhiKgKfOkNTItaJs4do5F28XmLOFT+WSOgQGOdg171eS5J1Nq+403hSjOoe5hAdrHbexO5YRMlyjiBeMGP2x+VLIH0ZUeVFsH1Ojc5QfyuzlNxhY= + - secure: IqJosVHlfo49EextXiSrvXtHRgZVyHpqDn0yZIerA6ldaDeNFsai7XT2l5/OsSNYVKM91xm6mWEzhb3uofGnFtCq7pfj4n96kK3zeSx1j0ZtHiiLU4VKQB6qtJuBr0CtNa3XOgKmVYi/xbwIGcRbHSUk45k9jxtPF2ZdbseLb2k= + - secure: cO7k9EiMjXqsELLLqESR8cdU3CJIyO14baXGBzhcxV8B4NEJ1w+mCW5xC8xkOPx0W1uw5kF+ieKNHTb9FuAl+13mztZfEneq5+KzQxhCVC4VLFk37z+YN4XsTtjOpoc+Sejxf7Rk+i2b6iQFgDOnpa2ujy/Kch0TyjjWoDk7z1A= + - secure: CvOZnuItfTL4pQdwh0SYV+J1xGxR5uPChnRqq8sHCIv44ltq0ZiZSu6R+8/SScWN162xW7KlxZYtOJn3obKeIAAiH7OZGOGhsl+li59W0j0ldGEGGAkruaQB8Q29z6sq8HR65fR7hJ/7elo410HgIsMUM5QD9xn5wPePHNTIuv0= + - secure: YY3DZiMltINKWUmnxR5wfzHwPGYzKp6HqHv0fydB4ekKMnxupc5Aegg3Iq9PBGZQ6cAjJprqZX8gJskD5wkPk/NutQ3gUSvpVbHleQmL+blJ1UtE9b7dJqKFDlE12q0KyCydx9hkKMK8G5OLVb/qYsqvrsbqVS2pemGYKL1ztJM= + - secure: NO0zNB/mDzx/qdN9o+jBt+AvVM1ChX5OOdA2s2czrEEZCWYPQUge+p2oNlbkl4+Cc8+N6Np2dUlFZkxIUQ/eDoQv0BVADqzhNvgZn7q1YZCAFtIVT8KJDr3+Ly1YqL9uCHk7hW6rBl+VVYatyo7XyugHneT0RzmQpYY3RKMiSaI= + - secure: KL8bAfL5Q8c5/yMUD8jGuTg52cfYTJPPExI67j8iWfz9WyohyV4wJgRw4G0s4MZP75WXZg74fk5Jhs/jbk3Q8lBtk6a9P8SHZuC7Rh/yHUmVRzYaKTr/yRaFnIBsC/mnvV6JNX5NXfSrmntGs8KA7lWGZeRLpWSOEoT0YgPRn9w= + - secure: U25VWQKe2pFluApjzrA27rHrQTB0SWU/kjnUUXPDxcyqkM5YStNeVb8g3DjT4zR3OIsNsUXCxWHVEMlzP6HQjk+dgIsrlwDi07r7NHAOXFj8m9fCrComKNUZdg79KtHWed2kiB4hrMH51oV7A0fXff51kZiKD0IoH0ySQfPQSBo= + - secure: OSyoiFqPN7IBfIXR1wi64Y8wJf4ZlGOaWYBxWNviKhtuZYyYsb1CnI+zN5kozXr5w83mDxOWnzKS1p6HHMzZD1v+3uJCWRjoLYlUDuOWRJ8OY5kqZsH5LbQ/2Qdl2YuC0YOvZwzfK7CboFLndt/V5bIcVLcQBB5dcDvfqiFW5T0= + - secure: EVdYNRFPFdH5JpULFcS4G9UcPLNwRAy0lL4xD3jVDVbfTt1XC9+GOGa/AsKx3MPFDuSGBCUkbvaq/SxrjL0Fh63X8T6jOeLzWj97qBaAg5Ih7ADSRsdfJ0spQ5K5lI/YbqlumQN+pJg+hkrO0puR7T745Et/WwTfASICr8/ZURU= + - secure: LHf2QsgQZdYiMJM325FLP7wq1eTr3jjMcNlIN6HYwF/zvGwOoqNfYgJqUZXKv45rD2eOeDfXAc2LD6cF6LN5hD+3yzvhybLPCqK4hLWvGnVxSJO2pYn0kdZdTsLMrUTCrQ7X22eFW/wk9ZM/JaefjNwJV2XN+83b+cnMiil0GZ4= + - secure: cNkBHL5W8Ca+X0gLBpHnfiWsas5SKXxbHJm1YKq8Olo/8XUk/90zZvFWSET+a7pwDOzOXVujlE/JfXA5gZVKpbG3JlVt2ntMa5JTaTVVt8GNWkW8slu5niJFdJdpL3EikhX8sgz69q+QFJTBmsDfeJzsUe01VZHsdDCIs/wsPLg= + - secure: bTRB05t28cpSCAxhqXcbDUbrx4QDnDoHQpnhjynwIq+9QuV6IMx65O6+CSL8T61z6BuPVDkvUpLQ8W74rpwtCa91XaSNuiNVciMIaq+yk8FV2gCRzzw55o79IuAFjns/JxGWLV717WR7bGxjU4zR6ybgZzuBgJMXqJSk4iNH62I= + - secure: iBqO1O6827L3eAYrm/X9hN8vsF4Gt+JVZ7kLkm1RGVLAWnQpMYODGl+xQS4WxmY+kaWtHAI8PBhM/WXQ/26d4Iljg/JPvJRH1JSB/+0bLCp+OAfpgEUajbB/6zj6RGNZnU//MyA6h6M/RUW9rES4KHvzQFAtODSyTNFojmEdqTk= + - secure: K0sT+IGy2ncdrfNcPJ5rnwT3oYB1KWK330t6msKlGi6gTwId1z4gDx+iz0ttbP7jCGbQrqj+H+ZTypV+HploVyMAcxFA/tFJsPdMjUmPC/wO2kmP1PCON4OO4+0y4OEA7AX3eBnGTXLJBtqoWb2HCeldA1gQsuM0C0WQ0sz/2lo= + - secure: jAfN1DSUxORr/9LauKKjxeflwufkG3dNOPxwAMX2mUjpV8nfP9N57D+oFsPh4vZYT7kvt2LKAVcn1mMxkXEDb9YOE4UCIB+F5fYqInJGACFUK+VNeM0sbUtkj1cNvd/V/C/cmxG1MbEyIfBy12I8Ezz2+pKpA150QYYaxyOcccQ= + - secure: MGJGwkNaElc8YxEtY2L2iV20srBejwmVqAyKMgfH2GUOEoyZ4ID5va2jAd3TZoteUN5f1wEHVNhBGfT6poYEKcoT09Sl5c81YO8Ws7oKxaSlIUwRwo4Tem+c6WQIkyqSfHaW4pl+nzv2eWBzp/EPQvbRrh6dWbDZYf8S0QqmomU= + - secure: T0osUxN/iHJ4iX/JPhC2dO+OS98SCF2vXCEpszO4LzaC7Cy2ftbzXkN3gcDc4CrbrIdwP/J2dYmBiETlrm6n8eaLNIOPuIJEigSATL4Dke66fQF3DLeSZOC10Ggu72OsIO5RJWQJxYkRjQzaK61553goRAHsIwceO63inKFaFi4= + - secure: DY27wvyRDVQ46WeQjqs7ANuUobazrUU3NT8OdSXXSN5NxBZQK17QmbOuvaCQHKOvuv3YXtKQAl/s7ztgBIH6zTDGVmaCfgJOAX3d5SAvCCHKw9/43Wh7W4ZJ1VprFU9gp1C3wLb45T1kT3rCk2IxCCZxyvepm+43w+hJFFOzFSA= + - secure: TrYhk5FHxgP4O19w56ZtatlJgFtIeBL9Ucco5KGZBvI7ynZm7Bd0k7k2qQ0Nw46FPhCRwTtqK46nx8sRn9jPmkqXGcoYgztDfI9EgD7B79lp7M4897wAa6ditKUDSL+SXG6bgngK5sZCVw749QdJX9F+NPmog5X54QNTBP27XUI= + - secure: SB5cNAQjM1WdYTl8Xe0oib+7S8lp6g0pyRIL1RhStOMjZ2rPS7eFfNUeUw8M1AA11zXXFfsBWCtf+kHI7CK7q4QC2W2129CTTxxgqHpC9hjMlOvddVsbN8vfqCHCWFnjinhvYsKdu6xM6mW6IxIQ1SjShra/u6kyfyKBWrkRvXI= + - secure: H2PPPnH66DD05RdHg8srecBrO/s21MOAqdvFgEhItzMeEmAmTYubCdFIMO4uwyA/bNJiHF0u4g7RYTpsEmW3GNbZ6DGBv2Ltqf/vBP6sTce1LxYTRMsCoYpurTQSODj11+9XU33ml+kH5uNR/CcSSOmco2I5vyBv4qKElsID8CY= + - secure: JWEwbpLg/4KevMApf+z6URboREh56AU5oqZQ7pS9JQG6MffNp1HTcu4R+rxvhM2g28kE/gSxJaEo+pgPCpsggycRC0/ZH2TDOl+WwiPRCjEhqYKkoEDxx4Ot5OP0jwahMTGT/33ihWlnl5DrgjAmvpbSlJsULGV97nYg0llDXb0= + - secure: WUDHKVsOgBFkxNOtifMT3jNOPfR8D7DpyFBWCjwrdIU6DLDFg92/2a7tq1CGCk7uWYUJFgdbCLnJPA304L6AzWKcycvZvgDKzkcmz0iNxpGMOfQPM57UeqQAYpgv+/4wnHzbn85ZXh2gzPsEBvJWaNjE2VftedKkqDXHe5Dg9L0= + - secure: eYgi6mgnI4qkcgi4c7vkR1vaZPcY67U4PeYVcJ0g3Ri1gP0l73D44M6QGpnUrmTdkULjj6ZVOWq2d6i+D8bLGN5R5kj90GzxVhxstVG86ZT7ottipvKez5G7cm1udDzXEK463EX+w4ITTY4Zp4tCS00o4ce+kxR/3ntiGBUYR2Y= + - secure: HOd08Pxhi4thM6PpyIk1PcFsjHKFziWT1VNaC+hhT2p0YIaEeOIL94OcrmYC9cm+Nv4Mns+QNwqazHUc8xyciYo7yVxGPVSxu+qc1GSXtnbEC3KCxjBoae2VEWaVJgZyv3Z525jHXdPKvURMA/+MF39ALRpReUiByi4Q2+rHoBg= diff --git a/cookbooks/rsyslog/Berksfile b/cookbooks/rsyslog/Berksfile new file mode 100644 index 0000000..90e07ca --- /dev/null +++ b/cookbooks/rsyslog/Berksfile @@ -0,0 +1,7 @@ +source 'https://supermarket.getchef.com' +metadata + +group :integration do + cookbook 'apt', '~> 2.0' + cookbook 'rsyslog_test', path: 'test/fixtures/rsyslog_test' +end diff --git a/cookbooks/rsyslog/CHANGELOG.md b/cookbooks/rsyslog/CHANGELOG.md new file mode 100644 index 0000000..963f497 --- /dev/null +++ b/cookbooks/rsyslog/CHANGELOG.md @@ -0,0 +1,167 @@ +rsyslog Cookbook CHANGELOG +========================== +This file is used to list changes made in each version of the rsyslog cookbook. + +v.2.0.0 (2015-05-18) +Note: This version includes several breaking changes for Ubuntu users. Be sure to take care when deploying these changes to production systems. + +- 49-relp.conf now properly uses the list of servers discovered in the client recipe +- Fixed a typo that prevented file-input.conf from properly templating +- Added allow_non_local attribute to allow non-local messages. This defaults to false, which preserves the previous functionality +- The rsyslog directory permissions are now properly set using the user/group attributes instead of root/root +- Properly drop permissions on Ubuntu systems to syslog/syslog. Introduces 2 new attributes to control the user/group: priv_user and priv_group +- Remove logging to /dev/xconsole in 50-default.conf on Ubuntu systems. This is generally not something you'd want to do and produces error messages at startup. + +v.1.15.0 (2015-02-23) +--------------------- +- Change minimum supported Fedora release to 20 to align with the Fedora product lifecycle +- Add supports CentOS to metadata +- Update Rubocop and Test Kitchen dependencies to the latest versions +- Update Chefspec to 4.0 +- Fix CentOS 5 support in the Kitchen config +- Fix rsyslog service notification in the file_input LWRP + +v.1.14.0 (2015-01-30) +--------------------- +- Don't attempt to use journald on Amazon Linux since Amazon Linux doesn't use systemd +- Fixed setting bad permissions on the working directory by using the rsyslog user/group variables. +- Fixed bad variable in the 49-relp.conf template that prevented Chef converges from completing. +- Removed the 'reload' action from the rsyslog service as newer rsyslog releases don't support reload. +- Updated Chefspecs to remove deprecation warnings and added additional tests. +- Removed node name from the comment block in the config files. +- Added a new file_input LWRP for defining configs. +- Added support for chef solo search cookbook. + +v1.13.0 (2014-11-25) +-------------------- +- Rsyslog's working directory is now an attribute and is set to the appropriate directory on RHEL based distros +- The working directory is now 0700 vs 0755 for additional security +- Add the ActionQueueMaxDiskSpace directive with a default of 1GB to prevent out of disk events during large buffering +- Updated RHEL / Fedora facilities to match those shipped by the distros +- Updated modules to match those used by journald (systemd) on Fedora 19+ and CentOS 7 +- Added an attribute additional_directives to pass a hash of configs. This is currently only being used to pass directives necessary for journald support on RHEL 7 / Fedora 19+ +- Added basic SUSE support +- Fixed logic that prevented Ubuntu from properly dropping privileges in Ubuntu >= 11.04 +- Removed references to rsyslog v3 in the config template +- Added a chefignore file +- Updated Gemfile with newer releases of Test Kitchen, Rubocop, and Berkshelf +- Added Fedora 20, Debian 6/7, CentOS 7, and Ubuntu 12.04/14.04 to the Test Kitchen config +- Removed an attribute that was in the Readme twice +- Updated Travis to Ruby 2.1.1 to better match Chef 12 +- Updated the Berksfile to point to Supermarket +- Refactored the specs to be more dry + +v1.12.2 (2014-02-28) +-------------------- +Fixing bug fix in rsyslog.conf + + +v1.12.0 (2014-02-27) +-------------------- +- [COOK-4021] Allow specifying default templates for local and remote +- [COOK-4126] rsyslog cookbook fails restarts due to not using upstart + + +v1.11.0 (2014-02-19) +-------------------- +### Bug +- **[COOK-4256](https://tickets.opscode.com/browse/COOK-4256)** - Fix syntax errors in default.conf on rhel + +### New Feature +- **[COOK-4022](https://tickets.opscode.com/browse/COOK-4022)** - Add use_local_ipv4 option to allow selecting internal interface on cloud systems +- **[COOK-4018](https://tickets.opscode.com/browse/COOK-4018)** - rsyslog TLS encryption support + + +v1.10.2 +------- +No change. Version bump for toolchain. + + +v1.10.0 +------- +### New Feature +- **[COOK-4021](https://tickets.opscode.com/browse/COOK-4021)** - Allow specifying default templates for local and remote + +### Improvement +- **[COOK-3876](https://tickets.opscode.com/browse/COOK-3876)** - Cater for setting rate limits + + +v1.9.0 +------ +### New Feature +- **[COOK-3736](https://tickets.opscode.com/browse/COOK-3736)** - Support OmniOS + +### Improvement +- **[COOK-3609](https://tickets.opscode.com/browse/COOK-3609)** - Add actionqueue to remote rsyslog configurations + +### Bug +- **[COOK-3608](https://tickets.opscode.com/browse/COOK-3608)** - Add 50-default template knobs +- **[COOK-3600](https://tickets.opscode.com/browse/COOK-3600)** - SmartOS support + + +v1.8.0 +------ +### Improvement +- **[COOK-3573](https://tickets.opscode.com/browse/COOK-3573)** - Add Test Kitchen, Specs, and Travis CI + +### New Feature +- **[COOK-3435](https://tickets.opscode.com/browse/COOK-3435)** - Add support for relp + +v1.7.0 +------ +### Improvement +- **[COOK-3253](https://tickets.opscode.com/browse/COOK-3253)** - Enable repeated message reduction +- **[COOK-3190](https://tickets.opscode.com/browse/COOK-3190)** - Allow specifying which logs to send to remote server +- **[COOK-2355](https://tickets.opscode.com/browse/COOK-2355)** - Support forwarding events to more than one server + +v1.6.0 +------ +### New Feature +- [COOK-2831]: enable high precision timestamps + +### Bug +- [COOK-2377]: calling node.save has adverse affects on nodes relying on a searched node's ohai attributes +- [COOK-2521]: rsyslog cookbook incorrectly sets directory ownership to rsyslog user +- [COOK-2540]: Syslogd needs to be disabled before starting rsyslogd on RHEL 5 + +### Improvement +- [COOK-2356]: rsyslog service supports status. Service should use it. +- [COOK-2357]: rsyslog cookbook copies in wrong defaults file on Ubuntu !9.10/10.04 + +v1.5.0 +------ +- [COOK-2141] - Add `$PreserveFQDN` configuration directive + +v1.4.0 +------ +- [COOK-1877] - RHEL 6 support and refactoring + +v1.3.0 +------ +- [COOK-1189] - template change does not restart rsyslog on Ubuntu + +This actually went into 1.2.0 with action `:reload`, but that change has been reverted and the action is back to `:restart`. + +v1.2.0 +------ +- [COOK-1678] - syslog user does not exist on debian 6.0 and ubuntu versions lower than 11.04 +- [COOK-1650] - enable max message size configuration via attribute + +v1.1.0 +------ +Changes from COOK-1167: + +- More versatile server discovery - use the IP as an attribute, or use search (see README) +- Removed cron dependency. +- Removed log archival; logrotate is recommended. +- Add an attribute to select the per-host directory in the log dir +- Works with Chef Solo now. +- Set debian/ubuntu default user and group. Drop privileges to `syslog.adm`. + + +v1.0.0 +------ +- [COOK-836] - use an attribute to specify the role to search for instead of relying on the rsyslog['server'] attribute. +- Clean up attribute usage to use strings instead of symbols. +- Update this README. +- Better handling for chef-solo. diff --git a/cookbooks/rsyslog/CONTRIBUTING.md b/cookbooks/rsyslog/CONTRIBUTING.md new file mode 100644 index 0000000..d876116 --- /dev/null +++ b/cookbooks/rsyslog/CONTRIBUTING.md @@ -0,0 +1,196 @@ +# Contributing to Chef Cookbooks + +We are glad you want to contribute to Chef Cookbooks! The first +step is the desire to improve the project. If you're new to the Chef +community, please read +[How to become a contributor](https://supermarket.getchef.com/become-a-contributor) +on the Supermarket website for more information. + +## Quick-contribute + +* Create an account on the [Supermarket](http://supermarket.getchef.com) +* Sign our contributor agreement (CLA)[online](https://supermarket.getchef.com/ccla-signatures/new) +* Visit the Github page for the project. +* Fork the repository +* Create a feature branch for your change. +* Create a Pull Request for your change. + +We regularly review contributions and will get back to you if we have +any suggestions or concerns. + +## The Apache License and the CLA/CCLA + +Licensing is very important to open source projects, it helps ensure +the software continues to be available under the terms that the author +desired. Chef uses the Apache 2.0 license to strike a balance between +open contribution and allowing you to use the software however you +would like to. + +The license tells you what rights you have that are provided by the +copyright holder. It is important that the contributor fully +understands what rights they are licensing and agrees to them. +Sometimes the copyright holder isn't the contributor, most often when +the contributor is doing work for a company. + +To make a good faith effort to ensure these criteria are met, Chef +Software Inc requires a Contributor License Agreement (CLA) or a Corporate +Contributor License Agreement (CCLA) for all contributions. This is +without exception due to some matters not being related to copyright +and to avoid having to continually check with our lawyers about small +patches. + +It only takes a few minutes to complete a CLA, and you retain the +copyright to your contribution. + +You can complete our contributor agreement (CLA) +[online](https://supermarket.getchef.com/ccla-signatures/new) If +you're contributing on behalf of your employer, have your employer +fill out our +[Corporate CLA](https://supermarket.getchef.com/ccla-signatures/new) +instead. + +## Using git + +You can get a quick copy of the repository for this cookbook by +running `git clone git://github.com/opscode-coobkooks/COOKBOOKNAME.git`. + +For collaboration purposes, it is best if you create a Github account +and fork the repository to your own account. Once you do this you will +be able to push your changes to your Github repository for others to +see and use. + +If you have another repository in your GitHub account named the same +as the cookbook, we suggest you suffix the repository with -cookbook. + +### Branches and Commits + +Create a _topic branch_ and a pull request on Github. It is a best +practice to have your commit message have a _summary line_ followed by +an empty line and then a brief description of the commit. This also +helps other contributors understand the purpose of changes to the +code. + +If your branch has multiple commits, please quash them into a +single commit. If the PR is addressing an issue in the Github issue +tracker, please reference it in the summary line. + + [#42] - platform_family and style + + * use platform_family for platform checking + * update notifies syntax to "resource_type[resource_name]" instead of + resources() lookup + * #40 - delete config files dropped off by packages in conf.d + * dropped debian 4 support because all other platforms have the same + values, and it is older than "old stable" debian release + +Remember that not all users use Chef in the same way or on the same +operating systems as you, so it is helpful to be clear about your use +case and change so they can understand it even when it doesn't apply +to them. + +### More information + +Additional help with git is available on the +[Working with Git](http://wiki.opscode.com/display/chef/Working+with+Git) +wiki page. + +## Functional and Unit Tests + +This cookbook is set up to run tests under +[Kitchen-ci's test-kitchen](https://github.com/test-kitchen/test-kitchen). +It uses Serverspec or Bats to perform integration tests after the node +has been converged. + +Test kitchen should run completely without exception using the default +[baseboxes provided by Chef](https://github.com/opscode/bento). +Because Test Kitchen creates VirtualBox machines and runs through +every configuration in the Kitchenfile, it may take some time for +these tests to complete. + +If your changes are only for a specific recipe, run only its +configuration with Test Kitchen. If you are adding a new recipe, or +other functionality such as a LWRP or definition, please add +appropriate tests and ensure they run with Test Kitchen. + +If any don't pass, investigate them before submitting your patch. + +Any new feature should have unit tests included with the patch with +good code coverage to help protect it from future changes. Similarly, +patches that fix a bug or regression should have a _regression test_. +Simply put, this is a test that would fail without your patch but +passes with it. The goal is to ensure this bug doesn't regress in the +future. Consider a regular expression that doesn't match a certain +pattern that it should, so you provide a patch and a test to ensure +that the part of the code that uses this regular expression works as +expected. Later another contributor may modify this regular expression +in a way that breaks your use cases. The test you wrote will fail, +signalling to them to research your ticket and use case and accounting +for it. + +If you need help writing tests, please ask on the Chef Developer's +mailing list, or the #chef-hacking IRC channel. + +## Code Review + +Chef regularly reviews code contributions and provides suggestions +for improvement in the code itself or the implementation. + +Depending on the project, these tickets are then merged within a week +or two, depending on the current release cycle. + +## Release Cycle + +The versioning for Chef Cookbook projects is X.Y.Z. + +* X is a major release, which may not be fully compatible with prior + major releases +* Y is a minor release, which adds both new features and bug fixes +* Z is a patch release, which adds just bug fixes + +Releases of Chef's cookbooks are usually announced on the Chef user +mailing list. Releases of several cookbooks may be batched together +and announced on the [Chef Blog](http://www.getchef.com/blog). + +## Working with the community + +These resources will help you learn more about Chef and connect to +other members of the Chef community: + +* [chef](http://lists.opscode.com/sympa/info/chef) and + [chef-dev](http://lists.opscode.com/sympa/info/chef-dev) mailing + lists +* #chef and #chef-hacking IRC channels on irc.freenode.net +* [Community Cookbook site](http://community.opscode.com) +* [Chef wiki](http://wiki.opscode.com/display/chef) +* Chef, Inc [product page](http://www.getchef.com/chef) + +## Cookbook Contribution Do's and Don't's + +Please do include tests for your contribution. If you need help, ask +on the [chef-dev mailing list](http://lists.opscode.com/sympa/info/chef-dev) +or the [#chef-hacking IRC channel](http://community.opscode.com/chat/chef-hacking). +Not all platforms that a cookbook supports may be supported by Test +Kitchen. Please provide evidence of testing your contribution if it +isn't trivial so we don't have to duplicate effort in testing. Chef +10.14+ "doc" formatted output is sufficient. + +Please do indicate new platform (families) or platform versions in the +commit message, and update the relevant ticket. + +If a contribution adds new platforms or platform versions, indicate +such in the body of the commit message(s). + + git commit -m 'Updated pool resource to correctly delete.' + +Please do ensure that your changes do not break or modify behavior for +other platforms supported by the cookbook. For example if your changes +are for Debian, make sure that they do not break on CentOS. + +Please do not modify the version number in the metadata.rb, Chef +Software, Inc will select the appropriate version based on the release +cycle information above. + +Please do not update the CHANGELOG.md for a new version. Not all +changes to a cookbook may be merged and released in the same versions. +Opscode will update the CHANGELOG.md when releasing a new version of +the cookbook. diff --git a/cookbooks/rsyslog/Gemfile b/cookbooks/rsyslog/Gemfile new file mode 100644 index 0000000..16a38d5 --- /dev/null +++ b/cookbooks/rsyslog/Gemfile @@ -0,0 +1,37 @@ +source 'https://rubygems.org' + +group :lint do + gem 'foodcritic', '~> 4.0' + gem 'rubocop', '~> 0.31' + gem 'rainbow', '< 2.0' + gem 'rake' +end + +group :unit do + gem 'berkshelf', '~> 3.2.0' + gem 'chefspec', '~> 4.0' +end + +group :kitchen_common do + gem 'test-kitchen', '~> 1.4' +end + +group :kitchen_vagrant do + gem 'kitchen-vagrant', '~> 0.18' +end + +group :kitchen_cloud do + gem 'kitchen-digitalocean' + gem 'kitchen-ec2' +end + +group :development do + gem 'ruby_gntp' + gem 'growl' + gem 'rb-fsevent' + gem 'guard', '~> 2.4' + gem 'guard-kitchen' + gem 'guard-foodcritic' + gem 'guard-rspec' + gem 'guard-rubocop' +end diff --git a/cookbooks/rsyslog/Guardfile b/cookbooks/rsyslog/Guardfile new file mode 100644 index 0000000..11dc1de --- /dev/null +++ b/cookbooks/rsyslog/Guardfile @@ -0,0 +1,35 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +# guard 'kitchen' do +# watch(%r{test/.+}) +# watch(%r{^recipes/(.+)\.rb$}) +# watch(%r{^attributes/(.+)\.rb$}) +# watch(%r{^files/(.+)}) +# watch(%r{^templates/(.+)}) +# watch(%r{^providers/(.+)\.rb}) +# watch(%r{^resources/(.+)\.rb}) +# end + +guard 'foodcritic', cookbook_paths: '.', all_on_start: false do + watch(%r{attributes/.+\.rb$}) + watch(%r{providers/.+\.rb$}) + watch(%r{recipes/.+\.rb$}) + watch(%r{resources/.+\.rb$}) + watch('metadata.rb') +end + +guard 'rubocop', all_on_start: false do + watch(%r{attributes/.+\.rb$}) + watch(%r{providers/.+\.rb$}) + watch(%r{recipes/.+\.rb$}) + watch(%r{resources/.+\.rb$}) + watch('metadata.rb') +end + +guard :rspec, cmd: 'bundle exec rspec', all_on_start: false, notification: false do + watch(%r{^libraries/(.+)\.rb$}) + watch(%r{^spec/(.+)_spec\.rb$}) + watch(%r{^(recipes)/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { 'spec' } +end diff --git a/cookbooks/rsyslog/LICENSE b/cookbooks/rsyslog/LICENSE new file mode 100644 index 0000000..11069ed --- /dev/null +++ b/cookbooks/rsyslog/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/cookbooks/rsyslog/README.md b/cookbooks/rsyslog/README.md new file mode 100644 index 0000000..f7cc337 --- /dev/null +++ b/cookbooks/rsyslog/README.md @@ -0,0 +1,247 @@ +rsyslog Cookbook +================ +[![Build Status](https://secure.travis-ci.org/opscode-cookbooks/rsyslog.png?branch=master)](http://travis-ci.org/opscode-cookbooks/rsyslog) + +Installs and configures rsyslog to replace sysklogd for client and/or server use. By default, the service will be configured to log to files on local disk. See the Recipes and Examples sections for other uses. + + +Requirements +------------ +### Platforms +Tested on: +- Ubuntu 10.04+ +- Red Hat / CentOS 5+ +- Fedora 20+ +- OmniOS r151006c + +### Other +To use the `recipe[rsyslog::client]` recipe, you'll need to set up the `rsyslog.server_search` or `rsyslog.server_ip` attributes. See the __Recipes__ and __Examples__ sections below. + + +Attributes +---------- +See `attributes/default.rb` for default values. + +* `node['rsyslog']['log_dir']` - If the node is an rsyslog server, this specifies the directory where the logs should be stored. +* `node['rsyslog']['working_dir']` - The temporary working directory where messages are buffered +* `node['rsyslog']['server']` - Determined automatically and set to true on the server. +* `node['rsyslog']['server_ip']` - If not defined then search will be used to determine rsyslog server. Default is `nil`. This can be a string or an array. +* `node['rsyslog']['server_search']` - Specify the criteria for the server search operation. Default is `role:loghost`. +* `node['rsyslog']['protocol']` - Specify whether to use `udp` or `tcp` for remote loghost. Default is `tcp`. +* `node['rsyslog']['port']` - Specify the port which rsyslog should connect to a remote loghost. +* `node['rsyslog']['remote_logs']` - Specify wether to send all logs to a remote server (client option). Default is `true`. +* `node['rsyslog']['per_host_dir']` - "PerHost" directories for template statements in `35-server-per-host.conf`. Default value is the previous cookbook version's value, to preserve compatibility. See __server__ recipe below. +* `node['rsyslog']['priv_seperation']` - Whether to use privilege separation or not. +* `node['rsyslog']['priv_user']` - User to run as when using privilege separation. Defult is `node['rsyslog']['user']` +* `node['rsyslog']['priv_group']` - Group to run as when using privilege separation. Defult is `node['rsyslog']['group']` +* `node['rsyslog']['max_message_size']` - Specify the maximum allowed message size. Default is 2k. +* `node['rsyslog']['user']` - Who should own the configuration files and directories +* `node['rsyslog']['group']` - Who should group-own the configuration files and directories +* `node['rsyslog']['defaults_file']` - The full path to the defaults/sysconfig file for the service. +* `node['rsyslog']['service_name']` - The platform-specific name of the service +* `node['rsyslog']['preserve_fqdn']` - Value of the `$PreserveFQDN` configuration directive in `/etc/rsyslog.conf`. Default is 'off' for compatibility purposes. +* `node['rsyslog']['high_precision_timestamps']` - Enable high precision timestamps, instead of the "old style" format. Default is 'false'. +* `node['rsyslog']['repeated_msg_reduction']` - Value of `$RepeatedMsgReduction` configuration directive in `/etc/rsyslog.conf`. Default is 'on' +* `node['rsyslog']['logs_to_forward']` - Specifies what logs should be sent to the remote rsyslog server. Default is all ( \*.\* ). +* `node['rsyslog']['default_log_dir']` - log directory used in `50-default.conf` template, defaults to `/var/log` +* `node['rsyslog']['default_facility_logs']` - Hash containing log facilities and destinations used in `50-default.conf` template. +* `node['rsyslog']['default_file_template']` - The name of a pre-defined log format template (ie - RSYSLOG_FileFormat), used for local log files. +* `node['rsyslog']['rate_limit_interval']` - Value of the $SystemLogRateLimitInterval configuration directive in `/etc/rsyslog.conf`. Default is nil, leaving it to the platform default. +* `node['rsyslog']['rate_limit_burst']` - Value of the $SystemLogRateLimitBurst configuration directive in `/etc/rsyslog.conf`. Default is nil, leaving it to the platform default. +* `node['rsyslog']['action_queue_max_disk_space']` - Max amount of disk space the disk-assisted queue is allowed to use ([more info](http://www.rsyslog.com/doc/queues.html)). +* `node['rsyslog']['enable_tls']` - Whether or not to enable TLS encryption. When enabled, forces protocol to `tcp`. Default is `false`. +* `node['rsyslog']['tls_ca_file']` - Path to TLS CA file. Required for both server and clients. +* `node['rsyslog']['tls_certificate_file']` - Path to TLS certificate file. Required for server, optional for clients. +* `node['rsyslog']['tls_key_file']` - Path to TLS key file. Required for server, optional for clients. +* `node['rsyslog']['tls_auth_mode']` - Value for `$InputTCPServerStreamDriverAuthMode`/`$ActionSendStreamDriverAuthMode`, determines whether client certs are validated. Defaults to `anon` (no validation). +* `node['rsyslog']['use_local_ipv4']` - Whether or not to make use the remote local IPv4 address on cloud systems when searching for servers (where available). Default is 'false'. +* `node['rsyslog']['allow_non_local']` - Whether or not to allow non-local messages. If 'false', incoming messages are only allowed from 127.0.0.1. Default is 'false'. +* `node['rsyslog']['additional_directives']` - Hash of additional directives and their values to place in the main rsyslog config file + +Recipes +------- +### default +Installs the rsyslog package, manages the rsyslog service and sets up basic configuration for a standalone machine. + +### client +Includes `recipe[rsyslog]`. + +Uses `node['rsyslog']['server_ip']` or Chef search (in that precedence order) to determine the remote syslog server's IP address. If search is used, the search query will look for the first `ipaddress` returned from the criteria specified in `node['rsyslog']['server_search']`. + +If the node itself is a rsyslog server ie it has `rsyslog.server` set to true then the configuration is skipped. + +If the node had an `/etc/rsyslog.d/35-server-per-host.conf` file previously configured, this file gets removed to prevent duplicate logging. + +Any previous logs are not cleaned up from the `log_dir`. + +### server +Configures the node to be a rsyslog server. The chosen rsyslog server node should be defined in the `server_ip` attribute or resolvable by the specified search criteria specified in `node['rsyslog']['server_search]` (so that nodes making use of the `client` recipe can find the server to log to). + +This recipe will create the logs in `node['rsyslog']['log_dir']`, and the configuration is in `/etc/rsyslog.d/server.conf`. This recipe also removes any previous configuration to a remote server by removing the `/etc/rsyslog.d/remote.conf` file. + +The cron job used in the previous version of this cookbook is removed, but it does not remove any existing cron job from your system (so it doesn't break anything unexpectedly). We recommend setting up logrotate for the logfiles instead. + +The `log_dir` will be concatenated with `per_host_dir` to store the logs for each client. Modify the attribute to have a value that is allowed by rsyslogs template matching values, see the rsyslog documentation for this. + +Directory structure: + +```erb +<%= @log_dir %>/<%= @per_host_dir %>/"logfile" +``` + +For example for the system with hostname `www`: + +```text +/srv/rsyslog/2011/11/19/www/messages +``` + +For example, to change this to just the hostname, set the attribute `node['rsyslog']['per_host_dir']` via a role: + +```ruby +"rsyslog" => { "per_host_dir" => "%HOSTNAME%" } +``` + +At this time, the server can only listen on UDP *or* TCP. + +Resources +========= + +file_input +---------- + +Configures a (text file input +monitor)[http://www.rsyslog.com/doc/imfile.html] to push a log file into +rsyslog. + +Attributes: +* `name`: name of the resource, also used for the syslog tag. Required. +* `file`: file path for input file to monitor. Required. +* `priority`: config order priority. Defaults to `99`. +* `severity`: syslog severity. Must be one of `emergency`, `alert`, +`critical`, `error`, `warning`, `notice`, `info` or `debug`. If +undefined, rsyslog interprets this as `notice`. +* `facility`: syslog facility. Must be one of `auth`, `authpriv`, +`daemon`, `cron`, `ftp`, `lpr`, `kern`, `mail`, `news`, `syslog`, +`user`, `uucp`, `local0`, ... , `local7`. If undefined, rsyslog +interprets this as `local0`. +* `cookbook`: cookbook containing the template. Defaults to `rsyslog`. +* `source`: template file source. Defaults to `file-input.conf.erb` + + +Usage +===== +Use `recipe[rsyslog]` to install and start rsyslog as a basic configured service for standalone systems. + +Use `recipe[rsyslog::client]` to have nodes log to a remote server (which is found via the `server_ip` attribute or by the recipe's search call -- see __client__) + +Use `recipe[rsyslog::server]` to set up a rsyslog server. It will listen on `node['rsyslog']['port']` protocol `node['rsyslog']['protocol']`. + +If you set up a different kind of centralized loghost (syslog-ng, graylog2, logstash, etc), you can still send log messages to it as long as the port and protocol match up with the server software. See __Examples__ + +Use `rsyslog_file_input` within your recipes to forward log files to +your remote syslog server. + + +### Examples +A `base` role (e.g., roles/base.rb), applied to all nodes so they are syslog clients: + +```ruby +name "base" +description "Base role applied to all nodes +run_list("recipe[rsyslog::client]") +``` + +Then, a role for the loghost (should only be one): + +```ruby +name "loghost" +description "Central syslog server" +run_list("recipe[rsyslog::server]") +``` + +By default this will set up the clients search for a node with the `loghost` role to talk to the server on TCP port 514. Change the `protocol` and `port` rsyslog attributes to modify this. + +If you want to specify another syslog compatible server with a role other than loghost, simply fill free to use the `server_ip` attribute or the `server_search` attribute. + +Example role that sets the per host directory: + +```ruby +name "loghost" +description "Central syslog server" +run_list("recipe[rsyslog::server]") +default_attributes( + "rsyslog" => { "per_host_dir" => "%HOSTNAME%" } +) +``` + +Default rsyslog options are rendered for RHEL family platforms, in `/etc/rsyslog.d/50-default.conf` +with other platforms using a configuration like Debian family defaults. You can override these +log facilities and destinations using the `rsyslog['default_facility_logs']` hash. + +```ruby +name "facility_log_example" +run_list("recipe[rsyslog::default]") +default_attributes( + "rsyslog" => { + "facility_logs" => { + '*.info;mail.none;authpriv.none;cron.none' => "/var/log/messages", + 'authpriv' => '/var/log/secure', + 'mail.*' => '-/var/log/maillog', + '*.emerg' => '*' + } + } +) +``` + +Development +----------- +This section details "quick development" steps. For a detailed explanation, see [[Contributing.md]]. + +1. Clone this repository from GitHub: + + $ git clone git@github.com:opscode-cookbooks/rsyslog.git + +2. Create a git branch + + $ git checkout -b my_bug_fix + +3. Install dependencies: + + $ bundle install + +4. Make your changes/patches/fixes, committing appropriately +5. **Write tests** +6. Run the tests: + - bundle exec foodcritic -f any . + - bundle exec rspec + - bundle exec rubocop + - bundle exec kitchen test + + In detail: + - Foodcritic will catch any Chef-specific style errors + - RSpec will run the unit tests + - Rubocop will check for Ruby-specific style errors + - Test Kitchen will run and converge the recipes + + +License & Authors +----------------- +- Author:: Joshua Timberman () +- Author:: Denis Barishev () +- Author:: Tim Smith () + +```text +Copyright:: 2009-2015, Chef Software, Inc + +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/cookbooks/rsyslog/Rakefile b/cookbooks/rsyslog/Rakefile new file mode 100644 index 0000000..965b4bf --- /dev/null +++ b/cookbooks/rsyslog/Rakefile @@ -0,0 +1,59 @@ +require 'rspec/core/rake_task' +require 'rubocop/rake_task' +require 'foodcritic' +require 'kitchen' + +# Style tests. Rubocop and Foodcritic +namespace :style do + desc 'Run Ruby style checks' + RuboCop::RakeTask.new(:ruby) + + desc 'Run Chef style checks' + FoodCritic::Rake::LintTask.new(:chef) do |t| + t.options = { + fail_tags: ['any'], + tags: ['~FC005'] + } + end +end + +desc 'Run all style checks' +task style: ['style:chef', 'style:ruby'] + +# Rspec and ChefSpec +desc 'Run ChefSpec examples' +RSpec::Core::RakeTask.new(:spec) + +# Integration tests. Kitchen.ci +namespace :integration do + desc 'Run Test Kitchen with Vagrant' + task :vagrant do + Kitchen.logger = Kitchen.default_file_logger + Kitchen::Config.new.instances.each do |instance| + instance.test(:always) + end + end + + desc 'Run Test Kitchen with cloud plugins' + task :cloud do + run_kitchen = true + if ENV['TRAVIS'] == 'true' && ENV['TRAVIS_PULL_REQUEST'] != 'false' + run_kitchen = false + end + + if run_kitchen + Kitchen.logger = Kitchen.default_file_logger + @loader = Kitchen::Loader::YAML.new(project_config: './.kitchen.cloud.yml') + config = Kitchen::Config.new(loader: @loader) + config.instances.each do |instance| + instance.test(:always) + end + end + end +end + +desc 'Run all tests on Travis' +task travis: ['style', 'spec', 'integration:cloud'] + +# Default +task default: ['style', 'spec', 'integration:vagrant'] diff --git a/cookbooks/rsyslog/TESTING.md b/cookbooks/rsyslog/TESTING.md new file mode 100644 index 0000000..5b20bb8 --- /dev/null +++ b/cookbooks/rsyslog/TESTING.md @@ -0,0 +1,187 @@ +TESTING doc +======================== + +Bundler +------- +A ruby environment with Bundler installed is a prerequisite for using +the testing harness shipped with this cookbook. At the time of this +writing, it works with Ruby 2.0 and Bundler 1.5.3. All programs +involved, with the exception of Vagrant, can be installed by cd'ing +into the parent directory of this cookbook and running "bundle install" + +Rakefile +-------- +The Rakefile ships with a number of tasks, each of which can be ran +individually, or in groups. Typing "rake" by itself will perform style +checks with Rubocop and Foodcritic, ChefSpec with rspec, and +integration with Test Kitchen using the Vagrant driver by +default.Alternatively, integration tests can be ran with Test Kitchen +cloud drivers. + +``` +$ rake -T +rake integration:cloud # Run Test Kitchen with cloud plugins +rake integration:vagrant # Run Test Kitchen with Vagrant +rake spec # Run ChefSpec examples +rake style # Run all style checks +rake style:chef # Lint Chef cookbooks +rake style:ruby # Run Ruby style checks +rake travis # Run all tests on Travis +``` + +Style Testing +------------- +Ruby style tests can be performed by Rubocop by issuing either +``` +bundle exec rubocop +``` +or +``` +rake style:ruby +``` + +Chef style tests can be performed with Foodcritic by issuing either +``` +bundle exec foodcritic +``` +or +``` +rake style:chef +``` + +Spec Testing +------------- +Unit testing is done by running Rspec examples. Rspec will test any +libraries, then test recipes using ChefSpec. This works by compiling a +recipe (but not converging it), and allowing the user to make +assertions about the resource_collection. + +Integration Testing +------------------- +Integration testing is performed by Test Kitchen. Test Kitchen will +use either the Vagrant driver or various cloud drivers to instantiate +machines and apply cookbooks. After a successful converge, tests are +uploaded and ran out of band of Chef. Tests should be designed to +ensure that a recipe has accomplished its goal. + +Integration Testing using Vagrant +--------------------------------- +Integration tests can be performed on a local workstation using +Virtualbox or VMWare. Detailed instructions for setting this up can be +found at the [Bento](https://github.com/opscode/bento) project web site. + +Integration tests using Vagrant can be performed with either +``` +bundle exec kitchen test +``` +or +``` +rake integration:vagrant +``` + +Integration Testing using Cloud providers +----------------------------------------- +Integration tests can be performed on cloud providers using +Test Kitchen plugins. This cookbook ships a ```.kitchen.cloud.yml``` +that references environmental variables present in the shell that +```kitchen test``` is ran from. These usually contain authentication +tokens for driving IaaS APIs, as well as the paths to ssh private keys +needed for Test Kitchen log into them after they've been created. + +Examples of environment variables being set in ```~/.bash_profile```: +``` +# digital_ocean +export DIGITAL_OCEAN_CLIENT_ID='your_bits_here' +export DIGITAL_OCEAN_API_KEY='your_bits_here' +export DIGITAL_OCEAN_SSH_KEY_IDS='your_bits_here' + +# aws +export AWS_ACCESS_KEY_ID='your_bits_here' +export AWS_SECRET_ACCESS_KEY='your_bits_here' +export AWS_KEYPAIR_NAME='your_bits_here' + +# joyent +export SDC_CLI_ACCOUNT='your_bits_here' +export SDC_CLI_IDENTITY='your_bits_here' +export SDC_CLI_KEY_ID='your_bits_here' +``` + +Integration tests using cloud drivers can be performed with either +``` +export KITCHEN_YAML=.kitchen.cloud.yml +bundle exec kitchen test +``` +or +``` +rake integration:cloud +``` + +Digital Ocean Hint +------------------ +At the time of this writing, you cannot find the numerical values +needed for your SSH_KEY_IDS from the GUI. Instead, you will need to +access the API from the command line. + + curl -L 'https://api.digitalocean.com/ssh_keys/?client_id=your_bits_here&api_key=your_bits_here' + +Words about .travis.yml +----------------------- +In order for Travis to perform integration tests on public cloud +providers, two major things need to happen. First, the environment +variables referenced by ```.kitchen.cloud.yml``` need to be made +available. Second, the private half of the ssh keys needed to log into +machines need to be dropped off on the machine. + +The first part is straight forward. The travis gem can encrypt +environment variables against the public key on the Travis repository +and add them to the .travis.yml. + +``` +gem install travis +travis encrypt AWS_ACCESS_KEY_ID='your_bits_here' --add +travis encrypt AWS_SECRET_ACCESS_'your_bits_here' --add +travis encrypt AWS_KEYPAIR_NAME='your_bits_here' --add +travis encrypt EC2_SSH_KEY_PATH='~/.ssh/id_ec2.pem' --add + +travis encrypt DIGITAL_OCEAN_CLIENT_ID='your_bits_here' --add +travis encrypt DIGITAL_OCEAN_API_KEY='your_bits_here' --add +travis encrypt DIGITAL_OCEAN_SSH_KEY_IDS='your_bits_here' --add +travis encrypt DIGITAL_OCEAN_SSH_KEY_PATH='~/.ssh/id_do.pem' --add +``` + +The second part is a little more complicated. Travis ENV variables are +restricted to 90 bytes, and will not fit an entire SSH key. This can +be worked around by breaking them up into 90 byte chunks, stashing +them into ENV variables, then digging them out in the +```before_install``` section of .travis.yml + +Here is an AWK script to do the encoding. +``` +base64 ~/.ssh/travisci_cook_digitalocean.pem | \ +awk '{ + j=0; + for( i=1; i> ~/.ssh/id_do.base64 +- cat ~/.ssh/id_do.base64 | tr -d ' ' | base64 --decode > ~/.ssh/id_do.pem + - echo -n $EC2_KEY_CHUNK_{0..30} >> ~/.ssh/id_ec2.base64 + - cat ~/.ssh/id_ec2.base64 | tr -d ' ' | base64 --decode > ~/.ssh/id_ec2.pem +``` + diff --git a/cookbooks/rsyslog/attributes/default.rb b/cookbooks/rsyslog/attributes/default.rb new file mode 100644 index 0000000..6ecd45f --- /dev/null +++ b/cookbooks/rsyslog/attributes/default.rb @@ -0,0 +1,124 @@ +# +# Cookbook Name:: rsyslog +# Attributes:: default +# +# Copyright 2009-2014, Chef Software, Inc. +# +# 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. +# + +default['rsyslog']['default_log_dir'] = '/var/log' +default['rsyslog']['log_dir'] = '/srv/rsyslog' +default['rsyslog']['working_dir'] = '/var/spool/rsyslog' +default['rsyslog']['server'] = false +default['rsyslog']['use_relp'] = false +default['rsyslog']['relp_port'] = 20_514 +default['rsyslog']['protocol'] = 'tcp' +default['rsyslog']['port'] = 514 +default['rsyslog']['server_ip'] = nil +default['rsyslog']['server_search'] = 'role:loghost' +default['rsyslog']['remote_logs'] = true +default['rsyslog']['per_host_dir'] = '%$YEAR%/%$MONTH%/%$DAY%/%HOSTNAME%' +default['rsyslog']['max_message_size'] = '2k' +default['rsyslog']['preserve_fqdn'] = 'off' +default['rsyslog']['high_precision_timestamps'] = false +default['rsyslog']['repeated_msg_reduction'] = 'on' +default['rsyslog']['logs_to_forward'] = '*.*' +default['rsyslog']['enable_imklog'] = true +default['rsyslog']['config_prefix'] = '/etc' +default['rsyslog']['default_file_template'] = nil +default['rsyslog']['default_remote_template'] = nil +default['rsyslog']['rate_limit_interval'] = nil +default['rsyslog']['rate_limit_burst'] = nil +default['rsyslog']['enable_tls'] = false +default['rsyslog']['action_queue_max_disk_space'] = '1G' +default['rsyslog']['tls_ca_file'] = nil +default['rsyslog']['tls_certificate_file'] = nil +default['rsyslog']['tls_key_file'] = nil +default['rsyslog']['tls_auth_mode'] = 'anon' +default['rsyslog']['use_local_ipv4'] = false +default['rsyslog']['allow_non_local'] = false +default['rsyslog']['additional_directives'] = {} + +# The most likely platform-specific attributes +default['rsyslog']['service_name'] = 'rsyslog' +default['rsyslog']['user'] = 'root' +default['rsyslog']['group'] = 'adm' +default['rsyslog']['priv_seperation'] = false +default['rsyslog']['priv_user'] = nil +default['rsyslog']['priv_group'] = nil +default['rsyslog']['modules'] = %w(imuxsock imklog) + +# platform family specific attributes +case node['platform_family'] +when 'rhel', 'fedora' + default['rsyslog']['working_dir'] = '/var/lib/rsyslog' + # format { facility => destination } + default['rsyslog']['default_facility_logs'] = { + '*.info;mail.none;authpriv.none;cron.none' => "#{node['rsyslog']['default_log_dir']}/messages", + 'authpriv.*' => "#{node['rsyslog']['default_log_dir']}/secure", + 'mail.*' => "-#{node['rsyslog']['default_log_dir']}/maillog", + 'cron.*' => "#{node['rsyslog']['default_log_dir']}/cron", + '*.emerg' => '*', + 'uucp,news.crit' => "#{node['rsyslog']['default_log_dir']}/spooler", + 'local7.*' => "#{node['rsyslog']['default_log_dir']}/boot.log" + } + # RHEL >= 7 and Fedora >= 19 use journald in systemd. Amazon Linux doesn't. + if node['platform'] != 'amazon' && (node['platform_version'].to_i == 7 || node['platform_version'].to_i >= 19) + default['rsyslog']['modules'] = %w(imuxsock imjournal) + default['rsyslog']['additional_directives'] = { 'OmitLocalLogging' => 'on', 'IMJournalStateFile' => 'imjournal.state' } + end +else + # format { facility => destination } + default['rsyslog']['default_facility_logs'] = { + 'auth,authpriv.*' => "#{node['rsyslog']['default_log_dir']}/auth.log", + '*.*;auth,authpriv.none' => "-#{node['rsyslog']['default_log_dir']}/syslog", + 'daemon.*' => "-#{node['rsyslog']['default_log_dir']}/daemon.log", + 'kern.*' => "-#{node['rsyslog']['default_log_dir']}/kern.log", + 'mail.*' => "-#{node['rsyslog']['default_log_dir']}/mail.log", + 'user.*' => "-#{node['rsyslog']['default_log_dir']}/user.log", + 'mail.info' => "-#{node['rsyslog']['default_log_dir']}/mail.info", + 'mail.warn' => "-#{node['rsyslog']['default_log_dir']}/mail.warn", + 'mail.err' => "#{node['rsyslog']['default_log_dir']}/mail.err", + 'news.crit' => "#{node['rsyslog']['default_log_dir']}/news/news.crit", + 'news.err' => "#{node['rsyslog']['default_log_dir']}/news/news.err", + 'news.notice' => "-#{node['rsyslog']['default_log_dir']}/news/news.notice", + '*.=debug;auth,authpriv.none;news.none;mail.none' => "-#{node['rsyslog']['default_log_dir']}/debug", + '*.=info;*.=notice;*.=warn;auth,authpriv.none;cron,daemon.none;mail,news.none' => "-#{node['rsyslog']['default_log_dir']}/messages", + '*.emerg' => '*' + } +end + +# platform specific attributes +case node['platform'] +when 'ubuntu' + # syslog user introduced with natty package + if node['platform_version'].to_f >= 11.04 + default['rsyslog']['user'] = 'syslog' + default['rsyslog']['group'] = 'adm' + default['rsyslog']['priv_seperation'] = true + default['rsyslog']['priv_group'] = 'syslog' + end +when 'arch' + default['rsyslog']['service_name'] = 'rsyslogd' +when 'smartos' + default['rsyslog']['config_prefix'] = '/opt/local/etc' + default['rsyslog']['modules'] = %w(immark imsolaris imtcp imudp) + default['rsyslog']['group'] = 'root' +when 'omnios' + default['rsyslog']['service_name'] = 'system/rsyslogd' + default['rsyslog']['modules'] = %w(immark imsolaris imtcp imudp) + default['rsyslog']['group'] = 'root' +when 'suse' + default['rsyslog']['service_name'] = 'syslog' +end diff --git a/cookbooks/rsyslog/chefignore b/cookbooks/rsyslog/chefignore new file mode 100644 index 0000000..e403950 --- /dev/null +++ b/cookbooks/rsyslog/chefignore @@ -0,0 +1,100 @@ +# Put files/directories that should be ignored in this file when uploading +# or sharing to the community site. +# Lines that start with '# ' are comments. + +# OS generated files # +###################### +.DS_Store +Icon? +nohup.out +ehthumbs.db +Thumbs.db + +# SASS # +######## +.sass-cache + +# EDITORS # +########### +\#* +.#* +*~ +*.sw[a-z] +*.bak +REVISION +TAGS* +tmtags +*_flymake.* +*_flymake +*.tmproj +.project +.settings +mkmf.log + +## COMPILED ## +############## +a.out +*.o +*.pyc +*.so +*.com +*.class +*.dll +*.exe +*/rdoc/ + +# Testing # +########### +.watchr +.rspec +spec/* +spec/fixtures/* +test/* +features/* +examples/* +Guardfile +Procfile + +# SCM # +####### +.git +*/.git +.gitignore +.gitmodules +.gitconfig +.gitattributes +.svn +*/.bzr/* +*/.hg/* +*/.svn/* + +# Berkshelf # +############# +Berksfile +Berksfile.lock +cookbooks/* +tmp + +# Cookbooks # +############# +CONTRIBUTING +CHANGELOG* + +# Strainer # +############ +Colanderfile +Strainerfile +.colander +.strainer + +# Vagrant # +########### +.vagrant +Vagrantfile + +# Travis # +########## +.travis.yml +test/ +spec/ +examples/ diff --git a/cookbooks/rsyslog/libraries/helpers.rb b/cookbooks/rsyslog/libraries/helpers.rb new file mode 100644 index 0000000..51dd4b4 --- /dev/null +++ b/cookbooks/rsyslog/libraries/helpers.rb @@ -0,0 +1,18 @@ +module RsyslogCookbook + # helpers for the various service providers on Ubuntu systems + module Helpers + def declare_rsyslog_service + if node['platform'] == 'ubuntu' && node['platform_version'].to_f >= 12.04 + service_provider = Chef::Provider::Service::Upstart + else + service_provider = nil + end + + service node['rsyslog']['service_name'] do + supports :restart => true, :status => true + action [:enable, :start] + provider service_provider + end + end + end +end diff --git a/cookbooks/rsyslog/metadata.json b/cookbooks/rsyslog/metadata.json new file mode 100644 index 0000000..cc27e33 --- /dev/null +++ b/cookbooks/rsyslog/metadata.json @@ -0,0 +1,375 @@ +{ + "name": "rsyslog", + "description": "Installs and configures rsyslog", + "long_description": "rsyslog Cookbook\n================\n[![Build Status](https://secure.travis-ci.org/opscode-cookbooks/rsyslog.png?branch=master)](http://travis-ci.org/opscode-cookbooks/rsyslog)\n\nInstalls and configures rsyslog to replace sysklogd for client and/or server use. By default, the service will be configured to log to files on local disk. See the Recipes and Examples sections for other uses.\n\n\nRequirements\n------------\n### Platforms\nTested on:\n- Ubuntu 10.04+\n- Red Hat / CentOS 5+\n- Fedora 20+\n- OmniOS r151006c\n\n### Other\nTo use the `recipe[rsyslog::client]` recipe, you'll need to set up the `rsyslog.server_search` or `rsyslog.server_ip` attributes. See the __Recipes__ and __Examples__ sections below.\n\n\nAttributes\n----------\nSee `attributes/default.rb` for default values.\n\n* `node['rsyslog']['log_dir']` - If the node is an rsyslog server, this specifies the directory where the logs should be stored.\n* `node['rsyslog']['working_dir']` - The temporary working directory where messages are buffered\n* `node['rsyslog']['server']` - Determined automatically and set to true on the server.\n* `node['rsyslog']['server_ip']` - If not defined then search will be used to determine rsyslog server. Default is `nil`. This can be a string or an array.\n* `node['rsyslog']['server_search']` - Specify the criteria for the server search operation. Default is `role:loghost`.\n* `node['rsyslog']['protocol']` - Specify whether to use `udp` or `tcp` for remote loghost. Default is `tcp`.\n* `node['rsyslog']['port']` - Specify the port which rsyslog should connect to a remote loghost.\n* `node['rsyslog']['remote_logs']` - Specify wether to send all logs to a remote server (client option). Default is `true`.\n* `node['rsyslog']['per_host_dir']` - \"PerHost\" directories for template statements in `35-server-per-host.conf`. Default value is the previous cookbook version's value, to preserve compatibility. See __server__ recipe below.\n* `node['rsyslog']['priv_seperation']` - Whether to use privilege separation or not.\n* `node['rsyslog']['priv_user']` - User to run as when using privilege separation. Defult is `node['rsyslog']['user']`\n* `node['rsyslog']['priv_group']` - Group to run as when using privilege separation. Defult is `node['rsyslog']['group']`\n* `node['rsyslog']['max_message_size']` - Specify the maximum allowed message size. Default is 2k.\n* `node['rsyslog']['user']` - Who should own the configuration files and directories\n* `node['rsyslog']['group']` - Who should group-own the configuration files and directories\n* `node['rsyslog']['defaults_file']` - The full path to the defaults/sysconfig file for the service.\n* `node['rsyslog']['service_name']` - The platform-specific name of the service\n* `node['rsyslog']['preserve_fqdn']` - Value of the `$PreserveFQDN` configuration directive in `/etc/rsyslog.conf`. Default is 'off' for compatibility purposes.\n* `node['rsyslog']['high_precision_timestamps']` - Enable high precision timestamps, instead of the \"old style\" format. Default is 'false'.\n* `node['rsyslog']['repeated_msg_reduction']` - Value of `$RepeatedMsgReduction` configuration directive in `/etc/rsyslog.conf`. Default is 'on'\n* `node['rsyslog']['logs_to_forward']` - Specifies what logs should be sent to the remote rsyslog server. Default is all ( \\*.\\* ).\n* `node['rsyslog']['default_log_dir']` - log directory used in `50-default.conf` template, defaults to `/var/log`\n* `node['rsyslog']['default_facility_logs']` - Hash containing log facilities and destinations used in `50-default.conf` template.\n* `node['rsyslog']['default_file_template']` - The name of a pre-defined log format template (ie - RSYSLOG_FileFormat), used for local log files.\n* `node['rsyslog']['rate_limit_interval']` - Value of the $SystemLogRateLimitInterval configuration directive in `/etc/rsyslog.conf`. Default is nil, leaving it to the platform default.\n* `node['rsyslog']['rate_limit_burst']` - Value of the $SystemLogRateLimitBurst configuration directive in `/etc/rsyslog.conf`. Default is nil, leaving it to the platform default.\n* `node['rsyslog']['action_queue_max_disk_space']` - Max amount of disk space the disk-assisted queue is allowed to use ([more info](http://www.rsyslog.com/doc/queues.html)).\n* `node['rsyslog']['enable_tls']` - Whether or not to enable TLS encryption. When enabled, forces protocol to `tcp`. Default is `false`.\n* `node['rsyslog']['tls_ca_file']` - Path to TLS CA file. Required for both server and clients.\n* `node['rsyslog']['tls_certificate_file']` - Path to TLS certificate file. Required for server, optional for clients.\n* `node['rsyslog']['tls_key_file']` - Path to TLS key file. Required for server, optional for clients.\n* `node['rsyslog']['tls_auth_mode']` - Value for `$InputTCPServerStreamDriverAuthMode`/`$ActionSendStreamDriverAuthMode`, determines whether client certs are validated. Defaults to `anon` (no validation).\n* `node['rsyslog']['use_local_ipv4']` - Whether or not to make use the remote local IPv4 address on cloud systems when searching for servers (where available). Default is 'false'.\n* `node['rsyslog']['allow_non_local']` - Whether or not to allow non-local messages. If 'false', incoming messages are only allowed from 127.0.0.1. Default is 'false'.\n* `node['rsyslog']['additional_directives']` - Hash of additional directives and their values to place in the main rsyslog config file\n\nRecipes\n-------\n### default\nInstalls the rsyslog package, manages the rsyslog service and sets up basic configuration for a standalone machine.\n\n### client\nIncludes `recipe[rsyslog]`.\n\nUses `node['rsyslog']['server_ip']` or Chef search (in that precedence order) to determine the remote syslog server's IP address. If search is used, the search query will look for the first `ipaddress` returned from the criteria specified in `node['rsyslog']['server_search']`.\n\nIf the node itself is a rsyslog server ie it has `rsyslog.server` set to true then the configuration is skipped.\n\nIf the node had an `/etc/rsyslog.d/35-server-per-host.conf` file previously configured, this file gets removed to prevent duplicate logging.\n\nAny previous logs are not cleaned up from the `log_dir`.\n\n### server\nConfigures the node to be a rsyslog server. The chosen rsyslog server node should be defined in the `server_ip` attribute or resolvable by the specified search criteria specified in `node['rsyslog']['server_search]` (so that nodes making use of the `client` recipe can find the server to log to).\n\nThis recipe will create the logs in `node['rsyslog']['log_dir']`, and the configuration is in `/etc/rsyslog.d/server.conf`. This recipe also removes any previous configuration to a remote server by removing the `/etc/rsyslog.d/remote.conf` file.\n\nThe cron job used in the previous version of this cookbook is removed, but it does not remove any existing cron job from your system (so it doesn't break anything unexpectedly). We recommend setting up logrotate for the logfiles instead.\n\nThe `log_dir` will be concatenated with `per_host_dir` to store the logs for each client. Modify the attribute to have a value that is allowed by rsyslogs template matching values, see the rsyslog documentation for this.\n\nDirectory structure:\n\n```erb\n<%= @log_dir %>/<%= @per_host_dir %>/\"logfile\"\n```\n\nFor example for the system with hostname `www`:\n\n```text\n/srv/rsyslog/2011/11/19/www/messages\n```\n\nFor example, to change this to just the hostname, set the attribute `node['rsyslog']['per_host_dir']` via a role:\n\n```ruby\n\"rsyslog\" => { \"per_host_dir\" => \"%HOSTNAME%\" }\n```\n\nAt this time, the server can only listen on UDP *or* TCP.\n\nResources\n=========\n\nfile_input\n----------\n\nConfigures a (text file input\nmonitor)[http://www.rsyslog.com/doc/imfile.html] to push a log file into\nrsyslog.\n\nAttributes:\n* `name`: name of the resource, also used for the syslog tag. Required.\n* `file`: file path for input file to monitor. Required.\n* `priority`: config order priority. Defaults to `99`.\n* `severity`: syslog severity. Must be one of `emergency`, `alert`,\n`critical`, `error`, `warning`, `notice`, `info` or `debug`. If\nundefined, rsyslog interprets this as `notice`.\n* `facility`: syslog facility. Must be one of `auth`, `authpriv`,\n`daemon`, `cron`, `ftp`, `lpr`, `kern`, `mail`, `news`, `syslog`,\n`user`, `uucp`, `local0`, ... , `local7`. If undefined, rsyslog\ninterprets this as `local0`.\n* `cookbook`: cookbook containing the template. Defaults to `rsyslog`.\n* `source`: template file source. Defaults to `file-input.conf.erb`\n\n\nUsage\n=====\nUse `recipe[rsyslog]` to install and start rsyslog as a basic configured service for standalone systems.\n\nUse `recipe[rsyslog::client]` to have nodes log to a remote server (which is found via the `server_ip` attribute or by the recipe's search call -- see __client__)\n\nUse `recipe[rsyslog::server]` to set up a rsyslog server. It will listen on `node['rsyslog']['port']` protocol `node['rsyslog']['protocol']`.\n\nIf you set up a different kind of centralized loghost (syslog-ng, graylog2, logstash, etc), you can still send log messages to it as long as the port and protocol match up with the server software. See __Examples__\n\nUse `rsyslog_file_input` within your recipes to forward log files to\nyour remote syslog server.\n\n\n### Examples\nA `base` role (e.g., roles/base.rb), applied to all nodes so they are syslog clients:\n\n```ruby\nname \"base\"\ndescription \"Base role applied to all nodes\nrun_list(\"recipe[rsyslog::client]\")\n```\n\nThen, a role for the loghost (should only be one):\n\n```ruby\nname \"loghost\"\ndescription \"Central syslog server\"\nrun_list(\"recipe[rsyslog::server]\")\n```\n\nBy default this will set up the clients search for a node with the `loghost` role to talk to the server on TCP port 514. Change the `protocol` and `port` rsyslog attributes to modify this.\n\nIf you want to specify another syslog compatible server with a role other than loghost, simply fill free to use the `server_ip` attribute or the `server_search` attribute.\n\nExample role that sets the per host directory:\n\n```ruby\nname \"loghost\"\ndescription \"Central syslog server\"\nrun_list(\"recipe[rsyslog::server]\")\ndefault_attributes(\n \"rsyslog\" => { \"per_host_dir\" => \"%HOSTNAME%\" }\n)\n```\n\nDefault rsyslog options are rendered for RHEL family platforms, in `/etc/rsyslog.d/50-default.conf`\nwith other platforms using a configuration like Debian family defaults. You can override these\nlog facilities and destinations using the `rsyslog['default_facility_logs']` hash.\n\n```ruby\nname \"facility_log_example\"\nrun_list(\"recipe[rsyslog::default]\")\ndefault_attributes(\n \"rsyslog\" => {\n \"facility_logs\" => {\n '*.info;mail.none;authpriv.none;cron.none' => \"/var/log/messages\",\n 'authpriv' => '/var/log/secure',\n 'mail.*' => '-/var/log/maillog',\n '*.emerg' => '*'\n }\n }\n)\n```\n\nDevelopment\n-----------\nThis section details \"quick development\" steps. For a detailed explanation, see [[Contributing.md]].\n\n1. Clone this repository from GitHub:\n\n $ git clone git@github.com:opscode-cookbooks/rsyslog.git\n\n2. Create a git branch\n\n $ git checkout -b my_bug_fix\n\n3. Install dependencies:\n\n $ bundle install\n\n4. Make your changes/patches/fixes, committing appropriately\n5. **Write tests**\n6. Run the tests:\n - bundle exec foodcritic -f any .\n - bundle exec rspec\n - bundle exec rubocop\n - bundle exec kitchen test\n\n In detail:\n - Foodcritic will catch any Chef-specific style errors\n - RSpec will run the unit tests\n - Rubocop will check for Ruby-specific style errors\n - Test Kitchen will run and converge the recipes\n\n\nLicense & Authors\n-----------------\n- Author:: Joshua Timberman ()\n- Author:: Denis Barishev ()\n- Author:: Tim Smith ()\n\n```text\nCopyright:: 2009-2015, Chef Software, Inc\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "maintainer": "Chef Software, Inc.", + "maintainer_email": "cookbooks@chef.io", + "license": "Apache 2.0", + "platforms": { + "ubuntu": ">= 10.04", + "debian": ">= 5.0", + "redhat": ">= 5.0", + "centos": ">= 5.0", + "fedora": ">= 20.0" + }, + "dependencies": { + + }, + "recommendations": { + + }, + "suggestions": { + + }, + "conflicting": { + + }, + "providing": { + + }, + "replacing": { + + }, + "attributes": { + "rsyslog": { + "display_name": "Rsyslog", + "description": "Hash of Rsyslog attributes", + "type": "hash", + "choice": [ + + ], + "calculated": false, + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/log_dir": { + "display_name": "Rsyslog Log Directory", + "description": "Filesystem location of logs from clients", + "default": "/srv/rsyslog", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/server": { + "display_name": "Rsyslog Server?", + "description": "Is this node an rsyslog server?", + "default": "false", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/server_ip": { + "display_name": "Rsyslog Server IP Address", + "description": "Set rsyslog server ip address explicitly", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/server_search": { + "display_name": "Rsyslog Server Search Criteria", + "description": "Set the search criteria for rsyslog server resolving", + "default": "role:loghost", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/protocol": { + "display_name": "Rsyslog Protocol", + "description": "Set which network protocol to use for rsyslog", + "default": "tcp", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/port": { + "display_name": "Rsyslog Port", + "description": "Port that Rsyslog listens for incoming connections", + "default": "514", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/remote_logs": { + "display_name": "Remote Logs", + "description": "Specifies whether redirect all log from client to server", + "default": "true", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/user": { + "display_name": "User", + "description": "The owner of Rsyslog config files and directories", + "default": "root", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/group": { + "display_name": "Group", + "description": "The group-owner of Rsyslog config files and directories", + "default": "adm", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/service_name": { + "display_name": "Service name", + "description": "The name of the service for the platform", + "default": "rsyslog", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/max_message_size": { + "display_name": "Maximum Rsyslog message size", + "description": "Specifies the maximum size of allowable Rsyslog messages", + "default": "2k", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/preserve_fqdn": { + "display_name": "Preserve FQDN", + "description": "Specifies if the short or full host name will be used. The default off setting is more compatible.", + "default": "off", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/repeated_msg_reduction": { + "display_name": "Filter duplicated messages", + "description": "Specifies whether or not repeated messages should be reduced.", + "default": "on", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/priv_seperation": { + "display_name": "Privilege separation", + "description": "Whether or not to make use of Rsyslog privilege separation", + "default": "false", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/default_file_template": { + "display_name": "Default file log format template", + "description": "The name of a pre-defined log format template (ie - `RSYSLOG_FileFormat`), used for local log files.", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/default_remote_template": { + "display_name": "Default remote log format template", + "description": "The name of a pre-defined log format template (ie - `RSYSLOG_SyslogProtocol23Format`), used for remote log forwarding.", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/enable_tls": { + "display_name": "Enable TLS", + "description": "Whether or not to enable TLS encryption. When enabled, forces protocol to \"tcp\"", + "default": "false", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/tls_ca_file": { + "display_name": "TLS CA file", + "description": "Path to TLS CA file. Required for both server and clients.", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/tls_certificate_file": { + "display_name": "TLS certificate file", + "description": "Path to TLS certificate file. Required for server, optional for clients.", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/tls_key_file": { + "display_name": "TLS key file", + "description": "Path to TLS key file. Required for server, optional for clients.", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/tls_auth_mode": { + "display_name": "TLS auth mode", + "description": "Value for \"$InputTCPServerStreamDriverAuthMode\"/\"$ActionSendStreamDriverAuthMode\", determines whether client certs are validated.", + "default": "anon", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/use_local_ipv4": { + "display_name": "Try to use local IPv4 address", + "description": "Whether or not to make use the remote local IPv4 address on cloud systems when searching for servers (where available).", + "default": "false", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + }, + "rsyslog/allow_non_local": { + "display_name": "Allow non-local messages", + "description": "Allow processing of messages coming any IP, not just 127.0.0.1", + "default": "false", + "choice": [ + + ], + "calculated": false, + "type": "string", + "required": "optional", + "recipes": [ + + ] + } + }, + "groupings": { + + }, + "recipes": { + "rsyslog": "Installs rsyslog", + "rsyslog::client": "Sets up a client to log to a remote rsyslog server", + "rsyslog::server": "Sets up an rsyslog server" + }, + "version": "2.0.0", + "source_url": "", + "issues_url": "" +} diff --git a/cookbooks/rsyslog/metadata.rb b/cookbooks/rsyslog/metadata.rb new file mode 100644 index 0000000..6f33e5e --- /dev/null +++ b/cookbooks/rsyslog/metadata.rb @@ -0,0 +1,131 @@ +name 'rsyslog' +maintainer 'Chef Software, Inc.' +maintainer_email 'cookbooks@chef.io' +license 'Apache 2.0' +description 'Installs and configures rsyslog' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '2.0.0' + +recipe 'rsyslog', 'Installs rsyslog' +recipe 'rsyslog::client', 'Sets up a client to log to a remote rsyslog server' +recipe 'rsyslog::server', 'Sets up an rsyslog server' + +supports 'ubuntu', '>= 10.04' +supports 'debian', '>= 5.0' +supports 'redhat', '>= 5.0' +supports 'centos', '>= 5.0' +supports 'fedora', '>= 20.0' + +attribute 'rsyslog', + :display_name => 'Rsyslog', + :description => 'Hash of Rsyslog attributes', + :type => 'hash' + +attribute 'rsyslog/log_dir', + :display_name => 'Rsyslog Log Directory', + :description => 'Filesystem location of logs from clients', + :default => '/srv/rsyslog' + +attribute 'rsyslog/server', + :display_name => 'Rsyslog Server?', + :description => 'Is this node an rsyslog server?', + :default => 'false' + +attribute 'rsyslog/server_ip', + :display_name => 'Rsyslog Server IP Address', + :description => 'Set rsyslog server ip address explicitly' + +attribute 'rsyslog/server_search', + :display_name => 'Rsyslog Server Search Criteria', + :description => 'Set the search criteria for rsyslog server resolving', + :default => 'role:loghost' + +attribute 'rsyslog/protocol', + :display_name => 'Rsyslog Protocol', + :description => 'Set which network protocol to use for rsyslog', + :default => 'tcp' + +attribute 'rsyslog/port', + :display_name => 'Rsyslog Port', + :description => 'Port that Rsyslog listens for incoming connections', + :default => '514' + +attribute 'rsyslog/remote_logs', + :display_name => 'Remote Logs', + :description => 'Specifies whether redirect all log from client to server', + :default => 'true' + +attribute 'rsyslog/user', + :display_name => 'User', + :description => 'The owner of Rsyslog config files and directories', + :default => 'root' + +attribute 'rsyslog/group', + :display_name => 'Group', + :description => 'The group-owner of Rsyslog config files and directories', + :default => 'adm' + +attribute 'rsyslog/service_name', + :display_name => 'Service name', + :description => 'The name of the service for the platform', + :default => 'rsyslog' + +attribute 'rsyslog/max_message_size', + :display_name => 'Maximum Rsyslog message size', + :description => 'Specifies the maximum size of allowable Rsyslog messages', + :default => '2k' + +attribute 'rsyslog/preserve_fqdn', + :display_name => 'Preserve FQDN', + :description => 'Specifies if the short or full host name will be used. The default off setting is more compatible.', + :default => 'off' + +attribute 'rsyslog/repeated_msg_reduction', + :display_name => 'Filter duplicated messages', + :description => 'Specifies whether or not repeated messages should be reduced.', + :default => 'on' + +attribute 'rsyslog/priv_seperation', + :display_name => 'Privilege separation', + :description => 'Whether or not to make use of Rsyslog privilege separation', + :default => 'false' + +attribute 'rsyslog/default_file_template', + :display_name => 'Default file log format template', + :description => 'The name of a pre-defined log format template (ie - `RSYSLOG_FileFormat`), used for local log files.' + +attribute 'rsyslog/default_remote_template', + :display_name => 'Default remote log format template', + :description => 'The name of a pre-defined log format template (ie - `RSYSLOG_SyslogProtocol23Format`), used for remote log forwarding.' + +attribute 'rsyslog/enable_tls', + :display_name => 'Enable TLS', + :description => 'Whether or not to enable TLS encryption. When enabled, forces protocol to "tcp"', + :default => 'false' + +attribute 'rsyslog/tls_ca_file', + :display_name => 'TLS CA file', + :description => 'Path to TLS CA file. Required for both server and clients.' + +attribute 'rsyslog/tls_certificate_file', + :display_name => 'TLS certificate file', + :description => 'Path to TLS certificate file. Required for server, optional for clients.' + +attribute 'rsyslog/tls_key_file', + :display_name => 'TLS key file', + :description => 'Path to TLS key file. Required for server, optional for clients.' + +attribute 'rsyslog/tls_auth_mode', + :display_name => 'TLS auth mode', + :description => 'Value for "$InputTCPServerStreamDriverAuthMode"/"$ActionSendStreamDriverAuthMode", determines whether client certs are validated.', + :default => 'anon' + +attribute 'rsyslog/use_local_ipv4', + :display_name => 'Try to use local IPv4 address', + :description => 'Whether or not to make use the remote local IPv4 address on cloud systems when searching for servers (where available).', + :default => 'false' + +attribute 'rsyslog/allow_non_local', + :display_name => 'Allow non-local messages', + :description => 'Allow processing of messages coming any IP, not just 127.0.0.1', + :default => 'false' diff --git a/cookbooks/rsyslog/providers/file_input.rb b/cookbooks/rsyslog/providers/file_input.rb new file mode 100644 index 0000000..6e17bd3 --- /dev/null +++ b/cookbooks/rsyslog/providers/file_input.rb @@ -0,0 +1,39 @@ +# Cookbook Name:: rsyslog +# Provider:: file_input +# +# Copyright 2012, Joseph Holsten +# +# 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. +# + +use_inline_resources + +include RsyslogCookbook::Helpers + +action :create do + declare_rsyslog_service + + template "/etc/rsyslog.d/#{new_resource.priority}-#{new_resource.name}.conf" do + mode '0664' + owner node['rsyslog']['user'] + group node['rsyslog']['group'] + source new_resource.source + cookbook new_resource.cookbook + variables 'file_name' => new_resource.file, + 'tag' => new_resource.name, + 'state_file' => new_resource.name, + 'severity' => new_resource.severity, + 'facility' => new_resource.facility + notifies :restart, resources('service[rsyslog]') + end +end diff --git a/cookbooks/rsyslog/recipes/client.rb b/cookbooks/rsyslog/recipes/client.rb new file mode 100644 index 0000000..2add130 --- /dev/null +++ b/cookbooks/rsyslog/recipes/client.rb @@ -0,0 +1,72 @@ +# +# Cookbook Name:: rsyslog +# Recipe:: client +# +# Copyright 2009-2014, Chef Software, Inc. +# +# 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. +# + +# Do not run this recipe if the server attribute is set +return if node['rsyslog']['server'] + +include_recipe 'rsyslog::default' + +def chef_solo_search_installed? + klass = ::Search.const_get('Helper') + return klass.is_a?(Class) +rescue NameError + return false +end + +# On Chef Solo, we use the node['rsyslog']['server_ip'] attribute, and on +# normal Chef, we leverage the search query. +if Chef::Config[:solo] && !chef_solo_search_installed? + if node['rsyslog']['server_ip'] + rsyslog_servers = Array(node['rsyslog']['server_ip']) + else + Chef::Application.fatal!("Chef Solo does not support search. You must set node['rsyslog']['server_ip'] or use the chef-solo-search cookbook!") + end +else + results = search(:node, node['rsyslog']['server_search']).map do |server| + ipaddress = server['ipaddress'] + # If both server and client are on the same cloud and local network, they may be + # instructed to communicate via the internal interface by enabling `use_local_ipv4` + if node['rsyslog']['use_local_ipv4'] && server.attribute?('cloud') && server['cloud']['local_ipv4'] + ipaddress = server['cloud']['local_ipv4'] + end + ipaddress + end + rsyslog_servers = Array(node['rsyslog']['server_ip']) + Array(results) +end + +if rsyslog_servers.empty? + Chef::Application.fatal!('The rsyslog::client recipe was unable to determine the remote syslog server. Checked both the server_ip attribute and search!') +end + +remote_type = node['rsyslog']['use_relp'] ? 'relp' : 'remote' + +template "#{node['rsyslog']['config_prefix']}/rsyslog.d/49-remote.conf" do + source "49-#{remote_type}.conf.erb" + owner 'root' + group 'root' + mode '0644' + variables(:servers => rsyslog_servers) + notifies :restart, "service[#{node['rsyslog']['service_name']}]" + only_if { node['rsyslog']['remote_logs'] } +end + +file "#{node['rsyslog']['config_prefix']}/rsyslog.d/server.conf" do + action :delete + notifies :restart, "service[#{node['rsyslog']['service_name']}]" +end diff --git a/cookbooks/rsyslog/recipes/default.rb b/cookbooks/rsyslog/recipes/default.rb new file mode 100644 index 0000000..d0fe51a --- /dev/null +++ b/cookbooks/rsyslog/recipes/default.rb @@ -0,0 +1,89 @@ +# +# Cookbook Name:: rsyslog +# Recipe:: default +# +# Copyright 2009-2014, Chef Software, Inc. +# +# 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. +# + +extend RsyslogCookbook::Helpers + +package 'rsyslog' +package 'rsyslog-relp' if node['rsyslog']['use_relp'] + +if node['rsyslog']['enable_tls'] && node['rsyslog']['tls_ca_file'] + Chef::Application.fatal!("Recipe rsyslog::default can not use 'enable_tls' with protocol '#{node['rsyslog']['protocol']}' (requires 'tcp')") unless node['rsyslog']['protocol'] == 'tcp' + package 'rsyslog-gnutls' +end + +directory "#{node['rsyslog']['config_prefix']}/rsyslog.d" do + owner 'root' + group 'root' + mode '0755' +end + +directory node['rsyslog']['working_dir'] do + owner node['rsyslog']['user'] + group node['rsyslog']['group'] + mode '0700' +end + +# Our main stub which then does its own rsyslog-specific +# include of things in /etc/rsyslog.d/* +template "#{node['rsyslog']['config_prefix']}/rsyslog.conf" do + source 'rsyslog.conf.erb' + owner 'root' + group 'root' + mode '0644' + notifies :restart, "service[#{node['rsyslog']['service_name']}]" +end + +template "#{node['rsyslog']['config_prefix']}/rsyslog.d/50-default.conf" do + source '50-default.conf.erb' + owner 'root' + group 'root' + mode '0644' + notifies :restart, "service[#{node['rsyslog']['service_name']}]" +end + +# syslog needs to be stopped before rsyslog can be started on RHEL versions before 6.0 +if platform_family?('rhel') && node['platform_version'].to_i < 6 + service 'syslog' do + action [:stop, :disable] + end +elsif platform_family?('smartos', 'omnios') + # syslog needs to be stopped before rsyslog can be started on SmartOS, OmniOS + service 'system-log' do + action :disable + end +end + +if platform_family?('omnios') + # manage the SMF manifest on OmniOS + template '/var/svc/manifest/system/rsyslogd.xml' do + source 'omnios-manifest.xml.erb' + owner 'root' + group 'root' + mode '0644' + notifies :run, 'execute[import rsyslog manifest]', :immediately + end + + execute 'import rsyslog manifest' do + action :nothing + command 'svccfg import /var/svc/manifest/system/rsyslogd.xml' + notifies :restart, "service[#{node['rsyslog']['service_name']}]" + end +end + +declare_rsyslog_service diff --git a/cookbooks/rsyslog/recipes/server.rb b/cookbooks/rsyslog/recipes/server.rb new file mode 100644 index 0000000..c1034d2 --- /dev/null +++ b/cookbooks/rsyslog/recipes/server.rb @@ -0,0 +1,44 @@ +# +# Cookbook Name:: rsyslog +# Recipe:: server +# +# Copyright 2009-2014, Chef Software, Inc. +# +# 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. +# + +# Manually set this attribute +node.set['rsyslog']['server'] = true + +include_recipe 'rsyslog::default' + +directory node['rsyslog']['log_dir'] do + owner node['rsyslog']['user'] + group node['rsyslog']['group'] + mode '0755' + recursive true +end + +template "#{node['rsyslog']['config_prefix']}/rsyslog.d/35-server-per-host.conf" do + source '35-server-per-host.conf.erb' + owner 'root' + group 'root' + mode '0644' + notifies :restart, "service[#{node['rsyslog']['service_name']}]" +end + +file "#{node['rsyslog']['config_prefix']}/rsyslog.d/remote.conf" do + action :delete + notifies :restart, "service[#{node['rsyslog']['service_name']}]" + only_if { ::File.exist?("#{node['rsyslog']['config_prefix']}/rsyslog.d/remote.conf") } +end diff --git a/cookbooks/rsyslog/resources/file_input.rb b/cookbooks/rsyslog/resources/file_input.rb new file mode 100644 index 0000000..aca6539 --- /dev/null +++ b/cookbooks/rsyslog/resources/file_input.rb @@ -0,0 +1,28 @@ +# Cookbook Name:: rsyslog +# Resource:: file_input +# +# Copyright 2012-2015, Joseph Holsten +# +# 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. +# + +actions :create +default_action :create + +attribute :name, :kind_of => String, :name_attribute => true, :required => true +attribute :file, :kind_of => String, :required => true +attribute :priority, :kind_of => Integer, :default => 99 +attribute :severity, :kind_of => String +attribute :facility, :kind_of => String +attribute :cookbook, :kind_of => String, :default => 'rsyslog' +attribute :source, :kind_of => String, :default => 'file-input.conf.erb' diff --git a/cookbooks/rsyslog/templates/default/35-server-per-host.conf.erb b/cookbooks/rsyslog/templates/default/35-server-per-host.conf.erb new file mode 100644 index 0000000..bdb3eca --- /dev/null +++ b/cookbooks/rsyslog/templates/default/35-server-per-host.conf.erb @@ -0,0 +1,62 @@ +# Generated by Chef +# Local modifications will be overwritten + +<% if node['rsyslog']['use_relp'] -%> +$ModLoad imrelp +$InputRELPServerRun <%= node['rsyslog']['relp_port'] %> +<% end -%> +$DirGroup <%= node['rsyslog']['group'] %> +$DirCreateMode 0755 +$FileGroup <%= node['rsyslog']['group'] %> + +$template PerHostAuth,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/auth.log" +$template PerHostCron,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/cron.log" +$template PerHostSyslog,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/syslog" +$template PerHostDaemon,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/daemon.log" +$template PerHostKern,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/kern.log" +$template PerHostLpr,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/lpr.log" +$template PerHostUser,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/user.log" +$template PerHostMail,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/mail.log" +$template PerHostMailInfo,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/mail.info" +$template PerHostMailWarn,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/mail.warn" +$template PerHostMailErr,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/mail.err" +$template PerHostNewsCrit,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/news.crit" +$template PerHostNewsErr,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/news.err" +$template PerHostNewsNotice,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/news.notice" +$template PerHostDebug,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/debug" +$template PerHostMessages,"<%= node['rsyslog']['log_dir'] %>/<%= node['rsyslog']['per_host_dir'] %>/messages" + +auth,authpriv.* ?PerHostAuth +*.*;auth,authpriv.none -?PerHostSyslog +cron.* ?PerHostCron +daemon.* -?PerHostDaemon +kern.* -?PerHostKern +lpr.* -?PerHostLpr +mail.* -?PerHostMail +user.* -?PerHostUser + +mail.info -?PerHostMailInfo +mail.warn ?PerHostMailWarn +mail.err ?PerHostMailErr + +news.crit ?PerHostNewsCrit +news.err ?PerHostNewsErr +news.notice -?PerHostNewsNotice + +*.=debug;\ + auth,authpriv.none;\ + news.none;mail.none -?PerHostDebug + +*.=info;*.=notice;*.=warn;\ + auth,authpriv.none;\ + cron,daemon.none;\ + mail,news.none -?PerHostMessages + + +<% unless node['rsyslog']['allow_non_local'] -%> +# +# Stop processing of all non-local messages. You can process remote messages +# on levels less than 35. +# +:fromhost-ip,!isequal,"127.0.0.1" ~ +<% end -%> diff --git a/cookbooks/rsyslog/templates/default/49-relp.conf.erb b/cookbooks/rsyslog/templates/default/49-relp.conf.erb new file mode 100644 index 0000000..f96d923 --- /dev/null +++ b/cookbooks/rsyslog/templates/default/49-relp.conf.erb @@ -0,0 +1,10 @@ +# Generated by Chef +$ModLoad omrelp +$ActionQueueType LinkedList # use asynchronous processing +$ActionQueueFileName srvrfwd # set file name, also enables disk mode +$ActionResumeRetryCount -1 # infinite retries on insert failure +$ActionQueueSaveOnShutdown on # save in-memory data if rsyslog shuts down + +<% @servers.each do |server| -%> +*.* :omrelp:<%= "#{server}:#{node['rsyslog']['relp_port']}" %><%= node['rsyslog']['default_remote_template'] ? ';' + node['rsyslog']['default_remote_template'] : nil %> +<% end -%> diff --git a/cookbooks/rsyslog/templates/default/49-remote.conf.erb b/cookbooks/rsyslog/templates/default/49-remote.conf.erb new file mode 100644 index 0000000..154a16f --- /dev/null +++ b/cookbooks/rsyslog/templates/default/49-remote.conf.erb @@ -0,0 +1,28 @@ +# Generated by Chef +$ActionQueueType LinkedList # use asynchronous processing +$ActionQueueFileName srvrfwd # set file name, also enables disk mode +$ActionResumeRetryCount -1 # infinite retries on insert failure +$ActionQueueSaveOnShutdown on # save in-memory data if rsyslog shuts down +$ActionQueueMaxDiskSpace <%= node['rsyslog']['action_queue_max_disk_space'] %> # Don't use more than this much space for the queue +<% if node['rsyslog']['enable_tls'] && node['rsyslog']['tls_ca_file'] -%> +$DefaultNetstreamDriverCAFile <%= node['rsyslog']['tls_ca_file'] %> +<% if node['rsyslog']['tls_certificate_file'] -%> +$DefaultNetstreamDriverCertFile <%= node['rsyslog']['tls_certificate_file'] %> +<% end -%> +<% if node['rsyslog']['tls_key_file'] -%> +$DefaultNetstreamDriverKeyFile <%= node['rsyslog']['tls_key_file'] %> +<% end -%> + +$DefaultNetstreamDriver gtls +$ActionSendStreamDriverMode 1 +$ActionSendStreamDriverAuthMode <%= node['rsyslog']['tls_auth_mode'] %> +<% end -%> + +<% @servers.each do |server| -%> +<% case node['rsyslog']['protocol'] -%> +<% when "tcp" -%> +<%= node['rsyslog']['logs_to_forward'] %> @@<%= server %>:<%= node['rsyslog']['port'] %><%= node["rsyslog"]["default_remote_template"] ? ';' + node["rsyslog"]["default_remote_template"] : nil %> +<% when "udp" -%> +<%= node['rsyslog']['logs_to_forward'] %> @<%= server %>:<%= node['rsyslog']['port'] %><%= node["rsyslog"]["default_remote_template"] ? ';' + node["rsyslog"]["default_remote_template"] : nil %> +<% end -%> +<% end -%> diff --git a/cookbooks/rsyslog/templates/default/50-default.conf.erb b/cookbooks/rsyslog/templates/default/50-default.conf.erb new file mode 100644 index 0000000..38ef1b9 --- /dev/null +++ b/cookbooks/rsyslog/templates/default/50-default.conf.erb @@ -0,0 +1,6 @@ +# Generated by Chef +# For more information see rsyslog.conf(5) and /etc/rsyslog.conf + +<% node['rsyslog']['default_facility_logs'].each do |key, value| %> +<%= key %> <%= value %> +<% end %> diff --git a/cookbooks/rsyslog/templates/default/file-input.conf.erb b/cookbooks/rsyslog/templates/default/file-input.conf.erb new file mode 100644 index 0000000..c500b8b --- /dev/null +++ b/cookbooks/rsyslog/templates/default/file-input.conf.erb @@ -0,0 +1,15 @@ +# <%= @tag %>.conf - Syslog file inputs for <%= @tag %> +# +# Generated by Chef for <%= node['fqdn'] %> +# Local modifications will be overwritten. +$ModLoad imfile +$InputFileName <%= @file_name %> +$InputFileTag <%= @tag %>: +$InputFileStateFile <%= @state_file %> +<% if @severity %> +$InputFileSeverity <%= @severity %> +<% end %> +<% if @facility %> +$InputFileFacility <%= @facility %> +<% end %> +$InputRunFileMonitor diff --git a/cookbooks/rsyslog/templates/default/omnios-manifest.xml.erb b/cookbooks/rsyslog/templates/default/omnios-manifest.xml.erb new file mode 100644 index 0000000..4bff7e1 --- /dev/null +++ b/cookbooks/rsyslog/templates/default/omnios-manifest.xml.erb @@ -0,0 +1,30 @@ + + + + ' type='service' version='0'> + + + + + + + + + + + + + + + + + + + + + + diff --git a/cookbooks/rsyslog/templates/default/rsyslog.conf.erb b/cookbooks/rsyslog/templates/default/rsyslog.conf.erb new file mode 100644 index 0000000..7a0226f --- /dev/null +++ b/cookbooks/rsyslog/templates/default/rsyslog.conf.erb @@ -0,0 +1,106 @@ +# rsyslog configuration file - Generated by Chef +# For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html +# If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html +# +# Set max message size +# +$MaxMessageSize <%= node['rsyslog']['max_message_size'] %> + +# +# Preserve FQDN +# +$PreserveFQDN <%= node['rsyslog']['preserve_fqdn'] %> + +################# +#### MODULES #### +################# + +<% if node['rsyslog']['modules'] && !node['rsyslog']['modules'].empty? %> + <% [*node['rsyslog']['modules']].each do |mod| %> +$ModLoad <%= mod %> + <% end %> +<% end %> + +<% if node['rsyslog']['server'] -%> + <% if node['rsyslog']['enable_tls'] && node['rsyslog']['tls_ca_file'] && + node['rsyslog']['tls_key_file'] && node['rsyslog']['tls_certificate_file'] -%> +$DefaultNetstreamDriver gtls +$DefaultNetstreamDriverCAFile <%= node['rsyslog']['tls_ca_file'] %> +$DefaultNetstreamDriverCertFile <%= node['rsyslog']['tls_certificate_file'] %> +$DefaultNetstreamDriverKeyFile <%= node['rsyslog']['tls_key_file'] %> + +$ModLoad imtcp + +$InputTCPServerStreamDriverMode 1 # run driver in TLS-only mode +$InputTCPServerStreamDriverAuthMode <%= node['rsyslog']['tls_auth_mode'] || 'anon' %> +$InputTCPServerRun <%= node['rsyslog']['port'] %> +# Provide <%= node['rsyslog']['protocol'].upcase %> log reception + <% else -%> +<% case node['rsyslog']['protocol'] -%> +<% when "tcp" -%> +$ModLoad imtcp +$InputTCPServerRun <%= node['rsyslog']['port'] %> +<% when "udp" -%> +$ModLoad imudp +$UDPServerRun <%= node['rsyslog']['port'] %> +<% end -%> + <% end -%> +<% end -%> + +########################### +#### GLOBAL DIRECTIVES #### +########################### + +<% if node["rsyslog"]["default_file_template"] -%> +# +# Default log format template +# +$ActionFileDefaultTemplate <%= node["rsyslog"]["default_file_template"] %> +<% elsif !node["rsyslog"]["high_precision_timestamps"] -%> +# +# Use default timestamp format. +# To enable high precision timestamps, comment out the following line. +# +$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat +<% end -%> + +# Filter duplicated messages +$RepeatedMsgReduction <%= node['rsyslog']['repeated_msg_reduction'] %> + +# +# Set temporary directory to buffer syslog queue +# +$WorkDirectory <%= node['rsyslog']['working_dir'] %> + +# +# Set the default permissions for all log files. +# +$FileOwner <%= node['rsyslog']['user'] %> +$FileGroup <%= node['rsyslog']['group'] %> +$FileCreateMode 0640 +$DirCreateMode 0755 +$Umask 0022 +<% if node['rsyslog']['priv_seperation'] %> +$PrivDropToUser <%= node['rsyslog']['priv_user'] || node['rsyslog']['user'] %> +$PrivDropToGroup <%= node['rsyslog']['priv_group'] || node['rsyslog']['group'] %> +<% end %> +<% unless node['rsyslog']['rate_limit_interval'].nil? %> +# +# Set the amount of time that is being measured for rate limiting +# +$SystemLogRateLimitInterval <%= node['rsyslog']['rate_limit_interval'] %> +<% end %> +<% unless node['rsyslog']['rate_limit_burst'].nil? %> +# +# Set the amount of messages, that have to occur in the time limit of +# SystemLogRateLimitInterval, to trigger rate limiting +# +$SystemLogRateLimitBurst <%= node['rsyslog']['rate_limit_burst'] %> +<% end %> +# +# Include all config files in <%= node['rsyslog']['config_prefix'] %>/rsyslog.d/ +# +$IncludeConfig <%= node['rsyslog']['config_prefix'] %>/rsyslog.d/*.conf +<% node['rsyslog']['additional_directives'].each_pair do |k,v| %> +$<%= k %> <%= v %> +<% end %> diff --git a/cookbooks/rsyslog/templates/smartos/50-default.conf.erb b/cookbooks/rsyslog/templates/smartos/50-default.conf.erb new file mode 100644 index 0000000..083c1ba --- /dev/null +++ b/cookbooks/rsyslog/templates/smartos/50-default.conf.erb @@ -0,0 +1,18 @@ +# Dropped of by Chef. Modifications will be lost. +# +# Default rules for rsyslog. +# +# For more information see rsyslog.conf(5) and <%= node['rsyslog']['config_prefix'] %>/rsyslog.conf + +*.err;kern.notice;auth.notice /dev/sysmsg +*.err;kern.debug;daemon.notice;mail.crit /var/adm/messages + +*.alert;kern.err;daemon.err operator +*.alert root + +*.emerg * + +mail.debug /var/log/syslog + +auth.info /var/log/auth.log +mail.info /var/log/postfix.log diff --git a/cookbooks/runit/CHANGELOG.md b/cookbooks/runit/CHANGELOG.md new file mode 100644 index 0000000..c10a8b0 --- /dev/null +++ b/cookbooks/runit/CHANGELOG.md @@ -0,0 +1,192 @@ +runit Cookbook CHANGELOG +======================== +This file is used to list changes made in each version of the runit cookbook. + +v1.6.0 (2015-04-06) +-------------------- +* Fedora 21 support +* Kitchen platform updates +* use imeyer’s packagecloud repo for RHEL +* fix converge_by usage +* do_action helper to set updated_by_last_action +* style fixes to provider + +v1.5.18 (2015-03-13) +-------------------- +* Add helper methods to detect installation presence + +v1.5.16 (2015-02-11) +-------------------- +* Allow removal of env files(nhuff) + +v1.5.14 (2015-01-15) +-------------------- +* Provide create action(clako) + +v1.5.12 (2014-12-15) +-------------------- +* prevent infinite loop inside docker container +* runit service failing inside docker container +* move to librarian-chef for kitchen dependency resolution +* update tests +* updates to chefspec matchers + +v1.5.10 (2014-03-07) +-------------------- +PR #53- Fix runit RPM file location for Chef provisionless Centos 5.9 Box Image + + +v1.5.9 +------ +Fix runit RPM file location for Chef provisionless Centos 5.9 Box Image + +v1.5.8 +------ +Fixing string interpolation bug + + +v1.5.3 +------ +Fixing assignment/compare error + + +v1.5.1 +------ +### Bug +- **[COOK-3950](https://tickets.chef.io/browse/COOK-3950)** - runit cookbook should use full service path when checking running status + + +v1.5.0 +------ +### Improvement +- **[COOK-3267] - Improve testing suite in runit cookbook +- Updating test-kitchen harness +- Cleaning up style for rubocop + + +v1.4.4 +------ +fixing metadata version error. locking to < 3.0 + + +v1.4.2 +------ +Locking yum dependency to '< 3' + +v1.4.0 +------ +[COOK-3560] Allow the user to configure runit's timeout (-w) and verbose (-v) settings + + +v1.3.0 +------ +### Improvement +- **[COOK-3663](https://tickets.chef.io/browse/COOK-3663)** - Add ./check scripts support + +### Bug +- **[COOK-3271](https://tickets.chef.io/browse/COOK-3271)** - Fix an issue where runit fails to install rpm package on rehl systems + +v1.2.0 +------ +### New Feature +- **[COOK-3243](https://tickets.chef.io/browse/COOK-3243)** - Expose LSB init directory as a configurable + +### Bug +- **[COOK-3182](https://tickets.chef.io/browse/COOK-3182)** - Do not hardcode rpmbuild location + +### Improvement +- **[COOK-3175](https://tickets.chef.io/browse/COOK-3175)** - Add svlogd config file support +- **[COOK-3115](https://tickets.chef.io/browse/COOK-3115)** - Add ability to install 'runit' package from Yum + +v1.1.6 +------ +### Bug +- [COOK-2353]: Runit does not update run template if the service is already enabled +- [COOK-3013]: Runit install fails on rhel if converge is only partially successful + +v1.1.4 +------ +### Bug +- [COOK-2549]: cannot enable_service (lwrp) on Gentoo +- [COOK-2567]: Runit doesn't start at boot in Gentoo +- [COOK-2629]: runit tests have ruby 1.9 method chaning syntax +- [COOK-2867]: On debian, runit recipe will follow symlinks from /etc/init.d, overwrite /usr/bin/sv + +v1.1.2 +------ +- [COOK-2477] - runit cookbook should enable EPEL repo for CentOS 5 +- [COOK-2545] - Runit cookbook fails on Amazon Linux +- [COOK-2322] - runit init template is broken on debian + +v1.1.0 +------ +- [COOK-2353] - Runit does not update run template if the service is already enabled +- [COOK-2497] - add :nothing to allowed actions + +v1.0.6 +------ +- [COOK-2404] - allow sending sigquit +- [COOK-2431] - gentoo - it should create the runit-start template before calling it + +v1.0.4 +------ +- [COOK-2351] - add `run_template_name` to allow alternate run script template + +v1.0.2 +------ +- [COOK-2299] - runit_service resource does not properly start a non-running service + +v1.0.0 +------ +- [COOK-2254] - (formerly CHEF-154) Convert `runit_service` definition to a service resource named `runit_service`. + +This version has some backwards incompatible changes (hence the major +version bump). It is recommended that users pin the cookbook to the +previous version where it is a dependency until this version has been +tested in a non-production environment (use version 0.16.2): + + depends "runit", "<= 0.16.2" + +If you use Chef environments, pin the version in the appropriate +environment(s). + +**Changes of note** + +1. The "runit" recipe must be included before the runit_service resource +can be used. +2. The `runit_service` definition created a separate `service` +resource for notification purposes. This is still available, but the +only actions that can be notified are `:start`, `:stop`, and `:restart`. +3. The `:enable` action blocks waiting for supervise/ok after the +service symlink is created. +4. User-controlled services should be created per the runit +documentation; see README.md for an example. +5. Some parameters in the definition have changed names in the +resource. See below. + +The following parameters in the definition are renamed in the resource +to clarify their intent. + +- directory -> sv_dir +- active_directory -> service_dir +- template_name -> use service_name (name attribute) +- nolog -> set "log" to false +- start_command -> unused (was previously in the "service" resource) +- stop_command -> unused (was previously in the "service" resource) +- restart_command -> unused (was previously in the "service" resource) + +v0.16.2 +------- +- [COOK-1576] - Do not symlink /etc/init.d/servicename to /usr/bin/sv on debian +- [COOK-1960] - default_logger still looks for sv-service-log-run template +- [COOK-2035] - runit README change + +v0.16.0 +------- +- [COOK-794] default logger and `no_log` for `runit_service` definition +- [COOK-1165] - restart functionality does not work right on Gentoo due to the wrong directory in the attributes +- [COOK-1440] - Delegate service control to normal user + +v0.15.0 +------- +- [COOK-1008] - Added parameters for names of different templates in runit diff --git a/cookbooks/runit/README.md b/cookbooks/runit/README.md new file mode 100644 index 0000000..9f1fbb8 --- /dev/null +++ b/cookbooks/runit/README.md @@ -0,0 +1,421 @@ +runit Cookbook +============== +Installs runit and provides the `runit_service` service resource for managing processes (services) under runit. + +This cookbook does not use runit to replace system init, nor are ther plans to do so. + +For more information about runit: + +- http://smarden.org/runit/ + + +Requirements +------------ +### Platforms +- Debian/Ubuntu +- Gentoo +- RHEL + +### Cookbooks +- packagecloud (for RHEL) + +Attributes +---------- +See `attributes/default.rb` for defaults generated per platform. + +- `node['runit']['sv_bin']` - Full path to the `sv` binary. +- `node['runit']['chpst_bin']` - Full path to the `chpst` binary. +- `node['runit']['service_dir']` - Full path to the default "services" directory where enabled services are linked. +- `node['runit']['sv_dir']` - Full path to the directory where service lives, which gets linked to `service_dir`. +- `node['runit']['lsb_init_dir']` - Full path to the directory where the LSB-compliant init script interface will be created. +- `node['runit']['start']` - Command to start the runsvdir service +- `node['runit']['stop]` - Command to stop the runsvdir service +- `node['runit']['reload']` - Command to reload the runsvdir service + +### Optional Attributes for RHEL systems + +- `node['runit']['prefer_local_yum']` - If `true`, assumes that a `runit` package is available on an already configured local yum repository. By default, the recipe installs the `runit` package from a Package Cloud repository (see below). This is set to the value of `node['runit']['use_package_from_yum']` for backwards compatibility, but otherwise defaults to `false`. + +Recipes +------- +### default +The default recipe installs runit and starts `runsvdir` to supervise the services in runit's service directory (e.g., `/etc/service`). + +On RHEL-family systems, it will install the runit RPM using [Ian Meyer's Package Cloud repository](https://packagecloud.io/imeyer/runit) for runit. This replaces the previous functionality where the RPM was build using his [runit RPM SPEC](https://github.com/imeyer/runit-rpm). However, if the attribute `node['runit']['prefer_local_yum']` is set to `true`, the packagecloud repository creation will be skipped and it is assumed that a `runit` package is available on an otherwise configured (outside this cookbook) local repository. + +On Debian family systems, the runit packages are maintained by the runit author, Gerrit Pape, and the recipe will use that for installation. + +On Gentoo, the runit ebuild package is installed. + +Resource/Provider +----------------- +This cookbook has a resource, `runit_service`, for managing services under runit. This service subclasses the Chef `service` resource. + +**This resource replaces the runit_service definition. See the CHANGELOG.md file in this cookbook for breaking change information and any actions you may need to take to update cookbooks using runit_service.** + +### Actions +- **enable** - enables the service, creating the required run scripts and symlinks. This is the default action. +- **start** - starts the service with `sv start` +- **stop** - stops the service with `sv stop` +- **disable** - stops the service with `sv down` and removes the service symlink +- **create** - create the service directory, but don't enable the service with symlink +- **restart** - restarts the service with `sv restart` +- **reload** - reloads the service with `sv force-reload` +- **once** - starts the service with `sv once`. +- **hup** - sends the `HUP` signal to the service with `sv hup` +- **cont** - sends the `CONT` signal to the service +- **term** - sends the `TERM` signal to the service +- **kill** - sends the `KILL` signal to the service +- **up** - starts the service with `sv up` +- **down** - downs the service with `sv down` +- **usr1** - sends the `USR1` signal to the service with `sv 1` +- **usr2** - sends the `USR2` signal to the service with `sv 2` + +Service management actions are taken with runit's "`sv`" program. + +Read the `sv(8)` [man page](http://smarden.org/runit/sv.8.html) for more information on the `sv` program. + +### Parameter Attributes + +The first three parameters, `sv_dir`, `service_dir`, and `sv_bin` will attempt to use the corresponding node attributes, and fall back to hardcoded default values that match the settings used on Debian platform systems. + +Many of these parameters are only used in the `:enable` action. + +- **sv_dir** - The base "service directory" for the services managed by + the resource. By default, this will attempt to use the + `node['runit']['sv_dir']` attribute, and falls back to `/etc/sv`. +- **service_dir** - The directory where services are symlinked to be + supervised by `runsvdir`. By default, this will attempt to use the + `node['runit']['service_dir']` attribute, and falls back to + `/etc/service`. +- **lsb_init_dir** - The directory where an LSB-compliant init script + interface will be created. By default, this will attempt to use the + `node['runit']['lsb_init_dir']` attribute, and falls back to + `/etc/init.d`. +- **sv_bin** - The path to the `sv` program binary. This will attempt + to use the `node['runit']['sv_bin']` attribute, and falls back to + `/usr/bin/sv`. +- **service_name** - *Name attribute*. The name of the service. This + will be used in the directory of the managed service in the + `sv_dir` and `service_dir`. +- **sv_timeout** - Override the default `sv` timeout of 7 seconds. +- **sv_verbose** - Whether to enable `sv` verbose mode. Default is + `false`. +- **sv_templates** - If true, the `:enable` action will create the + service directory with the appropriate templates. Default is + `true`. Set this to `false` if the service has a package that + provides its own service directory. See __Usage__ examples. +- **options** - Options passed as variables to templates, for + compatibility with legacy runit service definition. Default is an + empty hash. +- **env** - A hash of environment variables with their values as content + used in the service's `env` directory. Default is an empty hash. +- **log** - Whether to start the service's logger with svlogd, requires + a template `sv-service_name-log-run.erb` to configure the log's run + script. Default is true. +- **default_logger** - Whether a default `log/run` script should be set + up. If true, the default content of the run script will use + `svlogd` to write logs to `/var/log/service_name`. Default is false. +- **log_size** - The maximum size a log file can grow to before it is + automatically rotated. See svlogd(8) for the default value. +- **log_num** - The maximum number of log files that will be retained + after rotation. See svlogd(8) for the default value. +- **log_min** - The minimum number of log files that will be retained + after rotation (if svlogd cannot create a new file and the minimum + has not been reached, it will block). Default is no minimum. +- **log_timeout** - The maximum age a log file can get to before it is + automatically rotated, whether it has reached `log_size` or not. + Default is no timeout. +- **log_processor** - A string containing a path to a program that + rotated log files will be fed through. See the **PROCESSOR** section + of svlogd(8) for details. Default is no processor. +- **log_socket** - An string containing an IP:port pair identifying a UDP + socket that log lines will be copied to. Default is none. +- **log_prefix** - A string that will be prepended to each line as it + is logged. Default is no prefix. +- **log_config_append** - A string containing optional additional lines to add + to the log service configuration. See svlogd(8) for more details. +- **cookbook** - A cookbook where templates are located instead of + where the resource is used. Applies for all the templates in the + `enable` action. +- **check** - whether the service has a check script, requires a + template `sv-service_name-check.erb` +- **finish** - whether the service has a finish script, requires a + template `sv-service_name-finish.erb` +- **control** - An array of signals to customize control of the service, + see [runsv man page](http://smarden.org/runit/runsv.8.html) on how + to use this. This requires that each template be created with the + name `sv-service_name-signal.erb`. +- **owner** - user that should own the templates created to enable the + service +- **group** - group that should own the templates created to enable the + service +- **run_template_name** - alternate filename of the run run script to + use replacing `service_name`. +- **log_template_name** - alternate filename of the log run script to + use replacing `service_name`. +- **check_script_template_name** - alternate filename of the check + script to use, replacing `service_name`. +- **finish_script_template_name** - alternate filename of the finish + script to use, replacing `service_name`. +- **control_template_names** - a hash of control signals (see *control* + above) and their alternate template name(s) replacing + `service_name`. +- **status_command** - The command used to check the status of the + service to see if it is enabled/running (if it's running, it's + enabled). This hardcodes the location of the sv program to + `/usr/bin/sv` due to the aforementioned cookbook load order. +- **restart_on_update** - Whether the service should be restarted when + the run script is updated. Defaults to `true`. Set to `false` if + the service shouldn't be restarted when the run script is updated. + +Unlike previous versions of the cookbook using the `runit_service` definition, the `runit_service` resource can be notified. See __Usage__ examples below. + + +Usage +----- +To get runit installed on supported platforms, use `recipe[runit]`. Once it is installed, use the `runit_service` resource to set up services to be managed by runit. + +In order to use the `runit_service` resource in your cookbook(s), each service managed will also need to have `sv-service_name-run.erb` and `sv-service_name-log-run.erb` templates created. If the `log` parameter is false, the log run script isn't created. If the `log` parameter is true, and `default_logger` is also true, the log run +script will be created with the default content: + +```bash +#!/bin/sh +exec svlogd -tt /var/log/service_name +``` + +### Examples +These are example use cases of the `runit_service` resource described above. There are others in the `runit_test` cookbook that is included in the [git repository](https://github.com/chef-cookbooks/runit). + +**Default Example** + +This example uses all the defaults in the `:enable` action to set up the service. + +We'll set up `chef-client` to run as a service under runit, such as is done in the `chef-client` cookbook. This example will be more simple than in that cookbook. First, create the required run template, `chef-client/templates/default/sv-chef-client-run.erb`. + +```bash +#!/bin/sh +exec 2>&1 +exec /usr/bin/env chef-client -i 1800 -s 30 +``` + +Then create the required log/run template, `chef-client/templates/default/sv-chef-client-log-run.erb`. + +```bash +#!/bin/sh +exec svlogd -tt ./main +``` + +__Note__ This will cause output of the running process to go to `/etc/sv/chef-client/log/main/current`. Some people may not like this, see the following example. This is preserved for compatibility reasons. + +Finally, set up the service in the recipe with: + +```ruby +runit_service "chef-client" +``` + +**Default Logger Example** + +To use a default logger with svlogd which will log to `/var/log/chef-client/current`, instead, use the `default_logger` option. + +```ruby +runit_service "chef-client" do + default_logger true +end +``` + +**No Log Service** + +If there isn't an appendant log service, set `log` to false, and the log/run script won't be created. + +```ruby +runit_service "no-svlog" do + log false +end +``` + +**Check Script** + +To create a service that has a check script in its service directory, set the `check` parameter to `true`, and create a `sv-checker-check.erb` template. + +```ruby +runit_service "checker" do + check true +end +``` + +This will create `/etc/sv/checker/check`. + +**Finish Script** + +To create a service that has a finish script in its service directory, set the `finish` parameter to `true`, and create a `sv-finisher-finish.erb` template. + +```ruby +runit_service "finisher" do + finish true +end +``` + +This will create `/etc/sv/finisher/finish`. + +**Alternate service directory** + +If the service directory for the managed service isn't the `sv_dir` (`/etc/sv`), then specify it: + +```ruby +runit_service "custom_service" do + sv_dir "/etc/custom_service/runit" +end +``` + +**No Service Directory** + +If the service to manage has a package that provides its service directory, such as `git-daemon` on Debian systems, set `sv_templates` to false. + +```ruby +package "git-daemon-run" + +runit_service "git-daemon" do + sv_templates false +end +``` + +This will create the service symlink in `/etc/service`, but it will not manage any templates in the service directory. + +**User Controlled Services** + +To set up services controlled by a non-privileged user, we follow the recommended configuration in the [runit documentation](http://smarden.org/runit/faq.html#user) (Is it possible to allow a user other than root to control a service?). + +Suppose the user's name is floyd, and floyd wants to run floyds-app. Assuming that the floyd user and group are already managed with Chef, create a `runsvdir-floyd` runit_service. + +```ruby +runit_service "runsvdir-floyd" +``` + +Create the `sv-runsvdir-floyd-log-run.erb` template, or add `log false`. Also create the `sv-runsvdir-floyd-run.erb` with the following content: + +```bash +#!/bin/sh +exec 2>&1 +exec chpst -ufloyd runsvdir /home/floyd/service +``` + +Next, create the `runit_service` resource for floyd's app: + +```ruby +runit_service "floyds-app" do + sv_dir "/home/floyd/sv" + service_dir "/home/floyd/service" + owner "floyd" + group "floyd" +end +``` + +And now floyd can manage the service with sv: + +```text +$ id +uid=1000(floyd) gid=1001(floyd) groups=1001(floyd) +$ sv stop /home/floyd/service/floyds-app/ +ok: down: /home/floyd/service/floyds-app/: 0s, normally up +$ sv start /home/floyd/service/floyds-app/ +ok: run: /home/floyd/service/floyds-app/: (pid 5287) 0s +$ sv status /home/floyd/service/floyds-app/ +run: /home/floyd/service/floyds-app/: (pid 5287) 13s; run: log: (pid 4691) 726s +``` + +**Options** + +Next, let's set up memcached under runit with some additional options using the `options` parameter. First, the `memcached/templates/default/sv-memcached-run.erb` template: + +```bash +#!/bin/sh +exec 2>&1 +exec chpst -u <%= @options[:user] %> /usr/bin/memcached -v -m <%= @options[:memory] %> -p <%= @options[:port] %> +``` + +Note that the script uses `chpst` (which comes with runit) to set the user option, then starts memcached on the specified memory and port (see below). + +The log/run template, `memcached/templates/default/sv-memcached-log-run.erb`: + +```bash +#!/bin/sh +exec svlogd -tt ./main +``` + +Finally, the `runit_service` in our recipe: + +```ruby +runit_service "memcached" do + options({ + :memory => node[:memcached][:memory], + :port => node[:memcached][:port], + :user => node[:memcached][:user]}.merge(params) + }) +end +``` + +This is where the user, port and memory options used in the run template are used. + +**Notifying Runit Services** + +In previous versions of this cookbook where the definition was used, it created a `service` resource that could be notified. With the `runit_service` resource, recipes need to use the full resource name. + +For example: + +```ruby +runit_service "my-service" + +template "/etc/my-service.conf" do + notifies :restart, "runit_service[my-service]" +end +``` + +Because the resource implements actions for various commands that `sv` can send to the service, any of those actions could be used for notification. For example, `chef-client` supports triggering a Chef run with a USR1 signal. + +```ruby +template "/tmp/chef-notifier" do + notifies :usr1, "runit_service[chef-client]" +end +``` + +For older implementations of services that used `runit_service` as a definition, but may support alternate service styles, use a conditional, such as based on an attribute: + +```ruby +service_to_notify = case node['nginx']['init_style'] + when "runit" + "runit_service[nginx]" + else + "service[nginx]" + end + +template "/etc/nginx/nginx.conf" do + notifies :restart, service_to_notify +end +``` + +**More Examples** + +For more examples, see the `runit_test` cookbook's `service` recipe in the [git repository](https://github.com/chef-cookbooks/runit). + + +License & Authors +----------------- +- Author:: Adam Jacob +- Author:: Joshua Timberman + +```text +Copyright:: 2008-2013, Chef Software, Inc + +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/cookbooks/runit/attributes/default.rb b/cookbooks/runit/attributes/default.rb new file mode 100644 index 0000000..f201c8b --- /dev/null +++ b/cookbooks/runit/attributes/default.rb @@ -0,0 +1,62 @@ +# +# Cookbook Name:: runit +# Attribute File:: sv_bin +# +# Copyright 2008-2009, Chef Software, Inc. +# +# 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. +# + +case node['platform_family'] +when 'debian' + default['runit']['sv_bin'] = '/usr/bin/sv' + default['runit']['chpst_bin'] = '/usr/bin/chpst' + default['runit']['service_dir'] = '/etc/service' + default['runit']['sv_dir'] = '/etc/sv' + default['runit']['lsb_init_dir'] = '/etc/init.d' + default['runit']['executable'] = '/sbin/runit' + + if node['platform'] == 'debian' + default['runit']['start'] = 'runsvdir-start' + default['runit']['stop'] = '' + default['runit']['reload'] = '' + elsif node['platform'] == 'ubuntu' + default['runit']['start'] = 'start runsvdir' + default['runit']['stop'] = 'stop runsvdir' + default['runit']['reload'] = 'reload runsvdir' + end + +when 'rhel', 'fedora' + default['runit']['sv_bin'] = '/sbin/sv' + default['runit']['chpst_bin'] = '/sbin/chpst' + default['runit']['service_dir'] = '/etc/service' + default['runit']['sv_dir'] = '/etc/sv' + default['runit']['lsb_init_dir'] = '/etc/init.d' + default['runit']['executable'] = '/sbin/runit' + default['runit']['prefer_local_yum'] = node['runit']['use_package_from_yum'] || false + default['runit']['start'] = '/etc/init.d/runit-start start' + default['runit']['stop'] = '/etc/init.d/runit-start stop' + default['runit']['reload'] = '/etc/init.d/runit-start reload' + +when 'gentoo' + default['runit']['sv_bin'] = '/usr/bin/sv' + default['runit']['chpst_bin'] = '/usr/bin/chpst' + default['runit']['service_dir'] = '/var/service' + default['runit']['sv_dir'] = '/etc/sv' + default['runit']['lsb_init_dir'] = '/etc/init.d' + default['runit']['executable'] = '/sbin/runit' + default['runit']['start'] = '/etc/init.d/runit-start start' + default['runit']['stop'] = '/etc/init.d/runit-start stop' + default['runit']['reload'] = '/etc/init.d/runit-start reload' + +end diff --git a/cookbooks/runit/files/default/runit.seed b/cookbooks/runit/files/default/runit.seed new file mode 100644 index 0000000..6492920 --- /dev/null +++ b/cookbooks/runit/files/default/runit.seed @@ -0,0 +1 @@ +runit runit/signalinit boolean true diff --git a/cookbooks/runit/files/default/runsvdir b/cookbooks/runit/files/default/runsvdir new file mode 100644 index 0000000..e69de29 diff --git a/cookbooks/runit/files/ubuntu-6.10/runsvdir b/cookbooks/runit/files/ubuntu-6.10/runsvdir new file mode 100644 index 0000000..4040e34 --- /dev/null +++ b/cookbooks/runit/files/ubuntu-6.10/runsvdir @@ -0,0 +1,6 @@ +start on runlevel-2 +start on runlevel-3 +start on runlevel-4 +start on runlevel-5 +stop on shutdown +respawn /usr/sbin/runsvdir-start diff --git a/cookbooks/runit/files/ubuntu-7.04/runsvdir b/cookbooks/runit/files/ubuntu-7.04/runsvdir new file mode 100644 index 0000000..ee173c9 --- /dev/null +++ b/cookbooks/runit/files/ubuntu-7.04/runsvdir @@ -0,0 +1,7 @@ +start on runlevel 2 +start on runlevel 3 +start on runlevel 4 +start on runlevel 5 +stop on shutdown +respawn +exec /usr/sbin/runsvdir-start diff --git a/cookbooks/runit/files/ubuntu-7.10/runsvdir b/cookbooks/runit/files/ubuntu-7.10/runsvdir new file mode 100644 index 0000000..ee173c9 --- /dev/null +++ b/cookbooks/runit/files/ubuntu-7.10/runsvdir @@ -0,0 +1,7 @@ +start on runlevel 2 +start on runlevel 3 +start on runlevel 4 +start on runlevel 5 +stop on shutdown +respawn +exec /usr/sbin/runsvdir-start diff --git a/cookbooks/runit/files/ubuntu-8.04/runsvdir b/cookbooks/runit/files/ubuntu-8.04/runsvdir new file mode 100644 index 0000000..ee173c9 --- /dev/null +++ b/cookbooks/runit/files/ubuntu-8.04/runsvdir @@ -0,0 +1,7 @@ +start on runlevel 2 +start on runlevel 3 +start on runlevel 4 +start on runlevel 5 +stop on shutdown +respawn +exec /usr/sbin/runsvdir-start diff --git a/cookbooks/runit/libraries/default.rb b/cookbooks/runit/libraries/default.rb new file mode 100644 index 0000000..e69de29 diff --git a/cookbooks/runit/libraries/helpers.rb b/cookbooks/runit/libraries/helpers.rb new file mode 100644 index 0000000..fc47d66 --- /dev/null +++ b/cookbooks/runit/libraries/helpers.rb @@ -0,0 +1,45 @@ +# +# Cookbook:: runit +# Libraries:: helpers +# +# Author: Joshua Timberman +# Copyright (c) 2014, Chef Software, Inc. +# +# 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. +# + +require 'chef/mixin/shell_out' +include Chef::Mixin::ShellOut +module Runit + module Helpers + def runit_installed? + return true if runit_rpm_installed? || (runit_executable? && runit_sv_works?) + end + + def runit_executable? + ::File.executable?(node['runit']['executable']) + end + + def runit_sv_works? + sv = shell_out("#{node['runit']['sv_bin']} --help") + sv.exitstatus == 100 && sv.stderr =~ /usage: sv .* command service/ + end + + def runit_rpm_installed? + shell_out('rpm -qa | grep -q "^runit"').exitstatus == 0 + end + end +end + +Chef::Recipe.send(:include, Runit::Helpers) +Chef::Resource.send(:include, Runit::Helpers) diff --git a/cookbooks/runit/libraries/matchers.rb b/cookbooks/runit/libraries/matchers.rb new file mode 100644 index 0000000..c7c6d29 --- /dev/null +++ b/cookbooks/runit/libraries/matchers.rb @@ -0,0 +1,67 @@ +if defined?(ChefSpec) + + def start_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :start, service) + end + + def stop_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :stop, service) + end + + def enable_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :enable, service) + end + + def disable_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :disable, service) + end + + def restart_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :restart, service) + end + + def reload_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :reload, service) + end + + def status_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :status, service) + end + + def once_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :once, service) + end + + def hup_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :hup, service) + end + + def cont_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :cont, service) + end + + def term_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :term, service) + end + + def kill_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :kill, service) + end + + def up_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :up, service) + end + + def down_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :down, service) + end + + def usr1_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :usr1, service) + end + + def usr2_runit_service(service) + ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :usr2, service) + end + +end diff --git a/cookbooks/runit/libraries/provider_runit_service.rb b/cookbooks/runit/libraries/provider_runit_service.rb new file mode 100644 index 0000000..3fefe0f --- /dev/null +++ b/cookbooks/runit/libraries/provider_runit_service.rb @@ -0,0 +1,550 @@ +# +# Cookbook Name:: runit +# Provider:: service +# +# Copyright 2011, Joshua Timberman +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +require 'chef/provider/service' +require 'chef/provider/link' +require 'chef/resource/link' +require 'chef/provider/directory' +require 'chef/resource/directory' +require 'chef/provider/template' +require 'chef/resource/template' +require 'chef/provider/file' +require 'chef/resource/file' +require 'chef/mixin/shell_out' +require 'chef/mixin/language' + +class Chef + class Provider + class Service + class Runit < Chef::Provider::Service + # refactor this whole thing into a Chef11 LWRP + include Chef::Mixin::ShellOut + + def initialize(*args) + super + new_resource.supports[:status] = true + end + + def load_current_resource + @current_resource = Chef::Resource::RunitService.new(new_resource.name) + current_resource.service_name(new_resource.service_name) + + Chef::Log.debug("Checking status of service #{new_resource.service_name}") + + # verify Runit was installed properly + unless ::File.exist?(new_resource.sv_bin) && ::File.executable?(new_resource.sv_bin) + no_runit_message = "Could not locate main runit sv_bin at \"#{new_resource.sv_bin}\". " + no_runit_message << "Did you remember to install runit before declaring a \"runit_service\" resource? " + no_runit_message << "\n\nTry adding the following to the top of your recipe:\n\ninclude_recipe \"runit\"" + fail no_runit_message + end + + current_resource.running(running?) + current_resource.enabled(enabled?) + current_resource.env(get_current_env) + current_resource + end + + # + # Chef::Provider::Service overrides + # + + def action_create + configure_service # Do this every run, even if service is already enabled and running + Chef::Log.info("#{new_resource} configured") + end + + def action_enable + configure_service # Do this every run, even if service is already enabled and running + Chef::Log.info("#{new_resource} configured") + if current_resource.enabled + Chef::Log.debug("#{new_resource} already enabled - nothing to do") + else + enable_service + Chef::Log.info("#{new_resource} enabled") + end + load_new_resource_state + new_resource.enabled(true) + restart_service if new_resource.restart_on_update && run_script.updated_by_last_action? + restart_log_service if new_resource.restart_on_update && log_run_script.updated_by_last_action? + restart_log_service if new_resource.restart_on_update && log_config_file.updated_by_last_action? + end + + def configure_service + if new_resource.sv_templates + Chef::Log.debug("Creating sv_dir for #{new_resource.service_name}") + do_action(sv_dir, :create) + Chef::Log.debug("Creating run_script for #{new_resource.service_name}") + do_action(run_script, :create) + + if new_resource.log + Chef::Log.debug("Setting up svlog for #{new_resource.service_name}") + do_action(log_dir, :create) + do_action(log_main_dir, :create) + do_action(default_log_dir, :create) if new_resource.default_logger + do_action(log_run_script, :create) + do_action(log_config_file, :create) + else + Chef::Log.debug("log not specified for #{new_resource.service_name}, continuing") + end + + unless new_resource.env.empty? + Chef::Log.debug("Setting up environment files for #{new_resource.service_name}") + do_action(env_dir, :create) + env_files.each do |file| + file.action.each { |action| do_action(file, action) } + end + else + Chef::Log.debug("Environment not specified for #{new_resource.service_name}, continuing") + end + + if new_resource.check + Chef::Log.debug("Creating check script for #{new_resource.service_name}") + do_action(check_script, :create) + else + Chef::Log.debug("Check script not specified for #{new_resource.service_name}, continuing") + end + + if new_resource.finish + Chef::Log.debug("Creating finish script for #{new_resource.service_name}") + do_action(finish_script, :create) + else + Chef::Log.debug("Finish script not specified for #{new_resource.service_name}, continuing") + end + + unless new_resource.control.empty? + Chef::Log.debug("Creating control signal scripts for #{new_resource.service_name}") + do_action(control_dir, :create) + control_signal_files.each { |file| do_action(file, :create) } + else + Chef::Log.debug("Control signals not specified for #{new_resource.service_name}, continuing") + end + end + + Chef::Log.debug("Creating lsb_init compatible interface #{new_resource.service_name}") + do_action(lsb_init, :create) + end + + def enable_service + Chef::Log.debug("Creating symlink in service_dir for #{new_resource.service_name}") + do_action(service_link, :create) + + unless inside_docker? + Chef::Log.debug("waiting until named pipe #{service_dir_name}/supervise/ok exists.") + until ::FileTest.pipe?("#{service_dir_name}/supervise/ok") + sleep 1 + Chef::Log.debug('.') + end + + if new_resource.log + Chef::Log.debug("waiting until named pipe #{service_dir_name}/log/supervise/ok exists.") + until ::FileTest.pipe?("#{service_dir_name}/log/supervise/ok") + sleep 1 + Chef::Log.debug('.') + end + end + else + Chef::Log.debug("skipping */supervise/ok check inside docker") + end + end + + def disable_service + shell_out("#{new_resource.sv_bin} #{sv_args}down #{service_dir_name}") + Chef::Log.debug("#{new_resource} down") + FileUtils.rm(service_dir_name) + Chef::Log.debug("#{new_resource} service symlink removed") + end + + def start_service + shell_out!("#{new_resource.sv_bin} #{sv_args}start #{service_dir_name}") + end + + def stop_service + shell_out!("#{new_resource.sv_bin} #{sv_args}stop #{service_dir_name}") + end + + def restart_service + shell_out!("#{new_resource.sv_bin} #{sv_args}restart #{service_dir_name}") + end + + def restart_log_service + shell_out!("#{new_resource.sv_bin} #{sv_args}restart #{service_dir_name}/log") + end + + def reload_service + shell_out!("#{new_resource.sv_bin} #{sv_args}force-reload #{service_dir_name}") + end + + def reload_log_service + shell_out!("#{new_resource.sv_bin} #{sv_args}force-reload #{service_dir_name}/log") + end + + # + # Addtional Runit-only actions + # + + # only take action if the service is running + [:down, :hup, :int, :term, :kill, :quit].each do |signal| + define_method "action_#{signal}".to_sym do + if current_resource.running + runit_send_signal(signal) + else + Chef::Log.debug("#{new_resource} not running - nothing to do") + end + end + end + + # only take action if service is *not* running + [:up, :once, :cont].each do |signal| + define_method "action_#{signal}".to_sym do + if current_resource.running + Chef::Log.debug("#{new_resource} already running - nothing to do") + else + runit_send_signal(signal) + end + end + end + + def action_usr1 + runit_send_signal(1, :usr1) + end + + def action_usr2 + runit_send_signal(2, :usr2) + end + + private + + def runit_send_signal(signal, friendly_name = nil) + friendly_name ||= signal + converge_by("send #{friendly_name} to #{new_resource}") do + shell_out!("#{new_resource.sv_bin} #{sv_args}#{signal} #{service_dir_name}") + Chef::Log.info("#{new_resource} sent #{friendly_name}") + end + end + + def running? + cmd = shell_out("#{new_resource.sv_bin} #{sv_args}status #{service_dir_name}") + (cmd.stdout =~ /^run:/ && cmd.exitstatus == 0) + end + + def log_running? + cmd = shell_out("#{new_resource.sv_bin} #{sv_args}status #{service_dir_name}/log") + (cmd.stdout =~ /^run:/ && cmd.exitstatus == 0) + end + + def enabled? + ::File.exists?(::File.join(service_dir_name, 'run')) + end + + def log_service_name + ::File.join(new_resource.service_name, 'log') + end + + def sv_dir_name + ::File.join(new_resource.sv_dir, new_resource.service_name) + end + + def sv_args + sv_args = '' + sv_args += "-w '#{new_resource.sv_timeout}' " unless new_resource.sv_timeout.nil? + sv_args += '-v ' if new_resource.sv_verbose + sv_args + end + + def service_dir_name + ::File.join(new_resource.service_dir, new_resource.service_name) + end + + def log_dir_name + ::File.join(new_resource.service_dir, new_resource.service_name, log) + end + + def template_cookbook + new_resource.cookbook.nil? ? new_resource.cookbook_name.to_s : new_resource.cookbook + end + + def default_logger_content + "#!/bin/sh +exec svlogd -tt /var/log/#{new_resource.service_name}" + end + + # + # Helper Resources + # + def do_action(resource, action) + resource.run_action(action) + new_resource.updated_by_last_action(true) if resource.updated_by_last_action? + end + + def sv_dir + @sv_dir ||= + begin + d = Chef::Resource::Directory.new(sv_dir_name, run_context) + d.recursive(true) + d.owner(new_resource.owner) + d.group(new_resource.group) + d.mode(00755) + d + end + end + + def run_script + @run_script ||= + begin + t = Chef::Resource::Template.new(::File.join(sv_dir_name, 'run'), run_context) + t.owner(new_resource.owner) + t.group(new_resource.group) + t.source("sv-#{new_resource.run_template_name}-run.erb") + t.cookbook(template_cookbook) + t.mode(00755) + t.variables(:options => new_resource.options) if new_resource.options.respond_to?(:has_key?) + t + end + end + + def log_dir + @log_dir ||= + begin + d = Chef::Resource::Directory.new(::File.join(sv_dir_name, 'log'), run_context) + d.recursive(true) + d.owner(new_resource.owner) + d.group(new_resource.group) + d.mode(00755) + d + end + end + + def log_main_dir + @log_main_dir ||= + begin + d = Chef::Resource::Directory.new(::File.join(sv_dir_name, 'log', 'main'), run_context) + d.recursive(true) + d.owner(new_resource.owner) + d.group(new_resource.group) + d.mode(00755) + d + end + end + + def default_log_dir + @default_log_dir ||= + begin + d = Chef::Resource::Directory.new(::File.join("/var/log/#{new_resource.service_name}"), run_context) + d.recursive(true) + d.owner(new_resource.owner) + d.group(new_resource.group) + d.mode(00755) + d + end + end + + def log_run_script + @log_run_script ||= + begin + if new_resource.default_logger + f = Chef::Resource::File.new( + ::File.join(sv_dir_name, 'log', 'run'), + run_context + ) + f.content(default_logger_content) + f.owner(new_resource.owner) + f.group(new_resource.group) + f.mode(00755) + f + else + t = Chef::Resource::Template.new( + ::File.join(sv_dir_name, 'log', 'run'), + run_context + ) + t.owner(new_resource.owner) + t.group(new_resource.group) + t.mode(00755) + t.source("sv-#{new_resource.log_template_name}-log-run.erb") + t.cookbook(template_cookbook) + t.variables(:options => new_resource.options) if new_resource.options.respond_to?(:has_key?) + t + end + end + end + + def log_config_file + @log_config_file ||= + begin + t = Chef::Resource::Template.new(::File.join(sv_dir_name, 'log', 'config'), run_context) + t.owner(new_resource.owner) + t.group(new_resource.group) + t.mode(00644) + t.cookbook('runit') + t.source('log-config.erb') + t.variables( + :size => new_resource.log_size, + :num => new_resource.log_num, + :min => new_resource.log_min, + :timeout => new_resource.log_timeout, + :processor => new_resource.log_processor, + :socket => new_resource.log_socket, + :prefix => new_resource.log_prefix, + :append => new_resource.log_config_append + ) + t + end + end + + def env_dir + @env_dir ||= + begin + d = Chef::Resource::Directory.new(::File.join(sv_dir_name, 'env'), run_context) + d.owner(new_resource.owner) + d.group(new_resource.group) + d.mode(00755) + d + end + end + + def env_files + @env_files ||= + begin + create_files = new_resource.env.map do |var, value| + f = Chef::Resource::File.new(::File.join(sv_dir_name, 'env', var), run_context) + f.owner(new_resource.owner) + f.group(new_resource.group) + f.content(value) + f.action(:create) + f + end + extra_env = current_resource.env.reject { |k,_| new_resource.env.key?(k) } + delete_files = extra_env.map do |k,_| + f = Chef::Resource::File.new(::File.join(sv_dir_name, 'env', k), run_context) + f.action(:delete) + f + end + create_files + delete_files + end + end + + def get_current_env + env_dir = ::File.join(sv_dir_name, 'env') + return {} unless ::File.directory? env_dir + files = ::Dir.glob(::File.join(env_dir,'*')) + env = files.reduce({}) do |c,o| + contents = ::IO.read(o).rstrip + c.merge!(::File.basename(o) => contents) + end + env + end + + def check_script + @check_script ||= + begin + t = Chef::Resource::Template.new(::File.join(sv_dir_name, 'check'), run_context) + t.owner(new_resource.owner) + t.group(new_resource.group) + t.source("sv-#{new_resource.check_script_template_name}-check.erb") + t.cookbook(template_cookbook) + t.mode(00755) + t.variables(:options => new_resource.options) if new_resource.options.respond_to?(:has_key?) + t + end + end + + def finish_script + @finish_script ||= + begin + t = Chef::Resource::Template.new(::File.join(sv_dir_name, 'finish'), run_context) + t.owner(new_resource.owner) + t.group(new_resource.group) + t.mode(00755) + t.source("sv-#{new_resource.finish_script_template_name}-finish.erb") + t.cookbook(template_cookbook) + t.variables(:options => new_resource.options) if new_resource.options.respond_to?(:has_key?) + t + end + end + + def control_dir + @control_dir ||= + begin + d = Chef::Resource::Directory.new(::File.join(sv_dir_name, 'control'), run_context) + d.owner(new_resource.owner) + d.group(new_resource.group) + d.mode(00755) + d + end + end + + def control_signal_files + @control_signal_files ||= + begin + new_resource.control.map do |signal| + t = Chef::Resource::Template.new( + ::File.join(sv_dir_name, 'control', signal), + run_context + ) + t.owner(new_resource.owner) + t.group(new_resource.group) + t.mode(00755) + t.source("sv-#{new_resource.control_template_names[signal]}-#{signal}.erb") + t.cookbook(template_cookbook) + t.variables(:options => new_resource.options) if new_resource.options.respond_to?(:has_key?) + t + end + end + end + + def lsb_init + @lsb_init ||= + begin + initfile = ::File.join(new_resource.lsb_init_dir, new_resource.service_name) + if node['platform'] == 'debian' + ::File.unlink(initfile) if ::File.symlink?(initfile) + t = Chef::Resource::Template.new(initfile, run_context) + t.owner('root') + t.group('root') + t.mode(00755) + t.cookbook('runit') + t.source('init.d.erb') + t.variables(:name => new_resource.service_name) + t + else + l = Chef::Resource::Link.new(initfile, run_context) + l.to(new_resource.sv_bin) + l + end + end + end + + def service_link + @service_link ||= + begin + l = Chef::Resource::Link.new(::File.join(service_dir_name), run_context) + l.to(sv_dir_name) + l + end + end + + def inside_docker? + results = `cat /proc/1/cgroup`.strip.split("\n") + results.any?{|val| /docker/ =~ val} + end + end + end + end +end diff --git a/cookbooks/runit/libraries/resource_runit_service.rb b/cookbooks/runit/libraries/resource_runit_service.rb new file mode 100644 index 0000000..ec651de --- /dev/null +++ b/cookbooks/runit/libraries/resource_runit_service.rb @@ -0,0 +1,249 @@ +# +# Cookbook Name:: runit +# Provider:: service +# +# Copyright 2011, Joshua Timberman +# Copyright 2011, Chef Software, Inc. +# +# 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. +# + +require 'chef/resource' +require 'chef/resource/service' + +class Chef + class Resource + # Missing top-level class documentation comment + class RunitService < Chef::Resource::Service + def initialize(name, run_context = nil) + super + runit_node = runit_attributes_from_node(run_context) + @resource_name = :runit_service + @provider = Chef::Provider::Service::Runit + @supports = { :restart => true, :reload => true, :status => true } + @action = :enable + @allowed_actions = [:nothing, :start, :stop, :enable, :disable, :restart, :reload, :status, :once, :hup, :cont, :term, :kill, :up, :down, :usr1, :usr2, :create] + + # sv_bin, sv_dir, service_dir and lsb_init_dir may have been set in the + # node attributes + @sv_bin = runit_node[:sv_bin] || '/usr/bin/sv' + @sv_dir = runit_node[:sv_dir] || '/etc/sv' + @service_dir = runit_node[:service_dir] || '/etc/service' + @lsb_init_dir = runit_node[:lsb_init_dir] || '/etc/init.d' + + @control = [] + @options = {} + @env = {} + @log = true + @cookbook = nil + @check = false + @finish = false + @owner = nil + @group = nil + @enabled = false + @running = false + @default_logger = false + @restart_on_update = true + @run_template_name = @service_name + @log_template_name = @service_name + @check_script_template_name = @service_name + @finish_script_template_name = @service_name + @control_template_names = {} + @status_command = "#{@sv_bin} status #{@service_dir}" + @sv_templates = true + @sv_timeout = nil + @sv_verbose = false + @log_size = nil + @log_num = nil + @log_min = nil + @log_timeout = nil + @log_processor = nil + @log_socket = nil + @log_prefix = nil + @log_config_append = nil + + # + # Backward Compat Hack + # + # This ensures a 'service' resource exists for all 'runit_service' resources. + # This should allow all recipes using the previous 'runit_service' definition to + # continue operating. + # + unless run_context.nil? + service_dir_name = ::File.join(@service_dir, @name) + @service_mirror = Chef::Resource::Service.new(name, run_context) + @service_mirror.provider(Chef::Provider::Service::Simple) + @service_mirror.supports(@supports) + @service_mirror.start_command("#{@sv_bin} start #{service_dir_name}") + @service_mirror.stop_command("#{@sv_bin} stop #{service_dir_name}") + @service_mirror.restart_command("#{@sv_bin} restart #{service_dir_name}") + @service_mirror.status_command("#{@sv_bin} status #{service_dir_name}") + @service_mirror.action(:nothing) + run_context.resource_collection.insert(@service_mirror) + end + end + + def sv_bin(arg = nil) + set_or_return(:sv_bin, arg, :kind_of => [String]) + end + + def sv_dir(arg = nil) + set_or_return(:sv_dir, arg, :kind_of => [String, FalseClass]) + end + + def sv_timeout(arg = nil) + set_or_return(:sv_timeout, arg, :kind_of => [Fixnum]) + end + + def sv_verbose(arg = nil) + set_or_return(:sv_verbose, arg, :kind_of => [TrueClass, FalseClass]) + end + + def service_dir(arg = nil) + set_or_return(:service_dir, arg, :kind_of => [String]) + end + + def lsb_init_dir(arg = nil) + set_or_return(:lsb_init_dir, arg, :kind_of => [String]) + end + + def control(arg = nil) + set_or_return(:control, arg, :kind_of => [Array]) + end + + def options(arg = nil) + @env.empty? ? opts = @options : opts = @options.merge!(:env_dir => ::File.join(@sv_dir, @service_name, 'env')) + set_or_return( + :options, + arg, + :kind_of => [Hash], + :default => opts + ) + end + + def env(arg = nil) + set_or_return(:env, arg, :kind_of => [Hash]) + end + + ## set log to current instance value if nothing is passed. + def log(arg = @log) + set_or_return(:log, arg, :kind_of => [TrueClass, FalseClass]) + end + + def cookbook(arg = nil) + set_or_return(:cookbook, arg, :kind_of => [String]) + end + + def finish(arg = nil) + set_or_return(:finish, arg, :kind_of => [TrueClass, FalseClass]) + end + + def check(arg = nil) + set_or_return(:check, arg, :kind_of => [TrueClass, FalseClass]) + end + + def owner(arg = nil) + set_or_return(:owner, arg, :regex => [Chef::Config[:user_valid_regex]]) + end + + def group(arg = nil) + set_or_return(:group, arg, :regex => [Chef::Config[:group_valid_regex]]) + end + + def default_logger(arg = nil) + set_or_return(:default_logger, arg, :kind_of => [TrueClass, FalseClass]) + end + + def restart_on_update(arg = nil) + set_or_return(:restart_on_update, arg, :kind_of => [TrueClass, FalseClass]) + end + + def run_template_name(arg = nil) + set_or_return(:run_template_name, arg, :kind_of => [String]) + end + alias_method :template_name, :run_template_name + + def log_template_name(arg = nil) + set_or_return(:log_template_name, arg, :kind_of => [String]) + end + + def check_script_template_name(arg = nil) + set_or_return(:check_script_template_name, arg, :kind_of => [String]) + end + + def finish_script_template_name(arg = nil) + set_or_return(:finish_script_template_name, arg, :kind_of => [String]) + end + + def control_template_names(arg = nil) + set_or_return( + :control_template_names, + arg, + :kind_of => [Hash], + :default => set_control_template_names + ) + end + + def set_control_template_names + @control.each do |signal| + @control_template_names[signal] ||= @service_name + end + @control_template_names + end + + def sv_templates(arg = nil) + set_or_return(:sv_templates, arg, :kind_of => [TrueClass, FalseClass]) + end + + def log_size(arg = nil) + set_or_return(:log_size, arg, :kind_of => [Integer]) + end + + def log_num(arg = nil) + set_or_return(:log_num, arg, :kind_of => [Integer]) + end + + def log_min(arg = nil) + set_or_return(:log_min, arg, :kind_of => [Integer]) + end + + def log_timeout(arg = nil) + set_or_return(:log_timeout, arg, :kind_of => [Integer]) + end + + def log_processor(arg = nil) + set_or_return(:log_processor, arg, :kind_of => [String]) + end + + def log_socket(arg = nil) + set_or_return(:log_socket, arg, :kind_of => [String, Hash]) + end + + def log_prefix(arg = nil) + set_or_return(:log_prefix, arg, :kind_of => [String]) + end + + def log_config_append(arg = nil) + set_or_return(:log_config_append, arg, :kind_of => [String]) + end + + def runit_attributes_from_node(run_context) + if run_context && run_context.node && run_context.node[:runit] + run_context.node[:runit] + else + {} + end + end + end + end +end diff --git a/cookbooks/runit/metadata.json b/cookbooks/runit/metadata.json new file mode 100644 index 0000000..df7d104 --- /dev/null +++ b/cookbooks/runit/metadata.json @@ -0,0 +1,40 @@ +{ + "name": "runit", + "version": "1.6.0", + "description": "Installs runit and provides runit_service definition", + "long_description": "runit Cookbook\n==============\nInstalls runit and provides the `runit_service` service resource for managing processes (services) under runit.\n\nThis cookbook does not use runit to replace system init, nor are ther plans to do so.\n\nFor more information about runit:\n\n- http://smarden.org/runit/\n\n\nRequirements\n------------\n### Platforms\n- Debian/Ubuntu\n- Gentoo\n- RHEL\n\n### Cookbooks\n- packagecloud (for RHEL)\n\nAttributes\n----------\nSee `attributes/default.rb` for defaults generated per platform.\n\n- `node['runit']['sv_bin']` - Full path to the `sv` binary.\n- `node['runit']['chpst_bin']` - Full path to the `chpst` binary.\n- `node['runit']['service_dir']` - Full path to the default \"services\" directory where enabled services are linked.\n- `node['runit']['sv_dir']` - Full path to the directory where service lives, which gets linked to `service_dir`.\n- `node['runit']['lsb_init_dir']` - Full path to the directory where the LSB-compliant init script interface will be created.\n- `node['runit']['start']` - Command to start the runsvdir service\n- `node['runit']['stop]` - Command to stop the runsvdir service\n- `node['runit']['reload']` - Command to reload the runsvdir service\n\n### Optional Attributes for RHEL systems\n\n- `node['runit']['prefer_local_yum']` - If `true`, assumes that a `runit` package is available on an already configured local yum repository. By default, the recipe installs the `runit` package from a Package Cloud repository (see below). This is set to the value of `node['runit']['use_package_from_yum']` for backwards compatibility, but otherwise defaults to `false`.\n\nRecipes\n-------\n### default\nThe default recipe installs runit and starts `runsvdir` to supervise the services in runit's service directory (e.g., `/etc/service`).\n\nOn RHEL-family systems, it will install the runit RPM using [Ian Meyer's Package Cloud repository](https://packagecloud.io/imeyer/runit) for runit. This replaces the previous functionality where the RPM was build using his [runit RPM SPEC](https://github.com/imeyer/runit-rpm). However, if the attribute `node['runit']['prefer_local_yum']` is set to `true`, the packagecloud repository creation will be skipped and it is assumed that a `runit` package is available on an otherwise configured (outside this cookbook) local repository.\n\nOn Debian family systems, the runit packages are maintained by the runit author, Gerrit Pape, and the recipe will use that for installation.\n\nOn Gentoo, the runit ebuild package is installed.\n\nResource/Provider\n-----------------\nThis cookbook has a resource, `runit_service`, for managing services under runit. This service subclasses the Chef `service` resource.\n\n**This resource replaces the runit_service definition. See the CHANGELOG.md file in this cookbook for breaking change information and any actions you may need to take to update cookbooks using runit_service.**\n\n### Actions\n- **enable** - enables the service, creating the required run scripts and symlinks. This is the default action.\n- **start** - starts the service with `sv start`\n- **stop** - stops the service with `sv stop`\n- **disable** - stops the service with `sv down` and removes the service symlink\n- **create** - create the service directory, but don't enable the service with symlink\n- **restart** - restarts the service with `sv restart`\n- **reload** - reloads the service with `sv force-reload`\n- **once** - starts the service with `sv once`.\n- **hup** - sends the `HUP` signal to the service with `sv hup`\n- **cont** - sends the `CONT` signal to the service\n- **term** - sends the `TERM` signal to the service\n- **kill** - sends the `KILL` signal to the service\n- **up** - starts the service with `sv up`\n- **down** - downs the service with `sv down`\n- **usr1** - sends the `USR1` signal to the service with `sv 1`\n- **usr2** - sends the `USR2` signal to the service with `sv 2`\n\nService management actions are taken with runit's \"`sv`\" program.\n\nRead the `sv(8)` [man page](http://smarden.org/runit/sv.8.html) for more information on the `sv` program.\n\n### Parameter Attributes\n\nThe first three parameters, `sv_dir`, `service_dir`, and `sv_bin` will attempt to use the corresponding node attributes, and fall back to hardcoded default values that match the settings used on Debian platform systems.\n\nMany of these parameters are only used in the `:enable` action.\n\n- **sv_dir** - The base \"service directory\" for the services managed by\n the resource. By default, this will attempt to use the\n `node['runit']['sv_dir']` attribute, and falls back to `/etc/sv`.\n- **service_dir** - The directory where services are symlinked to be\n supervised by `runsvdir`. By default, this will attempt to use the\n `node['runit']['service_dir']` attribute, and falls back to\n `/etc/service`.\n- **lsb_init_dir** - The directory where an LSB-compliant init script\n interface will be created. By default, this will attempt to use the\n `node['runit']['lsb_init_dir']` attribute, and falls back to\n `/etc/init.d`.\n- **sv_bin** - The path to the `sv` program binary. This will attempt\n to use the `node['runit']['sv_bin']` attribute, and falls back to\n `/usr/bin/sv`.\n- **service_name** - *Name attribute*. The name of the service. This\n will be used in the directory of the managed service in the\n `sv_dir` and `service_dir`.\n- **sv_timeout** - Override the default `sv` timeout of 7 seconds.\n- **sv_verbose** - Whether to enable `sv` verbose mode. Default is\n `false`.\n- **sv_templates** - If true, the `:enable` action will create the\n service directory with the appropriate templates. Default is\n `true`. Set this to `false` if the service has a package that\n provides its own service directory. See __Usage__ examples.\n- **options** - Options passed as variables to templates, for\n compatibility with legacy runit service definition. Default is an\n empty hash.\n- **env** - A hash of environment variables with their values as content\n used in the service's `env` directory. Default is an empty hash.\n- **log** - Whether to start the service's logger with svlogd, requires\n a template `sv-service_name-log-run.erb` to configure the log's run\n script. Default is true.\n- **default_logger** - Whether a default `log/run` script should be set\n up. If true, the default content of the run script will use\n `svlogd` to write logs to `/var/log/service_name`. Default is false.\n- **log_size** - The maximum size a log file can grow to before it is\n automatically rotated. See svlogd(8) for the default value.\n- **log_num** - The maximum number of log files that will be retained\n after rotation. See svlogd(8) for the default value.\n- **log_min** - The minimum number of log files that will be retained\n after rotation (if svlogd cannot create a new file and the minimum\n has not been reached, it will block). Default is no minimum.\n- **log_timeout** - The maximum age a log file can get to before it is\n automatically rotated, whether it has reached `log_size` or not.\n Default is no timeout.\n- **log_processor** - A string containing a path to a program that\n rotated log files will be fed through. See the **PROCESSOR** section\n of svlogd(8) for details. Default is no processor.\n- **log_socket** - An string containing an IP:port pair identifying a UDP\n socket that log lines will be copied to. Default is none.\n- **log_prefix** - A string that will be prepended to each line as it\n is logged. Default is no prefix.\n- **log_config_append** - A string containing optional additional lines to add\n to the log service configuration. See svlogd(8) for more details.\n- **cookbook** - A cookbook where templates are located instead of\n where the resource is used. Applies for all the templates in the\n `enable` action.\n- **check** - whether the service has a check script, requires a\n template `sv-service_name-check.erb`\n- **finish** - whether the service has a finish script, requires a\n template `sv-service_name-finish.erb`\n- **control** - An array of signals to customize control of the service,\n see [runsv man page](http://smarden.org/runit/runsv.8.html) on how\n to use this. This requires that each template be created with the\n name `sv-service_name-signal.erb`.\n- **owner** - user that should own the templates created to enable the\n service\n- **group** - group that should own the templates created to enable the\n service\n- **run_template_name** - alternate filename of the run run script to\n use replacing `service_name`.\n- **log_template_name** - alternate filename of the log run script to\n use replacing `service_name`.\n- **check_script_template_name** - alternate filename of the check\n script to use, replacing `service_name`.\n- **finish_script_template_name** - alternate filename of the finish\n script to use, replacing `service_name`.\n- **control_template_names** - a hash of control signals (see *control*\n above) and their alternate template name(s) replacing\n `service_name`.\n- **status_command** - The command used to check the status of the\n service to see if it is enabled/running (if it's running, it's\n enabled). This hardcodes the location of the sv program to\n `/usr/bin/sv` due to the aforementioned cookbook load order.\n- **restart_on_update** - Whether the service should be restarted when\n the run script is updated. Defaults to `true`. Set to `false` if\n the service shouldn't be restarted when the run script is updated.\n\nUnlike previous versions of the cookbook using the `runit_service` definition, the `runit_service` resource can be notified. See __Usage__ examples below.\n\n\nUsage\n-----\nTo get runit installed on supported platforms, use `recipe[runit]`. Once it is installed, use the `runit_service` resource to set up services to be managed by runit.\n\nIn order to use the `runit_service` resource in your cookbook(s), each service managed will also need to have `sv-service_name-run.erb` and `sv-service_name-log-run.erb` templates created. If the `log` parameter is false, the log run script isn't created. If the `log` parameter is true, and `default_logger` is also true, the log run\nscript will be created with the default content:\n\n```bash\n#!/bin/sh\nexec svlogd -tt /var/log/service_name\n```\n\n### Examples\nThese are example use cases of the `runit_service` resource described above. There are others in the `runit_test` cookbook that is included in the [git repository](https://github.com/chef-cookbooks/runit).\n\n**Default Example**\n\nThis example uses all the defaults in the `:enable` action to set up the service.\n\nWe'll set up `chef-client` to run as a service under runit, such as is done in the `chef-client` cookbook. This example will be more simple than in that cookbook. First, create the required run template, `chef-client/templates/default/sv-chef-client-run.erb`.\n\n```bash\n#!/bin/sh\nexec 2>&1\nexec /usr/bin/env chef-client -i 1800 -s 30\n```\n\nThen create the required log/run template, `chef-client/templates/default/sv-chef-client-log-run.erb`.\n\n```bash\n#!/bin/sh\nexec svlogd -tt ./main\n```\n\n__Note__ This will cause output of the running process to go to `/etc/sv/chef-client/log/main/current`. Some people may not like this, see the following example. This is preserved for compatibility reasons.\n\nFinally, set up the service in the recipe with:\n\n```ruby\nrunit_service \"chef-client\"\n```\n\n**Default Logger Example**\n\nTo use a default logger with svlogd which will log to `/var/log/chef-client/current`, instead, use the `default_logger` option.\n\n```ruby\nrunit_service \"chef-client\" do\n default_logger true\nend\n```\n\n**No Log Service**\n\nIf there isn't an appendant log service, set `log` to false, and the log/run script won't be created.\n\n```ruby\nrunit_service \"no-svlog\" do\n log false\nend\n```\n\n**Check Script**\n\nTo create a service that has a check script in its service directory, set the `check` parameter to `true`, and create a `sv-checker-check.erb` template.\n\n```ruby\nrunit_service \"checker\" do\n check true\nend\n```\n\nThis will create `/etc/sv/checker/check`.\n\n**Finish Script**\n\nTo create a service that has a finish script in its service directory, set the `finish` parameter to `true`, and create a `sv-finisher-finish.erb` template.\n\n```ruby\nrunit_service \"finisher\" do\n finish true\nend\n```\n\nThis will create `/etc/sv/finisher/finish`.\n\n**Alternate service directory**\n\nIf the service directory for the managed service isn't the `sv_dir` (`/etc/sv`), then specify it:\n\n```ruby\nrunit_service \"custom_service\" do\n sv_dir \"/etc/custom_service/runit\"\nend\n```\n\n**No Service Directory**\n\nIf the service to manage has a package that provides its service directory, such as `git-daemon` on Debian systems, set `sv_templates` to false.\n\n```ruby\npackage \"git-daemon-run\"\n\nrunit_service \"git-daemon\" do\n sv_templates false\nend\n```\n\nThis will create the service symlink in `/etc/service`, but it will not manage any templates in the service directory.\n\n**User Controlled Services**\n\nTo set up services controlled by a non-privileged user, we follow the recommended configuration in the [runit documentation](http://smarden.org/runit/faq.html#user) (Is it possible to allow a user other than root to control a service?).\n\nSuppose the user's name is floyd, and floyd wants to run floyds-app. Assuming that the floyd user and group are already managed with Chef, create a `runsvdir-floyd` runit_service.\n\n```ruby\nrunit_service \"runsvdir-floyd\"\n```\n\nCreate the `sv-runsvdir-floyd-log-run.erb` template, or add `log false`. Also create the `sv-runsvdir-floyd-run.erb` with the following content:\n\n```bash\n#!/bin/sh\nexec 2>&1\nexec chpst -ufloyd runsvdir /home/floyd/service\n```\n\nNext, create the `runit_service` resource for floyd's app:\n\n```ruby\nrunit_service \"floyds-app\" do\n sv_dir \"/home/floyd/sv\"\n service_dir \"/home/floyd/service\"\n owner \"floyd\"\n group \"floyd\"\nend\n```\n\nAnd now floyd can manage the service with sv:\n\n```text\n$ id\nuid=1000(floyd) gid=1001(floyd) groups=1001(floyd)\n$ sv stop /home/floyd/service/floyds-app/\nok: down: /home/floyd/service/floyds-app/: 0s, normally up\n$ sv start /home/floyd/service/floyds-app/\nok: run: /home/floyd/service/floyds-app/: (pid 5287) 0s\n$ sv status /home/floyd/service/floyds-app/\nrun: /home/floyd/service/floyds-app/: (pid 5287) 13s; run: log: (pid 4691) 726s\n```\n\n**Options**\n\nNext, let's set up memcached under runit with some additional options using the `options` parameter. First, the `memcached/templates/default/sv-memcached-run.erb` template:\n\n```bash\n#!/bin/sh\nexec 2>&1\nexec chpst -u <%= @options[:user] %> /usr/bin/memcached -v -m <%= @options[:memory] %> -p <%= @options[:port] %>\n```\n\nNote that the script uses `chpst` (which comes with runit) to set the user option, then starts memcached on the specified memory and port (see below).\n\nThe log/run template, `memcached/templates/default/sv-memcached-log-run.erb`:\n\n```bash\n#!/bin/sh\nexec svlogd -tt ./main\n```\n\nFinally, the `runit_service` in our recipe:\n\n```ruby\nrunit_service \"memcached\" do\n options({\n :memory => node[:memcached][:memory],\n :port => node[:memcached][:port],\n :user => node[:memcached][:user]}.merge(params)\n })\nend\n```\n\nThis is where the user, port and memory options used in the run template are used.\n\n**Notifying Runit Services**\n\nIn previous versions of this cookbook where the definition was used, it created a `service` resource that could be notified. With the `runit_service` resource, recipes need to use the full resource name.\n\nFor example:\n\n```ruby\nrunit_service \"my-service\"\n\ntemplate \"/etc/my-service.conf\" do\n notifies :restart, \"runit_service[my-service]\"\nend\n```\n\nBecause the resource implements actions for various commands that `sv` can send to the service, any of those actions could be used for notification. For example, `chef-client` supports triggering a Chef run with a USR1 signal.\n\n```ruby\ntemplate \"/tmp/chef-notifier\" do\n notifies :usr1, \"runit_service[chef-client]\"\nend\n```\n\nFor older implementations of services that used `runit_service` as a definition, but may support alternate service styles, use a conditional, such as based on an attribute:\n\n```ruby\nservice_to_notify = case node['nginx']['init_style']\n when \"runit\"\n \"runit_service[nginx]\"\n else\n \"service[nginx]\"\n end\n\ntemplate \"/etc/nginx/nginx.conf\" do\n notifies :restart, service_to_notify\nend\n```\n\n**More Examples**\n\nFor more examples, see the `runit_test` cookbook's `service` recipe in the [git repository](https://github.com/chef-cookbooks/runit).\n\n\nLicense & Authors\n-----------------\n- Author:: Adam Jacob \n- Author:: Joshua Timberman \n\n```text\nCopyright:: 2008-2013, Chef Software, Inc\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "maintainer": "Heavy Water Operations, LLC.", + "maintainer_email": "support@hw-ops.com", + "license": "Apache 2.0", + "platforms": { + "ubuntu": ">= 0.0.0", + "debian": ">= 0.0.0", + "gentoo": ">= 0.0.0", + "centos": ">= 0.0.0", + "redhat": ">= 0.0.0", + "amazon": ">= 0.0.0", + "scientific": ">= 0.0.0", + "oracle": ">= 0.0.0", + "enterpriseenterprise": ">= 0.0.0" + }, + "dependencies": { + "packagecloud": ">= 0.0.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + "runit": "Installs and configures runit" + } +} \ No newline at end of file diff --git a/cookbooks/runit/recipes/default.rb b/cookbooks/runit/recipes/default.rb new file mode 100644 index 0000000..c3439f8 --- /dev/null +++ b/cookbooks/runit/recipes/default.rb @@ -0,0 +1,85 @@ +# +# Cookbook Name:: runit +# Recipe:: default +# +# Copyright 2008-2010, Chef Software, Inc. +# +# 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. +# + +service 'runit' do + action :nothing +end + +execute 'start-runsvdir' do + command value_for_platform( + 'debian' => { 'default' => 'runsvdir-start' }, + 'ubuntu' => { 'default' => 'start runsvdir' }, + 'gentoo' => { 'default' => '/etc/init.d/runit-start start' } + ) + action :nothing +end + +execute 'runit-hup-init' do + command 'telinit q' + only_if 'grep ^SV /etc/inittab' + action :nothing +end + +case node['platform_family'] +when 'rhel', 'fedora' + + packagecloud_repo 'imeyer/runit' unless node['runit']['prefer_local_yum'] + package 'runit' + +when 'debian', 'gentoo' + + if platform?('gentoo') + template '/etc/init.d/runit-start' do + source 'runit-start.sh.erb' + mode 0755 + end + + service 'runit-start' do + action :nothing + end + end + + package 'runit' do + action :install + response_file 'runit.seed' if platform?('ubuntu', 'debian') + notifies value_for_platform( + 'debian' => { '4.0' => :run, 'default' => :nothing }, + 'ubuntu' => { + 'default' => :nothing, + '9.04' => :run, + '8.10' => :run, + '8.04' => :run }, + 'gentoo' => { 'default' => :run } + ), 'execute[start-runsvdir]', :immediately + notifies value_for_platform( + 'debian' => { 'squeeze/sid' => :run, 'default' => :nothing }, + 'default' => :nothing + ), 'execute[runit-hup-init]', :immediately + notifies :enable, 'service[runit-start]' if platform?('gentoo') + end + + if node['platform'] =~ /ubuntu/i && node['platform_version'].to_f <= 8.04 + cookbook_file '/etc/event.d/runsvdir' do + source 'runsvdir' + mode 0644 + notifies :run, 'execute[start-runsvdir]', :immediately + only_if { ::File.directory?('/etc/event.d') } + end + end +end diff --git a/cookbooks/runit/templates/debian/init.d.erb b/cookbooks/runit/templates/debian/init.d.erb new file mode 100644 index 0000000..48b5367 --- /dev/null +++ b/cookbooks/runit/templates/debian/init.d.erb @@ -0,0 +1,66 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: <%= @name %> +# Required-Start: +# Required-Stop: +# Default-Start: +# Default-Stop: +# Short-Description: initscript for runit-managed <%= @name %> service +### END INIT INFO + +# Author: Chef Software, Inc. + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="runit-managed <%= @name %>" +NAME=<%= @name %> +RUNIT=/usr/bin/sv +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if runit is not installed +[ -x $RUNIT ] || exit 0 + +# 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 + + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME" + $RUNIT start $NAME + [ "$VERBOSE" != no ] && log_end_msg $? + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + $RUNIT stop $NAME + [ "$VERBOSE" != no ] && log_end_msg $? + ;; + status) + $RUNIT status $NAME && exit 0 || exit $? + ;; + reload) + [ "$VERBOSE" != no ] && log_daemon_msg "Reloading $DESC" "$NAME" + $RUNIT reload $NAME + [ "$VERBOSE" != no ] && log_end_msg $? + ;; + force-reload) + [ "$VERBOSE" != no ] && log_daemon_msg "Force reloading $DESC" "$NAME" + $RUNIT force-reload $NAME + [ "$VERBOSE" != no ] && log_end_msg $? + ;; + restart) + [ "$VERBOSE" != no ] && log_daemon_msg "Restarting $DESC" "$NAME" + $RUNIT restart $NAME + [ "$VERBOSE" != no ] && log_end_msg $? + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|reload|force-reload|restart}" >&2 + exit 3 + ;; +esac + +: + diff --git a/cookbooks/runit/templates/default/log-config.erb b/cookbooks/runit/templates/default/log-config.erb new file mode 100644 index 0000000..6e33db1 --- /dev/null +++ b/cookbooks/runit/templates/default/log-config.erb @@ -0,0 +1,24 @@ +<% if @size -%> +s<%= @size %> +<% end -%> +<% if @num -%> +n<%= @num %> +<% end -%> +<% if @min -%> +N<%= @min %> +<% end -%> +<% if @timeout -%> +t<%= @timeout %> +<% end -%> +<% if @processor -%> +!<%= @processor %> +<% end -%> +<% if @socket -%> +u<%= @socket %> +<% end -%> +<% if @prefix -%> +p<%= @prefix %> +<% end -%> +<% if @append -%> +<%= @append %> +<% end -%> diff --git a/cookbooks/runit/templates/gentoo/runit-start.sh.erb b/cookbooks/runit/templates/gentoo/runit-start.sh.erb new file mode 100644 index 0000000..a6c11b3 --- /dev/null +++ b/cookbooks/runit/templates/gentoo/runit-start.sh.erb @@ -0,0 +1,32 @@ +#!/sbin/runscript +# Copyright 1999-2006 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: $ + +depend() { + after net +} + +start() { + ebegin "Starting runsvdir" + start-stop-daemon --start --exec /usr/bin/runsvdir \ + --background --make-pidfile \ + --pidfile /var/run/runsvdir.pid -- <%= node.runit.sv_dir %> + eend $? +} + +stop() { + local ret1 ret2 + ebegin "Stopping runsvdir" + start-stop-daemon --stop --oknodo --pidfile /var/run/runsvdir.pid + ret1=$? + eend ${ret1} + + ebegin "Stopping services and logging" + sv shutdown -w 10 <%= node.runit.sv_dir %>/* + ret2=$? + eend ${ret2} + + return $((ret1+ret2)) +} + diff --git a/cookbooks/smf/README.md b/cookbooks/smf/README.md new file mode 100644 index 0000000..a1dabb0 --- /dev/null +++ b/cookbooks/smf/README.md @@ -0,0 +1,370 @@ +SMF +=== + +## Description + +Service Management Facility (SMF) is a tool in many Illumos and Solaris-derived operating systems +that treats services as first class objects of the system. It provides an XML syntax for +declaring how the system can interact with and control a service. + +The SMF cookbook contains providers for creating or modifying a service within the SMF framework. + + +## Requirements + +Any operating system that uses SMF, ie Solaris, SmartOS, OpenIndiana etc. + +The `smf` provider depends on the `builder` gem, which can be installed +via the `smf::default` recipe. + +Requires the RBAC cookbook, which can be found at . + +Processes can be run inside a project wrapper. In this case, look to the Resource Control cookbook, +which can be found at . Note that the SMF LWRP +does not create or manage the project. + + +## Basic Usage + +Note that we run the `smf::default` recipe before using LWRPs from this +cookbook. + +```ruby +include_recipe 'smf' + +smf 'my-service' do + user 'non-root-user' + start_command 'my-service start' + start_timeout 10 + stop_command 'pkill my-service' + stop_timeout 5 + restart_command 'my-service restart' + restart_timeout 60 + environment 'PATH' => '/home/non-root-user/bin', + 'RAILS_ENV' => 'staging' + locale 'C' + manifest_type 'application' + service_path '/var/svc/manifest' + notifies :restart, 'service[my-service]' +end + +service 'my-service' do + action :enable +end + +service 'my-service' do + action :restart +end +``` + + +## Attributes + +Ownership: +* `user` - User to run service commands as +* `group` - Group to run service commands as + +RBAC +* `authorization` - What management and value authorizations should be + created for this service. Defaults to the service name. + +Dependency management: +* `include_default_dependencies` - Service should depend on file system + and network services. Defaults to `true`. See [Dependencies](#dependencies) + for more info. +* `dependency` - an optional array of hashes signifying service and path + dependencies for this service to run. See [Dependencies](#dependencies). + +Process management: +* `project` - Name of project to run commands in +* `start_command` +* `start_timeout` +* `stop_command` - defaults to `:kill`, which basically means it will destroy every PID generated from the start command +* `stop_timeout` +* `restart_command` - defaults to `stop_command`, then `start_command` +* `restart_timeout` +* `refresh_command` - by default SMF treats this as `true`. This will be called when the SMF definition changes or + when a `notify :reload, 'service[thing]'` is called. +* `refresh_timeout` +* `duration` - Can be either `contract`, `wait`, `transient` or + `child`, but defaults to `contract`. See the [Duration](#duration) section below. +* `environment` - Hash - Environment variables to set while running commands +* `ignore` - Array - Faults to ignore in subprocesses. For example, + if core dumps in children are handled by a master process and you + don't want SMF thinking the service is exploding, you can ignore + ["core", "signal"]. +* `privileges` - Array - An array of privileges to be allowed for started processes. + Defaults to ['basic', 'net_privaddr'] +* `property_groups` - Hash - This should be in the form `{"group name" => {"type" => "application", "key" => "value", ...}}` +* `working_directory` - PWD that SMF should cd to in order to run commands +* `locale` - Character encoding to use (default "C") + +Manifest/FMRI metadata: +* `service_path` - defaults to `/var/svc/manifest` +* `manifest_type` - defaults to `application` +* `stability` - String - defaults to "Evolving". Valid options are + "Standard", "Stable", "Evolving", "Unstable", "External" and + "Obsolete" + +Deprecated: +* `credentials_user` - deprecated in favor of `user` + + +## Provider Actions + +### :install (default) + +This will drop a manifest XML file into `#{service_path}/#{manifest_type}/#{name}.xml`. If there is already a service +with a name that is matched by `new_resource.name` then the FMRI of our manifest will be set to the FMRI of the +pre-existing service. In this case, our properties will be merged into the properties of the pre-existing service. + +In this way, updates to recipes that use the SMF provider will not delete existing service properties, but will add +or overwrite them. + +Because of this, the SMF provider can be used to update properties for +services that are installed via a package manager. + +### :delete + +Remove an SMF definition. This stops the service if it is running. + +### :add_rbac + +This uses the `rbac` cookbook to define permissions that can then be applied to a user. This can be useful when local +users should manage services that are added via packages. + +```ruby +smf "nginx" do + action :add_rbac +end + +rbac_auth "Allow my user to manage nginx" do + user "my_user" + auth "nginx" +end +``` + + +## Resource Notes + +### `user`, `working_directory` and `environment` + +SMF does a remarkably good job running services as delegated users, and removes a lot of pain if you configure a +service correctly. There are many examples online (blogs, etc) of users wrapping their services in shell scripts with +`start`, `stop`, `restart` arguments. In general it seems as if the intention of these scripts is to take care of the +problem of setting environment variables and shelling out as another user. + +The use of init scripts to wrap executables can be unnecessary with SMF, as it provides hooks for all of these use cases. +When using `user`, SMF will assume that the `working_directory` is the user's home directory. This can be +easily overwritten (to `/home/user/app/current` for a Rails application, for example). One thing to be careful of is +that shell profile files will not be loaded. For this reason, if environment variables (such as PATH) are different +on your system or require additional entries arbitrary key/values may be set using the `environment` attribute. + +All things considered, one should think carefully about the need for an init script when working with SMF. For +well-behaved applications with simple configuration, an init script is overkill. Applications with endless command-line +options or that need a real login shell (for instance ruby applications that use RVM) an init script may make life +easier. + +### Role Based Authorization + +By default the SMF definition creates authorizations based on the +service name. The service user is then granted these authorizations. If +the service is named `asplosions`, then `solaris.smf.manage.asplosions` +and `solaris.smf.value.asplosions` will be created. + +The authorization can be changed by manually setting `authorization` on +the smf block: + +```ruby +smf 'asplosions' do + user 'monkeyking' + start_command 'asplode' + authorization 'booms' +end +``` + +This can be helpful if there are many services configured on a single +host, as multiple services can be collapsed into the same +authorizations. For instance: https://illumos.org/issues/4968 + +### Dependencies + +SMF allows services to explicitly list their dependencies on other +services. Among other things, this ensures that services are enabled in +the proper order on boot, so that a service doesn't fail to start +because another service has not yet been started. + +By default, services created by the SMF LWRP depend on the following other services: +* svc:/milestone/sysconfig +* svc:/system/filesystem/local +* svc:/milestone/name-services +* svc:/milestone/network + +On Solaris11, `svc:/milestone/sysconfig` is replaced with +`svc:/milestone/config`. + +These are configured with the attribute `include_default_dependencies`, +which defaults to `true`. + +Other dependencies can be specified with the `dependencies` attribute, +which takes an array of hashes as follows: + +```ruby +smf 'redis' + +smf 'redis-6999' do + start_command "..." + dependencies [ + {name: 'redis', fmris: ['svc:/application/management/redis'], + grouping: 'require_all', restart_on: 'restart', type: 'service'} + ] +end +``` + +Valid options for grouping: +* require_all - All listed FMRIs must be online +* require_any - Any of the listed FMRIs must be online +* exclude_all - None of the listed FMRIs can be online +* optional_all - FMRIs are either online or unable to come online + +Valid options for restart_on: +* error - Hardware fault +* restart - Restarts service if the depedency is restarted +* refresh - Restarted if the dependency is restarted or refreshed for + any reason +* none - Don't do anything + +Valid options for type: +* service - expects dependency FMRIs to be other services ie: svc:/type/of/service:instance +* path - expects FMRIs to be paths, ie file://localhost/etc/redis/redis.conf + +Note: the provider currently does not do any validation of these values. Also, type:path has not been extensively +tested. Use this at your own risk, or improve the provider's compatibility with type:path and submit a pull request! + +### Duration + +There are several different ways that SMF can track your service. By default it uses `contract`. +Basically, this means that it will keep track of the PIDs of all daemonized processes generated from `start_command`. +If SMF sees that processes are cycling, it may try to restart the service. If things get too hectic, it +may think that your service is flailing and put it into maintenance mode. If this is normal for your service, +for instance if you have a master that occasionally reaps processes, you may want to specify additional +configuration options. + +If you have a job that you want managed by SMF, but which is not daemonized, another duration option is +`transient`. In this mode, SMF will not watch any processes, but will expect that the main process exits cleanly. +This can be used, for instance, for a script that must be run at boot time, or for a script that you want to delegate +to particular users with Role Based Access Control. In this case, the script can be registered with SMF to run as root, +but with the start_command delegated to your user. + +A third option is `wait`. This covers non-daemonized processes. + +A fourth option is `child`. + +### Ignore + +Sometimes you have a case where your service behaves poorly. The Ruby server Unicorn, for example, has a master +process that likes to kill its children. This causes core dumps that SMF will interpret to be a failing service. +Instead you can `ignore ["core", "signal"]` and SMF will stop caring about core dumps. + +### Privileges + +Some system calls require privileges generally only granted to superusers or particular roles. In Solaris, an +SMF definition can also set specific privileges for contracted processes. + +By default the SMF provider will grant 'basic' and 'net_privaddr' permissions, but this can be set as follows: + +```ruby +smf 'elasticsearch' do + start_command 'elasticsearch' + privileges ['basic', 'proc_lock_memory'] +end +``` + +See the (privileges man page)[https://www.illumos.org/man/5/privileges] for more information. + +### Property Groups + +Property Groups are where you can store extra information for SMF to use later. They should be used in the +following format: + +```ruby +smf "my-service" do + start_command "do-something" + property_groups({ + "config" => { + "type" => "application", + "my-property" => "property value" + } + }) +end +``` + +`type` will default to `application`, and is used in the manifest XML to declare how the property group will be +used. For this reason, `type` can not be used as a property name (ie variable). + +One way to use property groups is to pass variables on to commands, as follows: + +```ruby +rails_env = node["from-chef-environment"]["rails-env"] + +smf "unicorn" do + start_command "bundle exec unicorn_rails -c /home/app_user/app/current/config/%{config/rails_env} -E %{config/rails_env} -D" + start_timeout 300 + restart_command ":kill -SIGUSR2" + restart_timeout 300 + working_directory "/home/app_user/app/current" + property_groups({ + "config" => { + "rails_env" => rails_env + } + }) +end +``` + +This is especially handy if you have a case where your commands may come from role attributes, but can +only work if they have access to variables set in an environment or computed in a recipe. + +### Stability + +This is for reference more than anything, so that administrators of a service know what to expect of possible changes to +the service definition. + +See: + + +## Working Examples + +Please see the [examples](https://github.com/livinginthepast/smf/blob/master/EXAMPLES.md) page for +example usages. + + +## Cookbook upgrades, possible side effects + +Changes to this cookbook may change the way that its internal checksums are generated for a service. +If you `notify :restart` any service from within the `smf` block or include a `refresh_command`, please +be aware that upgrading this cookbook may trigger a refresh or a registered notification on the first +subsequent chef run. + +## Contributing + +* fork +* file an issue to track updates/communication +* add tests +* rebase master into your branch +* issue a pull request + +Please do not increment the cookbook version in a fork. Version updates +will be done on the master branch after any pull requests are merged. + +When upstream changes are added to the master branch while you are +working on a contribution, please rebase master into your branch and +force push. A pull request should be able to be merged through a +fast-forward, without a merge commit. + +## Testing + +```bash +bundle +vagrant plugin install vagrant-smartos-zones +bundle exec strainer test +``` diff --git a/cookbooks/smf/libraries/helper.rb b/cookbooks/smf/libraries/helper.rb new file mode 100644 index 0000000..ab2b0c6 --- /dev/null +++ b/cookbooks/smf/libraries/helper.rb @@ -0,0 +1,9 @@ +unless defined?(SMFManifest::Helper) + module SMFManifest + # Generic helper that other helpers can inherit from. + # Takes the current node object, as well as an optional + # resource. + class Helper < Struct.new(:node, :resource) + end + end +end diff --git a/cookbooks/smf/libraries/matchers.rb b/cookbooks/smf/libraries/matchers.rb new file mode 100644 index 0000000..e0242e3 --- /dev/null +++ b/cookbooks/smf/libraries/matchers.rb @@ -0,0 +1,9 @@ +if defined?(ChefSpec) + def install_smf(name) + ChefSpec::Matchers::ResourceMatcher.new(:smf, :install, name) + end + + def delete_smf(name) + ChefSpec::Matchers::ResourceMatcher.new(:smf, :delete, name) + end +end diff --git a/cookbooks/smf/libraries/rbac_helper.rb b/cookbooks/smf/libraries/rbac_helper.rb new file mode 100644 index 0000000..6461afe --- /dev/null +++ b/cookbooks/smf/libraries/rbac_helper.rb @@ -0,0 +1,31 @@ +module SMFManifest + # Helper methods for determining whether work needs to be done + # with respect to assigning RBAC values to a service. + class RBACHelper < SMFManifest::Helper + include Chef::Mixin::ShellOut + + def authorization_set? + current_authorization == authorization + end + + def value_authorization_set? + current_value_authorization == value_authorization + end + + def current_authorization + shell_out("svcprop -p general/action_authorization #{resource.name}").stdout.chomp + end + + def current_value_authorization + shell_out("svcprop -p general/value_authorization #{resource.name}").stdout.chomp + end + + def authorization + "solaris.smf.manage.#{resource.authorization_name}" + end + + def value_authorization + "solaris.smf.value.#{resource.authorization_name}" + end + end +end diff --git a/cookbooks/smf/libraries/xml_builder.rb b/cookbooks/smf/libraries/xml_builder.rb new file mode 100644 index 0000000..a9b7f7f --- /dev/null +++ b/cookbooks/smf/libraries/xml_builder.rb @@ -0,0 +1,209 @@ +## This is kind of a hack, to ensure that the cookbook can be +# loaded. On first load, nokogiri may not be present. It is +# installed at load time by recipes/default.rb, so that at run +# time nokogiri will be present. +# +require 'forwardable' + +# rubocop:disable Metrics/ClassLength +module SMFManifest + # XMLBuilder manages the translation of the SMF Chef resource attributes into + # XML that can be parsed by `svccfg import`. + # + # SMFManifest::XMLBuilder.new(resource, node).to_xml + # + class XMLBuilder + # allow delegation + extend Forwardable + + attr_reader :resource, :node + + # delegate methods to :resource + def_delegators :resource, :name, :authorization_name, :dependencies, :duration, :environment, :group, :ignore, + :include_default_dependencies, :locale, :manifest_type, :project, :property_groups, + :service_path, :stability, :working_directory + + public + + def initialize(smf_resource, node) + @resource = smf_resource + @node = node + end + + def to_xml + @xml_output ||= xml_output + end + + protected + + ## methods that need to be called from within the context + # of the Nokogiri builder block need to be protected, rather + # than private. + + def commands + @commands ||= { + 'start' => resource.start_command, + 'stop' => resource.stop_command, + 'restart' => resource.restart_command, + 'refresh' => resource.refresh_command + } + end + + def timeout + @timeouts ||= { + 'start' => resource.start_timeout, + 'stop' => resource.stop_timeout, + 'restart' => resource.restart_timeout, + 'refresh' => resource.refresh_timeout + } + end + + def default_dependencies + if node.platform == 'solaris2' && node.platform_version == '5.11' + [ + { 'name' => 'milestone', 'value' => '/milestone/config' }, + { 'name' => 'fs-local', 'value' => '/system/filesystem/local' }, + { 'name' => 'name-services', 'value' => '/milestone/name-services' }, + { 'name' => 'network', 'value' => '/milestone/network' } + ] + else + [ + { 'name' => 'milestone', 'value' => '/milestone/sysconfig' }, + { 'name' => 'fs-local', 'value' => '/system/filesystem/local' }, + { 'name' => 'name-services', 'value' => '/milestone/name-services' }, + { 'name' => 'network', 'value' => '/milestone/network' } + ] + end + end + + private + + def xml_output + xml_builder = ::Builder::XmlMarkup.new(indent: 2) + xml_builder.instruct! + xml_builder.declare! :DOCTYPE, :service_bundle, :SYSTEM, '/usr/share/lib/xml/dtd/service_bundle.dtd.1' + xml_builder.service_bundle('name' => name, 'type' => 'manifest') do |xml| + xml.service('name' => service_fmri, 'type' => 'service', 'version' => '1') do |service| + service.create_default_instance('enabled' => 'false') + service.single_instance + + if include_default_dependencies + default_dependencies.each do |dependency| + service.dependency('name' => dependency['name'], + 'grouping' => 'require_all', + 'restart_on' => 'none', + 'type' => 'service') do |dep| + dep.service_fmri('value' => "svc:#{dependency['value']}") + end + end + end + + dependencies.each do |dependency| + service.dependency('name' => dependency['name'], + 'grouping' => dependency['grouping'], + 'restart_on' => dependency['restart_on'], + 'type' => dependency['type']) do |dep| + dependency['fmris'].each do |service_fmri| + dep.service_fmri('value' => service_fmri) + end + end + end + + service.method_context(exec_context) do |context| + context.method_credential(credentials) if user != 'root' + + if environment + context.method_environment do |env| + environment.each_pair do |var, value| + env.envvar('name' => var, 'value' => value) + end + end + end + end + + commands.each_pair do |type, command| + if command + service.exec_method('type' => 'method', 'name' => type, 'exec' => command, 'timeout_seconds' => timeout[type]) + end + end + + service.property_group('name' => 'general', 'type' => 'framework') do |group| + group.propval('name' => 'action_authorization', + 'type' => 'astring', + 'value' => "solaris.smf.manage.#{authorization_name}") + group.propval('name' => 'value_authorization', + 'type' => 'astring', + 'value' => "solaris.smf.value.#{authorization_name}") + end + + if sets_duration? || ignores_faults? + service.property_group('name' => 'startd', 'type' => 'framework') do |group| + group.propval('name' => 'duration', 'type' => 'astring', 'value' => duration) if sets_duration? + group.propval('name' => 'ignore_error', 'type' => 'astring', 'value' => ignore.join(',')) if ignores_faults? + end + end + + property_groups.each_pair do |name, properties| + service.property_group('name' => name, 'type' => properties.delete('type') { |_type| 'application' }) do |group| + properties.each_pair do |key, value| + group.propval('name' => key, 'value' => value, 'type' => check_type(value)) + end + end + end + + service.stability('value' => stability) + + service.template do |template| + template.common_name do |common_name| + common_name.loctext(name, 'xml:lang' => locale) + end + end + end + end + + xml_builder.target! + end + + def credentials + creds = { 'user' => user, 'privileges' => resource.privilege_list } + creds.merge!('group' => group) unless group.nil? + creds + end + + def user + resource.user || resource.credentials_user || 'root' + end + + def exec_context + context = {} + context['working_directory'] = working_directory unless working_directory.nil? + context['project'] = project unless project.nil? + context + end + + def check_type(value) + if value == value.to_i + 'integer' + else + 'astring' + end + end + + def ignores_faults? + !ignore.nil? + end + + def sets_duration? + duration != 'contract' + end + + # resource.fmri is set in the SMF :install action of the default provider. + # If there is already a service with a name that is matched by our resource.name + # then we grab the FMRI (fault management resource identifier) from the system. + # If a service is not found, we set this to our own FMRI. + def service_fmri + resource.fmri.nil? || resource.fmri.empty? ? "#{manifest_type}/management/#{name}" : resource.fmri.gsub(/^\//, '') + end + end +end +# rubocop:enable Metrics/ClassLength diff --git a/cookbooks/smf/metadata.json b/cookbooks/smf/metadata.json new file mode 100644 index 0000000..c18edb3 --- /dev/null +++ b/cookbooks/smf/metadata.json @@ -0,0 +1,32 @@ +{ + "name": "smf", + "description": "A light weight resource provider (LWRP) for SMF (Service Management Facility)", + "long_description": "SMF\n===\n\n## Description\n\nService Management Facility (SMF) is a tool in many Illumos and Solaris-derived operating systems\nthat treats services as first class objects of the system. It provides an XML syntax for \ndeclaring how the system can interact with and control a service.\n\nThe SMF cookbook contains providers for creating or modifying a service within the SMF framework.\n\n\n## Requirements\n\nAny operating system that uses SMF, ie Solaris, SmartOS, OpenIndiana etc.\n\nThe `smf` provider depends on the `builder` gem, which can be installed\nvia the `smf::default` recipe.\n\nRequires the RBAC cookbook, which can be found at .\n\nProcesses can be run inside a project wrapper. In this case, look to the Resource Control cookbook,\nwhich can be found at . Note that the SMF LWRP\ndoes not create or manage the project.\n\n\n## Basic Usage\n\nNote that we run the `smf::default` recipe before using LWRPs from this\ncookbook.\n\n```ruby\ninclude_recipe 'smf'\n\nsmf 'my-service' do\n user 'non-root-user'\n start_command 'my-service start'\n start_timeout 10\n stop_command 'pkill my-service'\n stop_timeout 5\n restart_command 'my-service restart'\n restart_timeout 60\n environment 'PATH' => '/home/non-root-user/bin',\n 'RAILS_ENV' => 'staging'\n locale 'C'\n manifest_type 'application'\n service_path '/var/svc/manifest'\n notifies :restart, 'service[my-service]'\nend\n\nservice 'my-service' do\n action :enable\nend\n\nservice 'my-service' do\n action :restart\nend\n```\n\n\n## Attributes\n\nOwnership:\n* `user` - User to run service commands as\n* `group` - Group to run service commands as\n\nRBAC\n* `authorization` - What management and value authorizations should be\n created for this service. Defaults to the service name.\n\nDependency management:\n* `include_default_dependencies` - Service should depend on file system\n and network services. Defaults to `true`. See [Dependencies](#dependencies)\n for more info.\n* `dependency` - an optional array of hashes signifying service and path\n dependencies for this service to run. See [Dependencies](#dependencies).\n\nProcess management:\n* `project` - Name of project to run commands in\n* `start_command`\n* `start_timeout`\n* `stop_command` - defaults to `:kill`, which basically means it will destroy every PID generated from the start command\n* `stop_timeout`\n* `restart_command` - defaults to `stop_command`, then `start_command`\n* `restart_timeout`\n* `refresh_command` - by default SMF treats this as `true`. This will be called when the SMF definition changes or\n when a `notify :reload, 'service[thing]'` is called.\n* `refresh_timeout`\n* `duration` - Can be either `contract`, `wait`, `transient` or\n `child`, but defaults to `contract`. See the [Duration](#duration) section below.\n* `environment` - Hash - Environment variables to set while running commands\n* `ignore` - Array - Faults to ignore in subprocesses. For example, \n if core dumps in children are handled by a master process and you \n don't want SMF thinking the service is exploding, you can ignore \n [\"core\", \"signal\"].\n* `privileges` - Array - An array of privileges to be allowed for started processes.\n Defaults to ['basic', 'net_privaddr']\n* `property_groups` - Hash - This should be in the form `{\"group name\" => {\"type\" => \"application\", \"key\" => \"value\", ...}}`\n* `working_directory` - PWD that SMF should cd to in order to run commands\n* `locale` - Character encoding to use (default \"C\")\n\nManifest/FMRI metadata:\n* `service_path` - defaults to `/var/svc/manifest`\n* `manifest_type` - defaults to `application`\n* `stability` - String - defaults to \"Evolving\". Valid options are\n \"Standard\", \"Stable\", \"Evolving\", \"Unstable\", \"External\" and\n \"Obsolete\"\n\nDeprecated:\n* `credentials_user` - deprecated in favor of `user`\n\n\n## Provider Actions\n\n### :install (default)\n\nThis will drop a manifest XML file into `#{service_path}/#{manifest_type}/#{name}.xml`. If there is already a service\nwith a name that is matched by `new_resource.name` then the FMRI of our manifest will be set to the FMRI of the \npre-existing service. In this case, our properties will be merged into the properties of the pre-existing service.\n\nIn this way, updates to recipes that use the SMF provider will not delete existing service properties, but will add \nor overwrite them.\n\nBecause of this, the SMF provider can be used to update properties for\nservices that are installed via a package manager.\n\n### :delete\n\nRemove an SMF definition. This stops the service if it is running.\n\n### :add_rbac\n\nThis uses the `rbac` cookbook to define permissions that can then be applied to a user. This can be useful when local\nusers should manage services that are added via packages.\n\n```ruby\nsmf \"nginx\" do\n action :add_rbac\nend\n\nrbac_auth \"Allow my user to manage nginx\" do\n user \"my_user\"\n auth \"nginx\"\nend\n```\n\n\n## Resource Notes\n\n### `user`, `working_directory` and `environment`\n\nSMF does a remarkably good job running services as delegated users, and removes a lot of pain if you configure a \nservice correctly. There are many examples online (blogs, etc) of users wrapping their services in shell scripts with \n`start`, `stop`, `restart` arguments. In general it seems as if the intention of these scripts is to take care of the\nproblem of setting environment variables and shelling out as another user.\n\nThe use of init scripts to wrap executables can be unnecessary with SMF, as it provides hooks for all of these use cases. \nWhen using `user`, SMF will assume that the `working_directory` is the user's home directory. This can be\neasily overwritten (to `/home/user/app/current` for a Rails application, for example). One thing to be careful of is \nthat shell profile files will not be loaded. For this reason, if environment variables (such as PATH) are different \non your system or require additional entries arbitrary key/values may be set using the `environment` attribute.\n\nAll things considered, one should think carefully about the need for an init script when working with SMF. For \nwell-behaved applications with simple configuration, an init script is overkill. Applications with endless command-line \noptions or that need a real login shell (for instance ruby applications that use RVM) an init script may make life\neasier.\n\n### Role Based Authorization\n\nBy default the SMF definition creates authorizations based on the\nservice name. The service user is then granted these authorizations. If\nthe service is named `asplosions`, then `solaris.smf.manage.asplosions`\nand `solaris.smf.value.asplosions` will be created.\n\nThe authorization can be changed by manually setting `authorization` on\nthe smf block:\n\n```ruby\nsmf 'asplosions' do\n user 'monkeyking'\n start_command 'asplode'\n authorization 'booms'\nend\n```\n\nThis can be helpful if there are many services configured on a single\nhost, as multiple services can be collapsed into the same\nauthorizations. For instance: https://illumos.org/issues/4968 \n\n### Dependencies\n\nSMF allows services to explicitly list their dependencies on other\nservices. Among other things, this ensures that services are enabled in\nthe proper order on boot, so that a service doesn't fail to start\nbecause another service has not yet been started.\n\nBy default, services created by the SMF LWRP depend on the following other services:\n* svc:/milestone/sysconfig\n* svc:/system/filesystem/local\n* svc:/milestone/name-services\n* svc:/milestone/network\n\nOn Solaris11, `svc:/milestone/sysconfig` is replaced with\n`svc:/milestone/config`.\n\nThese are configured with the attribute `include_default_dependencies`,\nwhich defaults to `true`.\n\nOther dependencies can be specified with the `dependencies` attribute,\nwhich takes an array of hashes as follows:\n\n```ruby\nsmf 'redis'\n\nsmf 'redis-6999' do\n start_command \"...\"\n dependencies [\n {name: 'redis', fmris: ['svc:/application/management/redis'],\n grouping: 'require_all', restart_on: 'restart', type: 'service'}\n ]\nend\n```\n\nValid options for grouping:\n* require_all - All listed FMRIs must be online\n* require_any - Any of the listed FMRIs must be online\n* exclude_all - None of the listed FMRIs can be online\n* optional_all - FMRIs are either online or unable to come online\n\nValid options for restart_on:\n* error - Hardware fault\n* restart - Restarts service if the depedency is restarted\n* refresh - Restarted if the dependency is restarted or refreshed for\n any reason\n* none - Don't do anything\n\nValid options for type:\n* service - expects dependency FMRIs to be other services ie: svc:/type/of/service:instance\n* path - expects FMRIs to be paths, ie file://localhost/etc/redis/redis.conf\n\nNote: the provider currently does not do any validation of these values. Also, type:path has not been extensively\ntested. Use this at your own risk, or improve the provider's compatibility with type:path and submit a pull request!\n\n### Duration\n\nThere are several different ways that SMF can track your service. By default it uses `contract`. \nBasically, this means that it will keep track of the PIDs of all daemonized processes generated from `start_command`.\nIf SMF sees that processes are cycling, it may try to restart the service. If things get too hectic, it\nmay think that your service is flailing and put it into maintenance mode. If this is normal for your service,\nfor instance if you have a master that occasionally reaps processes, you may want to specify additional\nconfiguration options.\n\nIf you have a job that you want managed by SMF, but which is not daemonized, another duration option is\n`transient`. In this mode, SMF will not watch any processes, but will expect that the main process exits cleanly.\nThis can be used, for instance, for a script that must be run at boot time, or for a script that you want to delegate\nto particular users with Role Based Access Control. In this case, the script can be registered with SMF to run as root,\nbut with the start_command delegated to your user.\n\nA third option is `wait`. This covers non-daemonized processes.\n\nA fourth option is `child`.\n\n### Ignore\n\nSometimes you have a case where your service behaves poorly. The Ruby server Unicorn, for example, has a master \nprocess that likes to kill its children. This causes core dumps that SMF will interpret to be a failing service.\nInstead you can `ignore [\"core\", \"signal\"]` and SMF will stop caring about core dumps.\n\n### Privileges\n\nSome system calls require privileges generally only granted to superusers or particular roles. In Solaris, an\nSMF definition can also set specific privileges for contracted processes.\n\nBy default the SMF provider will grant 'basic' and 'net_privaddr' permissions, but this can be set as follows:\n\n```ruby\nsmf 'elasticsearch' do\n start_command 'elasticsearch'\n privileges ['basic', 'proc_lock_memory']\nend\n```\n\nSee the (privileges man page)[https://www.illumos.org/man/5/privileges] for more information.\n\n### Property Groups\n\nProperty Groups are where you can store extra information for SMF to use later. They should be used in the\nfollowing format:\n\n```ruby\nsmf \"my-service\" do\n start_command \"do-something\"\n property_groups({\n \"config\" => {\n \"type\" => \"application\",\n \"my-property\" => \"property value\"\n }\n })\nend\n```\n\n`type` will default to `application`, and is used in the manifest XML to declare how the property group will be\nused. For this reason, `type` can not be used as a property name (ie variable).\n\nOne way to use property groups is to pass variables on to commands, as follows:\n\n```ruby\nrails_env = node[\"from-chef-environment\"][\"rails-env\"]\n\nsmf \"unicorn\" do\n start_command \"bundle exec unicorn_rails -c /home/app_user/app/current/config/%{config/rails_env} -E %{config/rails_env} -D\"\n start_timeout 300\n restart_command \":kill -SIGUSR2\"\n restart_timeout 300\n working_directory \"/home/app_user/app/current\"\n property_groups({\n \"config\" => {\n \"rails_env\" => rails_env\n }\n })\nend\n```\n\nThis is especially handy if you have a case where your commands may come from role attributes, but can\nonly work if they have access to variables set in an environment or computed in a recipe.\n\n### Stability\n\nThis is for reference more than anything, so that administrators of a service know what to expect of possible changes to \nthe service definition.\n\nSee: \n\n\n## Working Examples\n\nPlease see the [examples](https://github.com/livinginthepast/smf/blob/master/EXAMPLES.md) page for\nexample usages.\n\n\n## Cookbook upgrades, possible side effects\n\nChanges to this cookbook may change the way that its internal checksums are generated for a service.\nIf you `notify :restart` any service from within the `smf` block or include a `refresh_command`, please\nbe aware that upgrading this cookbook may trigger a refresh or a registered notification on the first\nsubsequent chef run.\n\n## Contributing\n\n* fork\n* file an issue to track updates/communication\n* add tests\n* rebase master into your branch\n* issue a pull request\n\nPlease do not increment the cookbook version in a fork. Version updates\nwill be done on the master branch after any pull requests are merged.\n\nWhen upstream changes are added to the master branch while you are\nworking on a contribution, please rebase master into your branch and\nforce push. A pull request should be able to be merged through a\nfast-forward, without a merge commit.\n\n## Testing\n\n```bash\nbundle\nvagrant plugin install vagrant-smartos-zones\nbundle exec strainer test\n```\n", + "maintainer": "Eric Saxby", + "maintainer_email": "sax@livinginthepast.org", + "license": "MIT", + "platforms": { + "smartos": ">= 0.0.0" + }, + "dependencies": { + "rbac": ">= 1.0.1" + }, + "recommendations": { + }, + "suggestions": { + "resource-control": ">= 0.0.0" + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + }, + "version": "2.2.7" +} \ No newline at end of file diff --git a/cookbooks/smf/metadata.rb b/cookbooks/smf/metadata.rb new file mode 100644 index 0000000..9019fa5 --- /dev/null +++ b/cookbooks/smf/metadata.rb @@ -0,0 +1,13 @@ +name 'smf' +maintainer 'Eric Saxby' +maintainer_email 'sax@livinginthepast.org' +license 'MIT' +description 'A light weight resource provider (LWRP) for SMF (Service Management Facility)' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '2.2.7' + +supports 'smartos' + +depends 'rbac', '>= 1.0.1' + +suggests 'resource-control' # For managing Solaris projects, when setting project on a manifest diff --git a/cookbooks/smf/providers/default.rb b/cookbooks/smf/providers/default.rb new file mode 100644 index 0000000..76670e1 --- /dev/null +++ b/cookbooks/smf/providers/default.rb @@ -0,0 +1,143 @@ + +require 'chef/mixin/shell_out' +require 'fileutils' +include Chef::Mixin::ShellOut + +def load_current_resource + find_fmri unless new_resource.fmri + + @current_resource = Chef::Resource::Smf.new(new_resource.name) + @current_resource.fmri(new_resource.fmri) + @current_resource.load +end + +action :install do + create_directories + write_manifest + create_rbac_definitions + import_manifest + deduplicate_manifest + add_rbac_permissions + + new_resource.updated_by_last_action(smf_changed?) + new_resource.save_checksum if smf_changed? +end + +action :add_rbac do + create_rbac_definitions + service new_resource.name + + manage = execute "add SMF authorization to allow RBAC for #{new_resource.name}" do + command "svccfg -s #{new_resource.name} " \ + 'setprop general/action_authorization=astring:' \ + "'solaris.smf.manage.#{new_resource.authorization_name}'" + not_if { SMFManifest::RBACHelper.new(node, new_resource).authorization_set? } + notifies :reload, "service[#{new_resource.name}]" + end + + value = execute "add SMF value to allow RBAC for #{new_resource.name}" do + command "svccfg -s #{new_resource.name} " \ + 'setprop general/value_authorization=astring: ' \ + 'solaris.smf.value.#{new_resource.authorization_name}' + not_if { SMFManifest::RBACHelper.new(node, new_resource).value_authorization_set? } + notifies :reload, "service[#{new_resource.name}]" + end + + new_resource.updated_by_last_action(manage.updated_by_last_action? || value.updated_by_last_action?) +end + +action :delete do + new_resource.updated_by_last_action(false) + + if @current_resource.smf_exists? + service new_resource.name do + action [:stop, :disable] + end + + execute "remove service #{new_resource.name} from SMF" do + command "svccfg delete #{new_resource.name}" + end + + delete_manifest + new_resource.remove_checksum + + new_resource.updated_by_last_action(true) + end +end + +private + +def smf_changed? + @current_resource.checksum != new_resource.checksum || !@current_resource.smf_exists? +end + +def find_fmri + fmri_check = shell_out(%(svcs -H -o FMRI #{new_resource.name})) + if fmri_check.exitstatus == 0 + new_resource.fmri fmri_check.stdout.chomp.split(':')[1] + else + new_resource.fmri "/#{new_resource.manifest_type}/management/#{new_resource.name}" + end +end + +def create_directories + Chef::Log.debug "Creating manifest directory at #{new_resource.xml_path}" + FileUtils.mkdir_p new_resource.xml_path +end + +def write_manifest + return unless smf_changed? + + Chef::Log.debug "Writing SMF manifest for #{new_resource.name}" + ::File.open(new_resource.xml_file, 'w') do |file| + file.puts SMFManifest::XMLBuilder.new(new_resource, node).to_xml + end +end + +def delete_manifest + return unless ::File.exist?(new_resource.xml_file) + + Chef::Log.debug "Removing SMF manifest for #{new_resource.name}" + ::File.delete(new_resource.xml_file) +end + +def create_rbac_definitions + rbac new_resource.authorization_name do + action :create + end +end + +def add_rbac_permissions + user = new_resource.user || new_resource.credentials_user || 'root' + + rbac_auth "Add RBAC for #{new_resource.name} to #{user}" do + user user + auth new_resource.authorization_name + not_if { user == 'root' } + end +end + +def import_manifest + return unless smf_changed? + + Chef::Log.debug("importing SMF manifest #{new_resource.xml_file}") + shell_out!("svccfg import #{new_resource.xml_file}") +end + +def deduplicate_manifest + # If we are overwriting properties from an old SMF definition (from pkgsrc, etc) + # there may be redundant XML files that we want to dereference + name = new_resource.name + + duplicate_manifest = shell_out("svcprop #{name} | grep -c manifestfiles").stdout.strip.to_i > 1 + return unless duplicate_manifest + + Chef::Log.debug "Removing duplicate SMF manifest reference from #{name}" + shell_out! "svccfg -s #{name} delprop " \ + "`svcprop #{name} | grep manifestfiles | grep -v #{new_resource.xml_file} | awk '{ print $1 }'` " \ + "&& svcadm refresh #{name}" +end + +def smf_defined?(fmri) + shell_out("svcs #{fmri}").exitstatus == 0 +end diff --git a/cookbooks/smf/recipes/SMFServicesOK.rb b/cookbooks/smf/recipes/SMFServicesOK.rb new file mode 100644 index 0000000..d3f5c1f --- /dev/null +++ b/cookbooks/smf/recipes/SMFServicesOK.rb @@ -0,0 +1,25 @@ +directory '/opt/scripts' do + action :create + mode '0755' + owner 'root' + group 'root' +end + +directory '/opt/local/etc/snmp/conf.d' do + action :create + mode '0755' + owner 'root' + group 'root' +end + +template '/opt/scripts/SMFServicesOK.sh' do + path '/opt/scripts/SMFServicesOK.sh' + source 'SMFServicesOK.sh.erb' + mode '0755' +end + +template 'SMFServicesOK.snmpd.conf' do + path '/opt/local/etc/snmp/conf.d/SMFServicesOK.snmpd.conf' + source 'SMFServicesOK.snmpd.conf.erb' + mode '0644' +end diff --git a/cookbooks/smf/recipes/default.rb b/cookbooks/smf/recipes/default.rb new file mode 100644 index 0000000..72d8f16 --- /dev/null +++ b/cookbooks/smf/recipes/default.rb @@ -0,0 +1,7 @@ +## These libraries need to be installed when the cookbook +# is loaded, otherwise they are not available when the +# cookbook runs. + +chef_gem 'builder' + +require 'builder' diff --git a/cookbooks/smf/resources/default.rb b/cookbooks/smf/resources/default.rb new file mode 100644 index 0000000..e5dc0f0 --- /dev/null +++ b/cookbooks/smf/resources/default.rb @@ -0,0 +1,124 @@ + +require 'chef/mixin/shell_out' +include Chef::Mixin::ShellOut + +actions :install, :add_rbac, :delete +default_action :install + +attribute :name, kind_of: String, name_attribute: true, required: true +attribute :user, kind_of: [String, NilClass], default: nil +attribute :group, kind_of: [String, NilClass], default: nil +attribute :project, kind_of: [String, NilClass], default: nil + +attribute :authorization, kind_of: [String, NilClass], default: nil + +attribute :start_command, kind_of: [String, NilClass], default: nil +attribute :start_timeout, kind_of: Integer, default: 5 +attribute :stop_command, kind_of: String, default: ':kill' +attribute :stop_timeout, kind_of: Integer, default: 5 +attribute :restart_command, kind_of: [String, NilClass], default: nil +attribute :restart_timeout, kind_of: Integer, default: 5 +attribute :refresh_command, kind_of: [String, NilClass], default: nil +attribute :refresh_timeout, kind_of: Integer, default: 5 + +attribute :include_default_dependencies, kind_of: [TrueClass, FalseClass], default: true +attribute :dependencies, kind_of: [Array], default: [] + +attribute :privileges, kind_of: [Array], default: %w(basic net_privaddr) +attribute :working_directory, kind_of: [String, NilClass], default: nil +attribute :environment, kind_of: [Hash, NilClass], default: nil +attribute :locale, kind_of: String, default: 'C' + +attribute :manifest_type, kind_of: String, default: 'application' +attribute :service_path, kind_of: String, default: '/var/svc/manifest' + +attribute :duration, kind_of: String, default: 'contract', regex: '(contract|wait|transient|child)' +attribute :ignore, kind_of: [Array, NilClass], default: nil +attribute :fmri, kind_of: String, default: nil + +attribute :stability, kind_of: String, equal_to: %(Standard Stable Evolving Unstable External Obsolete), + default: 'Evolving' + +attribute :property_groups, kind_of: Hash, default: {} + +# Deprecated +attribute :credentials_user, kind_of: [String, NilClass], default: nil + +## internal methods + +def xml_path + "#{service_path}/#{manifest_type}" +end + +def xml_file + "#{xml_path}/#{name}.xml" +end + +require 'fileutils' +require 'digest/md5' + +# Save a checksum out to a file, for future chef runs +# +def save_checksum + Chef::Log.debug("Saving checksum for SMF #{name}: #{checksum}") + ::FileUtils.mkdir_p(Chef::Config.checksum_path) + f = ::File.new(checksum_file, 'w') + f.write checksum +end + +def remove_checksum + return unless ::File.exist?(checksum_file) + + Chef::Log.debug("Removing checksum for SMF #{name}") + ::File.delete(checksum_file) +end + +# Load current resource from checksum file and projects database. +# This should only ever be called on @current_resource, never on new_resource. +# +def load + @checksum ||= ::File.exist?(checksum_file) ? ::File.read(checksum_file) : '' + @smf_exists = shell_out("svcs #{fmri}").exitstatus == 0 + Chef::Log.debug("Loaded checksum for SMF #{name}: #{@checksum}") + Chef::Log.debug("SMF service already exists for #{fmri}? #{@smf_exists.inspect}") +end + +def authorization_name + authorization || name +end + +def checksum + attributes = [ + user, credentials_user, group, + project, start_command, start_timeout, stop_command, + stop_timeout, restart_command, restart_timeout, + refresh_command, refresh_timeout, working_directory, + locale, authorization, manifest_type, service_path, + duration, ignore.to_s, include_default_dependencies, + dependencies, fmri, stability, environment_as_string, + privilege_list, property_groups_as_string, '0' + ] + @checksum ||= Digest::MD5.hexdigest(attributes.join(':')) +end + +def checksum_file + "#{Chef::Config.checksum_path}/smf--#{name}" +end + +def environment_as_string + return nil if environment.nil? + environment.inject('') { |memo, k, v| memo << [k, v].join('|') } +end + +def privilege_list + privileges.join(',') +end + +def property_groups_as_string + return nil if property_groups.empty? + property_groups.inject('') { |memo, k, v| memo << [k, v].join('|') } +end + +def smf_exists? + !!@smf_exists +end diff --git a/cookbooks/smf/templates/default/SMFServicesOK.sh.erb b/cookbooks/smf/templates/default/SMFServicesOK.sh.erb new file mode 100644 index 0000000..b135216 --- /dev/null +++ b/cookbooks/smf/templates/default/SMFServicesOK.sh.erb @@ -0,0 +1,13 @@ +#!/bin/bash +# if we're on SunOS 5.10+, we should check for beat services. + +if [ "`uname -s`" = "SunOS" ] && [ `uname -r|cut -d. -f1` -ge 5 ] && [ `uname -r|cut -d. -f2` -ge 10 ] +then + B=`svcs -Ha |egrep -v "disabled|online|legacy_run"` + if [ "foo$B" == "foo" ] + then + echo "OK" + else + echo $B + fi +fi diff --git a/cookbooks/smf/templates/default/SMFServicesOK.snmpd.conf.erb b/cookbooks/smf/templates/default/SMFServicesOK.snmpd.conf.erb new file mode 100644 index 0000000..8d3add8 --- /dev/null +++ b/cookbooks/smf/templates/default/SMFServicesOK.snmpd.conf.erb @@ -0,0 +1 @@ +extend SMFServicesOK /opt/scripts/SMFServicesOK.sh diff --git a/cookbooks/ssh_known_hosts/CHANGELOG.md b/cookbooks/ssh_known_hosts/CHANGELOG.md new file mode 100644 index 0000000..d824cdf --- /dev/null +++ b/cookbooks/ssh_known_hosts/CHANGELOG.md @@ -0,0 +1,73 @@ +v2.0.0 (2014-12-02) +------------------- +- [#36] Fix the way keys are rendered +- [#22] Update to README +- [#32] Clean up logging +- [#23] Do not hash public keys +- [#34] Serverspec updates +- [#28] Add data bag caching option +- [#20] Add checspec matchers +- [#33] Add test to verify chefspec matcher + +v1.3.2 (2014-04-23) +------------------- +- [COOK-4579] - Do not use ssh-keyscan stderr + + +v1.3.0 (2014-04-09) +------------------- +- [COOK-4489] Updated ssh-keyscan to include -t type + + +v1.2.0 (2014-02-18) +------------------- +### Bug +- **[COOK-3453](https://tickets.opscode.com/browse/COOK-3453)** - ssh_known_hosts cookbook ruby block executes on every chef run + + +v1.1.0 +------ +[COOK-3765] - support ssh-keyscan using an alternative port number + + +v1.0.2 +------ +### Bug +- **[COOK-3113](https://tickets.opscode.com/browse/COOK-3113)** - Use empty string when result is `nil` + +v1.0.0 +------ +This is a major release because it requires a server that supports the partial search feature. + +- Opscode Hosted Chef +- Opscode Private Chef +- Open Source Chef 11 + +### Improvement + +- [COOK-830]: uses an inordinate amount of RAM when running exception handlers + +v0.7.4 +------ +- [COOK-2440] - `ssh_known_hosts` fails to use data bag entries, doesn't grab items + +v0.7.2 +------ +- [COOK-2364] - Wrong LWRP name used in recipe + +v0.7.0 +------ +- [COOK-2320] - Merge `known_host` LWRP into `ssh_known_hosts` + +v0.6.0 +------ +- [COOK-2268] - Allow to run with chef-solo + +v0.5.0 +------ +- [COOK-1077] - allow adding arbitrary host keys from a data bag + +v0.4.0 +------ +- COOK-493: include fqdn +- COOK-721: corrected permissions diff --git a/cookbooks/ssh_known_hosts/README.md b/cookbooks/ssh_known_hosts/README.md new file mode 100644 index 0000000..d75bd81 --- /dev/null +++ b/cookbooks/ssh_known_hosts/README.md @@ -0,0 +1,202 @@ +ssh_known_hosts Cookbook +======================== +The Chef `ssh_known_hosts` cookbook exposes resource and default recipe for adding hosts and keys to the `/etc/ssh/ssh_known_hosts` file. + +- The default recipe builds `/etc/ssh/ssh_known_hosts` based either on search indexes using `rsa,dsa` key types and ohai data **or**, when `['ssh_known_hosts']['use_data_bag_cache']` is `true`, on the contents of a data bag that is maintained by the `cacher` recipe running on a worker node. +- The cacher recipe builds and maintains a data bag based on search indexes using `rsa,dsa` key types and ohai data. +- The LWRP provides a way to add custom entries in your own recipes. + +You can also optionally put other host keys in a data bag called "`ssh_known_hosts`". See below for details. + + +Requirements +------------ +Should work on any operating system that supports `/etc/ssh/ssh_known_hosts`. + +The Opscode `partial_search` cookbook is required for the default recipe, as well as a Chef Server that supports partial search: + +- Opscode Hosted Chef +- Opscode Private Chef +- Open Source Chef Server 11 + + +Usage +----- +### LWRP + +Use the LWRP `ssh_known_hosts_entry` to append an entry for the specified host in `/etc/ssh/ssh_known_hosts`. For example: + +```ruby +ssh_known_hosts_entry 'github.com' +``` + +This will append an entry in `/etc/ssh/ssh_known_hosts` like this: + +```text +# github.com SSH-2.0-OpenSSH_5.5p1 Debian-6+squeeze1+github8 +github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +``` + +You can optionally specify your own key, if you don't want to use `ssh-keyscan`: + +```ruby +ssh_known_hosts_entry 'github.com' do + key 'node.example.com ssh-rsa ...' +end +``` + +### Cacher + +Use the `cacher` recipe on a single "worker" node somewhere in your cluster to maintain a data bag (`server_data/known_hosts` by default) containing all of your nodes host keys. The advantage to this approach is that is much faster than running a search of all nodes, and substantially lightens the load on locally hosted Chef servers. The drawback is that the data is slightly delayed (because the cacher worker must converge first). + +To use the cacher, simply include the `ssh_known_hosts::cacher` cookbook in a wrapper cookbook or run list on a designated worker node. + +#### Attributes + +The following attributes are set on a per-platform basis, see the `attributes/default.rb`. + +* `node['ssh_known_hosts']['file']` - Sets up the location of the ssh_known_hosts file for the system. + Defaults to '/etc/ssh/ssh_known_hosts' +* `node['ssh_known_hosts']['key_type']` - Determines which key type ssh-keyscan will use to determine the + host key, different systems will have different available key types, check your manpage for available + key types for ssh-keyscan. Defaults to 'rsa,dsa' +* `node['ssh_known_hosts']['use_data_bag_cache']` - Use the data bag maintained by the cacher server to build `/etc/ssh/ssh_known_hosts` instead of a direct search (requires that a node be set up to run the cacher recipe regularly). +* `node['ssh_known_hosts']['cacher']['data_bag']`/`node['ssh_known_hosts']['cacher']['data_bag_item']` - Data bag where cacher recipe should store its keys. + +#### LWRP Attributes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeDescriptionExampleDefault
    hostthe host to addgithub.com
    key(optional) provide your own keyssh-rsa ...ssh-keyscan -H #{host}
    port(optional) the server port that ssh-keyscan will use to gather the public key222222
    + +- - - + +### Default Recipe + +Searches the Chef Server for all hosts that have SSH host keys using `rsa,dsa` key types and generates an `/etc/ssh/ssh_known_hosts`. + +#### Adding custom host keys + +There are two ways to add custom host keys. You can either use the provided LWRP (see above), or by creating a data bag called "`ssh_known_hosts`" and adding an item for each host: + +```javascript +{ + "id": "github", + "fqdn": "github.com", + "rsa": "github-rsa-host-key" +} +``` + +There are additional optional values you may use in the data bag: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeDescriptionExampleDefault
    ida unique id for this data bag entrygithub
    fqdnthe fqdn of the hostgithub.com
    rsathe rsa key for this serverssh-rsa AAAAB3...
    ipaddressthe ipaddress of the node (if fqdn is missing)1.1.1.1
    hostnamelocal hostname of the server (if not a fqdn)myserver.local
    dsathe dsa key for this serverssh-dsa ABAAC3...
    + +###ChefSpec matchers + +A custom matcher is available for you to use in recipe tests. + +``` +describe 'my_cookbook::my_recipe' do + let(:chef_run) { ChefSpec::Runner.new.converge(described_recipe) } + it { expect(chef_run).to append_to_ssh_known_hosts 'github.com' } +end +``` + + +License and Authors +------------------- +- Author: Scott M. Likens () +- Author: Seth Vargo () +- Author: Lamont Granquist () + +```text +Copyright:: 2011-2013, Opscode, Inc + +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/cookbooks/ssh_known_hosts/attributes/default.rb b/cookbooks/ssh_known_hosts/attributes/default.rb new file mode 100644 index 0000000..76d69a1 --- /dev/null +++ b/cookbooks/ssh_known_hosts/attributes/default.rb @@ -0,0 +1,23 @@ +# +# Author:: Seth Vargo () +# Attributes:: default +# +# Copyright 2013, Seth Vargo +# +# 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. +# + +default['ssh_known_hosts']['file'] = '/etc/ssh/ssh_known_hosts' +default['ssh_known_hosts']['key_type'] = 'rsa,dsa' +default['ssh_known_hosts']['cacher']['data_bag'] = 'server_data' +default['ssh_known_hosts']['cacher']['data_bag_item'] = 'known_hosts' diff --git a/cookbooks/ssh_known_hosts/libraries/matchers.rb b/cookbooks/ssh_known_hosts/libraries/matchers.rb new file mode 100644 index 0000000..3037711 --- /dev/null +++ b/cookbooks/ssh_known_hosts/libraries/matchers.rb @@ -0,0 +1,7 @@ +if defined?(ChefSpec) + + def append_to_ssh_known_hosts(resource) + ChefSpec::Matchers::ResourceMatcher.new(:ssh_known_hosts_entry, :create, resource) + end + +end diff --git a/cookbooks/ssh_known_hosts/metadata.json b/cookbooks/ssh_known_hosts/metadata.json new file mode 100644 index 0000000..9f59d40 --- /dev/null +++ b/cookbooks/ssh_known_hosts/metadata.json @@ -0,0 +1,31 @@ +{ + "name": "ssh_known_hosts", + "version": "2.0.0", + "description": "Dyanmically generates /etc/ssh/known_hosts based on search indexes", + "long_description": "ssh_known_hosts Cookbook\n========================\nThe Chef `ssh_known_hosts` cookbook exposes resource and default recipe for adding hosts and keys to the `/etc/ssh/ssh_known_hosts` file.\n\n- The default recipe builds `/etc/ssh/ssh_known_hosts` based either on search indexes using `rsa,dsa` key types and ohai data **or**, when `['ssh_known_hosts']['use_data_bag_cache']` is `true`, on the contents of a data bag that is maintained by the `cacher` recipe running on a worker node.\n- The cacher recipe builds and maintains a data bag based on search indexes using `rsa,dsa` key types and ohai data.\n- The LWRP provides a way to add custom entries in your own recipes.\n\nYou can also optionally put other host keys in a data bag called \"`ssh_known_hosts`\". See below for details.\n\n\nRequirements\n------------\nShould work on any operating system that supports `/etc/ssh/ssh_known_hosts`.\n\nThe Opscode `partial_search` cookbook is required for the default recipe, as well as a Chef Server that supports partial search:\n\n- Opscode Hosted Chef\n- Opscode Private Chef\n- Open Source Chef Server 11\n\n\nUsage\n-----\n### LWRP\n\nUse the LWRP `ssh_known_hosts_entry` to append an entry for the specified host in `/etc/ssh/ssh_known_hosts`. For example:\n\n```ruby\nssh_known_hosts_entry 'github.com'\n```\n\nThis will append an entry in `/etc/ssh/ssh_known_hosts` like this:\n\n```text\n# github.com SSH-2.0-OpenSSH_5.5p1 Debian-6+squeeze1+github8\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n```\n\nYou can optionally specify your own key, if you don't want to use `ssh-keyscan`:\n\n```ruby\nssh_known_hosts_entry 'github.com' do\n key 'node.example.com ssh-rsa ...'\nend\n```\n\n### Cacher\n\nUse the `cacher` recipe on a single \"worker\" node somewhere in your cluster to maintain a data bag (`server_data/known_hosts` by default) containing all of your nodes host keys. The advantage to this approach is that is much faster than running a search of all nodes, and substantially lightens the load on locally hosted Chef servers. The drawback is that the data is slightly delayed (because the cacher worker must converge first).\n\nTo use the cacher, simply include the `ssh_known_hosts::cacher` cookbook in a wrapper cookbook or run list on a designated worker node.\n\n#### Attributes\n\nThe following attributes are set on a per-platform basis, see the `attributes/default.rb`.\n\n* `node['ssh_known_hosts']['file']` - Sets up the location of the ssh_known_hosts file for the system. \n Defaults to '/etc/ssh/ssh_known_hosts'\n* `node['ssh_known_hosts']['key_type']` - Determines which key type ssh-keyscan will use to determine the \n host key, different systems will have different available key types, check your manpage for available \n key types for ssh-keyscan. Defaults to 'rsa,dsa'\n* `node['ssh_known_hosts']['use_data_bag_cache']` - Use the data bag maintained by the cacher server to build `/etc/ssh/ssh_known_hosts` instead of a direct search (requires that a node be set up to run the cacher recipe regularly).\n* `node['ssh_known_hosts']['cacher']['data_bag']`/`node['ssh_known_hosts']['cacher']['data_bag_item']` - Data bag where cacher recipe should store its keys.\n\n#### LWRP Attributes\n\n\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    AttributeDescriptionExampleDefault
    hostthe host to addgithub.com
    key(optional) provide your own keyssh-rsa ...ssh-keyscan -H #{host}
    port(optional) the server port that ssh-keyscan will use to gather the public key222222
    \n\n- - -\n\n### Default Recipe\n\nSearches the Chef Server for all hosts that have SSH host keys using `rsa,dsa` key types and generates an `/etc/ssh/ssh_known_hosts`.\n\n#### Adding custom host keys\n\nThere are two ways to add custom host keys. You can either use the provided LWRP (see above), or by creating a data bag called \"`ssh_known_hosts`\" and adding an item for each host:\n\n```javascript\n{\n \"id\": \"github\",\n \"fqdn\": \"github.com\",\n \"rsa\": \"github-rsa-host-key\"\n}\n```\n\nThere are additional optional values you may use in the data bag:\n\n\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    AttributeDescriptionExampleDefault
    ida unique id for this data bag entrygithub
    fqdnthe fqdn of the hostgithub.com
    rsathe rsa key for this serverssh-rsa AAAAB3...
    ipaddressthe ipaddress of the node (if fqdn is missing)1.1.1.1
    hostnamelocal hostname of the server (if not a fqdn)myserver.local
    dsathe dsa key for this serverssh-dsa ABAAC3...
    \n\n###ChefSpec matchers\n\nA custom matcher is available for you to use in recipe tests.\n\n``` \ndescribe 'my_cookbook::my_recipe' do\n\tlet(:chef_run) { ChefSpec::Runner.new.converge(described_recipe) }\n\tit { expect(chef_run).to append_to_ssh_known_hosts 'github.com' }\nend\n```\n\n\nLicense and Authors\n-------------------\n- Author: Scott M. Likens ()\n- Author: Seth Vargo ()\n- Author: Lamont Granquist ()\n\n```text\nCopyright:: 2011-2013, Opscode, Inc\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "maintainer": "Opscode, Inc.", + "maintainer_email": "cookbooks@opscode.com", + "license": "Apache 2.0", + "platforms": { + }, + "dependencies": { + "partial_search": ">= 0.0.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + "ssh_known_hosts": "Provides an LWRP for managing SSH known hosts. Also includes a recipe for automatically adding all nodes to the SSH known hosts." + } +} \ No newline at end of file diff --git a/cookbooks/ssh_known_hosts/providers/entry.rb b/cookbooks/ssh_known_hosts/providers/entry.rb new file mode 100644 index 0000000..e6e60a1 --- /dev/null +++ b/cookbooks/ssh_known_hosts/providers/entry.rb @@ -0,0 +1,78 @@ +# +# Author:: Seth Vargo () +# Provider:: entry +# +# Copyright 2013, Seth Vargo +# +# 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. +# + +use_inline_resources if defined?(use_inline_resources) + +def whyrun_supported? + true +end + +action :create do + + if new_resource.key + + if new_resource.key_type == 'rsa' || new_resource.key_type == 'dsa' + key_type = "ssh-#{new_resource.key_type}" + else + key_type = new_resource.key_type + end + + key = "#{new_resource.host} #{key_type} #{new_resource.key}" + + else + + key = `ssh-keyscan -t#{node['ssh_known_hosts']['key_type']} -p #{new_resource.port} #{new_resource.host}` + end + comment = key.split("\n").first || "" + + if key_exists?(key, comment) + Chef::Log.debug "Known hosts key for #{new_resource.name} already exists - skipping" + else + new_keys = (keys + [key]).uniq.sort + file "ssh_known_hosts-#{new_resource.name}" do + path node['ssh_known_hosts']['file'] + action :create + backup false + content "#{new_keys.join($/)}#{$/}" + end + end +end + +private + def keys + unless @keys + if key_file_exists? + lines = ::File.readlines(node['ssh_known_hosts']['file']) + @keys = lines.map {|line| line.chomp}.reject {|line| line.empty?} + else + @keys = [] + end + end + @keys + end + + def key_file_exists? + ::File.exists?(node['ssh_known_hosts']['file']) + end + + def key_exists?(key, comment) + keys.any? do |line| + line.match(/#{Regexp.escape(comment)}|#{Regexp.escape(key)}/) + end + end diff --git a/cookbooks/ssh_known_hosts/recipes/cacher.rb b/cookbooks/ssh_known_hosts/recipes/cacher.rb new file mode 100644 index 0000000..3ec840b --- /dev/null +++ b/cookbooks/ssh_known_hosts/recipes/cacher.rb @@ -0,0 +1,60 @@ +# Gather a list of all nodes, warning if using Chef Solo +if Chef::Config[:solo] + fail 'ssh_known_hosts::cacher requires Chef search - Chef Solo does ' \ + 'not support search!' +else + all_host_keys = partial_search( + :node, 'keys:*', + :keys => { + 'hostname' => [ 'hostname' ], + 'fqdn' => [ 'fqdn' ], + 'ipaddress' => [ 'ipaddress' ], + 'host_rsa_public' => [ 'keys', 'ssh', 'host_rsa_public' ], + 'host_dsa_public' => [ 'keys', 'ssh', 'host_dsa_public' ] + } + ).collect do |host| + { + 'fqdn' => host['fqdn'] || host['ipaddress'] || host['hostname'], + 'key' => host['host_rsa_public'] || host['host_dsa_public'] + } + end + Chef::Log.debug("Partial search got: #{all_host_keys.inspect}") +end + +new_data_bag_content = { + "id" => node['ssh_known_hosts']['cacher']['data_bag_item'], + "keys" => all_host_keys +} + +Chef::Log.debug('New data bag content: ' \ + "#{new_data_bag_content.inspect}") + +if Chef::DataBag.list.key?(node['ssh_known_hosts']['cacher']['data_bag']) + # Check to see if there are actually any changes to be made (so we don't save + # data bags unnecessarily) + existing_data_bag_content = data_bag_item( + node['ssh_known_hosts']['cacher']['data_bag'], + node['ssh_known_hosts']['cacher']['data_bag_item'] + ).raw_data + Chef::Log.debug('Existing data bag content: ' \ + "#{existing_data_bag_content.inspect}") +else + Chef::Log.debug('Data bag ' \ + "\"#{node['ssh_known_hosts']['cacher']['data_bag']}\" not found. " \ + 'Creating.') + new_databag = Chef::DataBag.new + new_databag.name(node['ssh_known_hosts']['cacher']['data_bag']) + new_databag.save +end + +unless (defined? existing_data_bag_content) && + new_data_bag_content == existing_data_bag_content + + Chef::Log.debug('Data bag contents differ. Saving updates.') + + host_key_db_item = Chef::DataBagItem.new + host_key_db_item.data_bag(node['ssh_known_hosts']['cacher']['data_bag']) + host_key_db_item.raw_data = new_data_bag_content + + host_key_db_item.save +end diff --git a/cookbooks/ssh_known_hosts/recipes/default.rb b/cookbooks/ssh_known_hosts/recipes/default.rb new file mode 100644 index 0000000..9266ad9 --- /dev/null +++ b/cookbooks/ssh_known_hosts/recipes/default.rb @@ -0,0 +1,87 @@ +# +# Cookbook Name:: ssh_known_hosts +# Recipe:: default +# +# Author:: Scott M. Likens () +# Author:: Joshua Timberman () +# Author:: Seth Vargo () +# +# Copyright 2009, Adapp, Inc. +# Copyright 2011-2013, Opscode, Inc. +# +# 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. + +if node['ssh_known_hosts']['use_data_bag_cache'] + # Load hosts from the ssh known hosts cacher (if the data bag exists) + unless Chef::DataBag.list.key?(node['ssh_known_hosts']['cacher']['data_bag']) + fail 'use_data_bag_cache is set but the configured data bag was not found' + end + + hosts = data_bag_item( + node['ssh_known_hosts']['cacher']['data_bag'], + node['ssh_known_hosts']['cacher']['data_bag_item'] + )['keys'] + Chef::Log.info "hosts data bag: #{hosts.inspect}" +else + # Gather a list of all nodes, warning if using Chef Solo + if Chef::Config[:solo] + Chef::Log.warn 'ssh_known_hosts requires Chef search - Chef Solo does not support search!' + + # On Chef Solo, we still want the current node to be in the ssh_known_hosts + hosts = [node] + else + hosts = partial_search(:node, "keys_ssh:* NOT name:#{node.name}", + :keys => { + 'hostname' => [ 'hostname' ], + 'fqdn' => [ 'fqdn' ], + 'ipaddress' => [ 'ipaddress' ], + 'host_rsa_public' => [ 'keys', 'ssh', 'host_rsa_public' ], + 'host_dsa_public' => [ 'keys', 'ssh', 'host_dsa_public' ] + } + ).collect do |host| + { + 'fqdn' => host['fqdn'] || host['ipaddress'] || host['hostname'], + 'key' => host['host_rsa_public'] || host['host_dsa_public'] + } + end + end +end + +# Add the data from the data_bag to the list of nodes. +# We need to rescue in case the data_bag doesn't exist. +if Chef::DataBag.list.key?('ssh_known_hosts') + begin + hosts += data_bag('ssh_known_hosts').collect do |item| + entry = data_bag_item('ssh_known_hosts', item) + { + 'fqdn' => entry['fqdn'] || entry['ipaddress'] || entry['hostname'], + 'key' => entry['rsa'] || entry['dsa'] + } + end + rescue + Chef::Log.info "Could not load data bag 'ssh_known_hosts'" + end +end + +# Loop over the hosts and add 'em +hosts.each do |host| + unless host['key'].nil? + # The key was specified, so use it + ssh_known_hosts_entry host['fqdn'] do + key host['key'] + end + else + # No key specified, so have known_host perform a DNS lookup + ssh_known_hosts_entry host['fqdn'] + end +end diff --git a/cookbooks/ssh_known_hosts/resources/entry.rb b/cookbooks/ssh_known_hosts/resources/entry.rb new file mode 100644 index 0000000..a0c1b31 --- /dev/null +++ b/cookbooks/ssh_known_hosts/resources/entry.rb @@ -0,0 +1,30 @@ +# +# Author:: Seth Vargo () +# Resource:: entry +# +# Copyright 2013, Seth Vargo +# +# 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. + +actions :create +default_action :create + +attribute :host, :kind_of => String, :name_attribute => true +attribute :key, :kind_of => String +attribute :key_type, :kind_of => String, :default => 'rsa' +attribute :port, :kind_of => Fixnum, :default => 22 + +def initialize(*args) + super + @action = :create +end diff --git a/cookbooks/sudo/CHANGELOG.md b/cookbooks/sudo/CHANGELOG.md new file mode 100644 index 0000000..6cfa2b6 --- /dev/null +++ b/cookbooks/sudo/CHANGELOG.md @@ -0,0 +1,119 @@ +v2.7.1 (2014-09-18) +------------------- +- [#53] - removed doublespace from sudoer.erb template + +v2.7.0 (2014-08-08) +------------------- +- [#44] Add basic ChefSpec matchers + +v2.6.0 (2014-05-15) +------------------- +- [COOK-4612] Add support for the command alias (Cmnd_Alias) directive +- [COOK-4637] - handle duplicate resources in LWRP + + +v2.5.2 (2014-02-27) +------------------- +Bumping version for toolchain sanity + + +v2.5.0 (2014-02-27) +------------------- +Bumping to 2.5.0 + + +v2.4.2 (2014-02-27) +------------------- +[COOK-4350] - Fix issue with "Defaults" line in sudoer.erb + + +v2.4.0 (2014-02-18) +------------------- +### Bug +- **[COOK-4225](https://tickets.opscode.com/browse/COOK-4225)** - Mac OS X: /etc/sudoers: syntax error when include_sudoers_d is true + +### Improvement +- **[COOK-4014](https://tickets.opscode.com/browse/COOK-4014)** - It should be possible to remove the 'sysadmin' group setting from /etc/sudoers +- **[COOK-3643](https://tickets.opscode.com/browse/COOK-3643)** - FreeBSD support for sudo cookbook + +### New Feature +- **[COOK-3409](https://tickets.opscode.com/browse/COOK-3409)** - enhance sudo lwrp's default template to allow defining default user parameters + + +v2.3.0 +------ +### Improvement +- **[COOK-3843](https://tickets.opscode.com/browse/COOK-3843)** - Make cookbook 'sudo' compatible with Mac OS X + + +v2.2.2 +------ +### Improvement +- **[COOK-3653](https://tickets.opscode.com/browse/COOK-3653)** - Change template attribute to kind_of String +- **[COOK-3572](https://tickets.opscode.com/browse/COOK-3572)** - Add Test Kitchen, Specs, and Travis CI + +### Bug +- **[COOK-3610](https://tickets.opscode.com/browse/COOK-3610)** - Document "Runas" attribute not described in the LWRP Attributes section +- **[COOK-3431](https://tickets.opscode.com/browse/COOK-3431)** - Validate correctly with `visudo` + + +v2.2.0 +------ +### New Feature +- **[COOK-3056](https://tickets.opscode.com/browse/COOK-3056)** - Allow custom sudoers config prefix + +v2.1.4 +------ +This is a bugfix for 11.6.0 compatibility, as we're not monkey-patching Erubis::Context. + +### Bug +- [COOK-3399]: Remove node attribute in comment of sudoers templates + +v2.1.2 +------ +### Bug +- [COOK-2388]: Chef::ShellOut is deprecated, please use Mixlib::ShellOut +- [COOK-2814]: Incorrect syntax in README example + +v2.1.0 +------ +* [COOK-2388] - Chef::ShellOut is deprecated, please use Mixlib::ShellOut +* [COOK-2427] - unable to install users cookbook in chef 11 +* [COOK-2814] - Incorrect syntax in README example + +v2.0.4 +------ +* [COOK-2078] - syntax highlighting README on GitHub flavored markdown +* [COOK-2119] - LWRP template doesn't support multiple commands in a single block. + +v2.0.2 +------ +* [COOK-2109] - lwrp uses incorrect action on underlying file resource. + +v2.0.0 +------ +This is a major release because the LWRP's "nopasswd" attribute is changed from true to false, to match the passwordless attribute in the attributes file. This requires a change to people's LWRP use. + +* [COOK-2085] - Incorrect default value in the sudo LWRP's nopasswd attribute + +v1.3.0 +------ +* [COOK-1892] - Revamp sudo cookbook and LWRP +* [COOK-2022] - add an attribute for setting /etc/sudoers Defaults + +v1.2.2 +------ +* [COOK-1628] - set host in sudo lwrp + +v1.2.0 +------ +* [COOK-1314] - default package action is now :install instead of :upgrade +* [COOK-1549] - Preserve SSH agent credentials upon sudo using an attribute + +v1.1.0 +------ +* [COOK-350] - LWRP to manage sudo files via includedir (/etc/sudoers.d) + +v1.0.2 +------ +* [COOK-903] - freebsd support diff --git a/cookbooks/sudo/README.md b/cookbooks/sudo/README.md new file mode 100644 index 0000000..e79c208 --- /dev/null +++ b/cookbooks/sudo/README.md @@ -0,0 +1,307 @@ +sudo cookbook +============= +[![Build Status](https://secure.travis-ci.org/opscode-cookbooks/sudo.png?branch=master)](http://travis-ci.org/opscode-cookbooks/sudo) + +The Chef `sudo` cookbook installs the `sudo` package and configures the `/etc/sudoers` file. + +It also exposes an LWRP for adding and managing sudoers. + + +Requirements +------------ +The platform has a package named `sudo` and the `sudoers` file is `/etc/sudoers`. + + +Attributes +---------- +- `node['authorization']['sudo']['groups']` - groups to enable sudo access (default: `[ "sysadmin" ]`) +- `node['authorization']['sudo']['users']` - users to enable sudo access (default: `[]`) +- `node['authorization']['sudo']['passwordless']` - use passwordless sudo (default: `false`) +- `node['authorization']['sudo']['include_sudoers_d']` - include and manager `/etc/sudoers.d` (default: `false`) +- `node['authorization']['sudo']['agent_forwarding']` - preserve `SSH_AUTH_SOCK` when sudoing (default: `false`) +- `node['authorization']['sudo']['sudoers_defaults']` - Array of `Defaults` entries to configure in `/etc/sudoers` + + +Usage +----- +#### Attributes +To use attributes for defining sudoers, set the attributes above on the node (or role) itself: + +```json +{ + "default_attributes": { + "authorization": { + "sudo": { + "groups": ["admin", "wheel", "sysadmin"], + "users": ["jerry", "greg"], + "passwordless": "true" + } + } + } +} +``` + +```ruby +# roles/example.rb +default_attributes( + "authorization" => { + "sudo" => { + "groups" => ["admin", "wheel", "sysadmin"], + "users" => ["jerry", "greg"], + "passwordless" => true + } + } +) +``` + +**Note that the template for the sudoers file has the group "sysadmin" with ALL:ALL permission, though the group by default does not exist.** + +#### Sudoers Defaults + +Configure a node attribute, +`node['authorization']['sudo']['sudoers_defaults']` as an array of +`Defaults` entries to configure in `/etc/sudoers`. A list of examples +for common platforms is listed below: + +*Debian* +```ruby +node.default['authorization']['sudo']['sudoers_defaults'] = ['env_reset'] +``` + +*Ubuntu 10.04* +```ruby +node.default['authorization']['sudo']['sudoers_defaults'] = ['env_reset'] +``` + +*Ubuntu 12.04* +```ruby +node.default['authorization']['sudo']['sudoers_defaults'] = [ + 'env_reset', + 'secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"' +] +``` + +*FreeBSD* +```ruby +node.default['authorization']['sudo']['sudoers_defaults'] = [ + 'env_reset', + 'secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"' +] +``` + +*RHEL family 5.x* +The version of sudo in RHEL 5 may not support `+=`, as used in `env_keep`, so its a single string. + +```ruby +node.default['authorization']['sudo']['sudoers_defaults'] = [ + '!visiblepw', + 'env_reset', + 'env_keep = "COLORS DISPLAY HOSTNAME HISTSIZE INPUTRC KDEDIR \ + LS_COLORS MAIL PS1 PS2 QTDIR USERNAME \ + LANG LC_ADDRESS LC_CTYPE LC_COLLATE LC_IDENTIFICATION \ + LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC \ + LC_PAPER LC_TELEPHONE LC_TIME LC_ALL LANGUAGE LINGUAS \ + _XKB_CHARSET XAUTHORITY"' +] +``` + +*RHEL family 6.x* +```ruby +node.default['authorization']['sudo']['sudoers_defaults'] = [ + '!visiblepw', + 'env_reset', + 'env_keep = "COLORS DISPLAY HOSTNAME HISTSIZE INPUTRC KDEDIR LS_COLORS"', + 'env_keep += "MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE"', + 'env_keep += "LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES"', + 'env_keep += "LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE"', + 'env_keep += "LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY"', + 'env_keep += "HOME"', + 'always_set_home', + 'secure_path = /sbin:/bin:/usr/sbin:/usr/bin' +] +``` + +*Mac OS X* +```ruby +node.default['authorization']['sudo']['sudoers_defaults'] = [ + 'env_reset', + 'env_keep += "BLOCKSIZE"', + 'env_keep += "COLORFGBG COLORTERM"', + 'env_keep += "__CF_USER_TEXT_ENCODING"', + 'env_keep += "CHARSET LANG LANGUAGE LC_ALL LC_COLLATE LC_CTYPE"', + 'env_keep += "LC_MESSAGES LC_MONETARY LC_NUMERIC LC_TIME"', + 'env_keep += "LINES COLUMNS"', + 'env_keep += "LSCOLORS"', + 'env_keep += "TZ"', + 'env_keep += "DISPLAY XAUTHORIZATION XAUTHORITY"', + 'env_keep += "EDITOR VISUAL"', + 'env_keep += "HOME MAIL"' +] +``` + +#### LWRP +**Note** Sudo version 1.7.2 or newer is required to use the sudo LWRP as it relies on the "#includedir" directive introduced in version 1.7.2. The recipe does not enforce installing the version. To use this LWRP, set `node['authorization']['sudo']['include_sudoers_d']` to `true`. + +There are two ways for rendering a sudoer-fragment using this LWRP: + + 1. Using the built-in template + 2. Using a custom, cookbook-level template + +Both methods will create the `/etc/sudoers.d/#{username}` file with the correct permissions. + +The LWRP also performs **fragment validation**. If a sudoer-fragment is not valid, the Chef run will throw an exception and fail. This ensures that your sudoers file is always valid and cannot become corrupt (from this cookbook). + +Example using the built-in template: + +```ruby +sudo 'tomcat' do + user "%tomcat" # or a username + runas 'app_user' # or 'app_user:tomcat' + commands ['/etc/init.d/tomcat restart'] +end +``` + +```ruby +sudo 'tomcat' do + template 'my_tomcat.erb' # local cookbook template + variables :cmds => ['/etc/init.d/tomcat restart'] +end +``` + +In either case, the following file would be generated in `/etc/sudoers.d/tomcat` + +```bash +# This file is managed by Chef for node.example.com +# Do NOT modify this file directly. + +%tomcat ALL=(app_user) /etc/init.d/tomcat restart +``` + +##### LWRP Attributes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeDescriptionExampleDefault
    namename of the `/etc/sudoers.d` filerestart-tomcatcurrent resource name
    commandsarray of commands this sudoer can execute['/etc/init.d/tomcat restart']['ALL']
    groupgroup to provide sudo privileges to, except `%` is prepended to the name in +case it is not already%admin
    nopasswdsupply a password to invoke sudotruefalse
    runasUser the command(s) can be run asrootALL
    templatethe erb template to render instead of the defaultrestart-tomcat.erb
    useruser to provide sudo privileges totomcat
    defaultsarray of defaults this user has['!requiretty','env_reset']
    variablesthe variables to pass to the custom template:commands => ['/etc/init.d/tomcat restart']
    + +**If you use the template attribute, all other attributes will be ignored except for the variables attribute.** + + +Development +----------- +This section details "quick development" steps. For a detailed explanation, see [[Contributing.md]]. + +1. Clone this repository from GitHub: + + $ git clone git@github.com:opscode-cookbooks/sudo.git + +2. Create a git branch + + $ git checkout -b my_bug_fix + +3. Install dependencies: + + $ bundle install + +4. Make your changes/patches/fixes, committing appropiately +5. **Write tests** +6. Run the tests: + - `bundle exec foodcritic -f any .` + - `bundle exec rspec` + - `bundle exec rubocop` + - `bundle exec kitchen test` + + In detail: + - Foodcritic will catch any Chef-specific style errors + - RSpec will run the unit tests + - Rubocop will check for Ruby-specific style errors + - Test Kitchen will run and converge the recipes + + + + +License and Authors +------------------- +- Author:: Bryan W. Berry +- Author:: Adam Jacob +- Author:: Seth Chisamore +- Author:: Seth Vargo + +```text +Copyright 2009-2012, Opscode, Inc. + +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/cookbooks/sudo/attributes/default.rb b/cookbooks/sudo/attributes/default.rb new file mode 100644 index 0000000..6b7ce50 --- /dev/null +++ b/cookbooks/sudo/attributes/default.rb @@ -0,0 +1,35 @@ +# +# Cookbook Name:: sudo +# Attribute File:: default +# +# Copyright 2008-2013, Opscode, Inc. +# +# 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. +# + +default['authorization']['sudo']['groups'] = ['sysadmin'] +default['authorization']['sudo']['users'] = [] +default['authorization']['sudo']['passwordless'] = false +default['authorization']['sudo']['include_sudoers_d'] = false +default['authorization']['sudo']['agent_forwarding'] = false +default['authorization']['sudo']['sudoers_defaults'] = ['!lecture,tty_tickets,!fqdn'] +default['authorization']['sudo']['command_aliases'] = [] + +case node['platform_family'] +when 'smartos' + default['authorization']['sudo']['prefix'] = '/opt/local/etc' +when 'freebsd' + default['authorization']['sudo']['prefix'] = '/usr/local/etc' +else + default['authorization']['sudo']['prefix'] = '/etc' +end diff --git a/cookbooks/sudo/files/default/README b/cookbooks/sudo/files/default/README new file mode 100644 index 0000000..4928b84 --- /dev/null +++ b/cookbooks/sudo/files/default/README @@ -0,0 +1,17 @@ +# +# As of Debian version 1.7.2p1-1, the default /etc/sudoers file created on +# installation of the package now includes the directive: +# +# #includedir /etc/sudoers.d +# +# This will cause sudo to read and parse any files in the /etc/sudoers.d +# directory that do not end in '~' or contain a '.' character. +# +# Note that there must be at least one file in the sudoers.d directory (this +# one will do), and all files in this directory should be mode 0440. +# +# Note also, that because sudoers contents can vary widely, no attempt is +# made to add this directive to existing sudoers files on upgrade. Feel free +# to add the above directive to the end of your /etc/sudoers file to enable +# this functionality for existing installations if you wish! +# diff --git a/cookbooks/sudo/libraries/matchers.rb b/cookbooks/sudo/libraries/matchers.rb new file mode 100644 index 0000000..3f3ec4b --- /dev/null +++ b/cookbooks/sudo/libraries/matchers.rb @@ -0,0 +1,9 @@ +if defined?(ChefSpec) + def install_sudo(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:sudo, :install, resource_name) + end + + def remove_sudo(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:sudo, :remove, resource_name) + end +end diff --git a/cookbooks/sudo/metadata.json b/cookbooks/sudo/metadata.json new file mode 100644 index 0000000..c911bc8 --- /dev/null +++ b/cookbooks/sudo/metadata.json @@ -0,0 +1,71 @@ +{ + "name": "sudo", + "version": "2.7.1", + "description": "Installs sudo and configures /etc/sudoers", + "long_description": "", + "maintainer": "Opscode, Inc.", + "maintainer_email": "cookbooks@opscode.com", + "license": "Apache 2.0", + "platforms": { + "redhat": ">= 0.0.0", + "centos": ">= 0.0.0", + "fedora": ">= 0.0.0", + "ubuntu": ">= 0.0.0", + "debian": ">= 0.0.0", + "freebsd": ">= 0.0.0", + "mac_os_x": ">= 0.0.0" + }, + "dependencies": { + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + "authorization": { + "display_name": "Authorization", + "description": "Hash of Authorization attributes", + "type": "hash" + }, + "authorization/sudo": { + "display_name": "Authorization Sudoers", + "description": "Hash of Authorization/Sudo attributes", + "type": "hash" + }, + "authorization/sudo/users": { + "display_name": "Sudo Users", + "description": "Users who are allowed sudo ALL", + "type": "array", + "default": "" + }, + "authorization/sudo/groups": { + "display_name": "Sudo Groups", + "description": "Groups who are allowed sudo ALL", + "type": "array", + "default": "" + }, + "authorization/sudo/passwordless": { + "display_name": "Passwordless Sudo", + "description": "", + "type": "string", + "default": "false" + }, + "authorization/sudo/include_sudoers_d": { + "display_name": "Include sudoers.d", + "description": "Whether to create the sudoers.d includedir", + "type": "string", + "default": "false" + } + }, + "groupings": { + }, + "recipes": { + "sudo": "Installs sudo and configures /etc/sudoers" + } +} \ No newline at end of file diff --git a/cookbooks/sudo/metadata.rb b/cookbooks/sudo/metadata.rb new file mode 100644 index 0000000..e455678 --- /dev/null +++ b/cookbooks/sudo/metadata.rb @@ -0,0 +1,46 @@ +name 'sudo' +maintainer 'Opscode, Inc.' +maintainer_email 'cookbooks@opscode.com' +license 'Apache 2.0' +description 'Installs sudo and configures /etc/sudoers' +version '2.7.1' + +recipe 'sudo', 'Installs sudo and configures /etc/sudoers' + +%w(redhat centos fedora ubuntu debian freebsd mac_os_x).each do |os| + supports os +end + +attribute 'authorization', + :display_name => 'Authorization', + :description => 'Hash of Authorization attributes', + :type => 'hash' + +attribute 'authorization/sudo', + :display_name => 'Authorization Sudoers', + :description => 'Hash of Authorization/Sudo attributes', + :type => 'hash' + +attribute 'authorization/sudo/users', + :display_name => 'Sudo Users', + :description => 'Users who are allowed sudo ALL', + :type => 'array', + :default => '' + +attribute 'authorization/sudo/groups', + :display_name => 'Sudo Groups', + :description => 'Groups who are allowed sudo ALL', + :type => 'array', + :default => '' + +attribute 'authorization/sudo/passwordless', + :display_name => 'Passwordless Sudo', + :description => '', + :type => 'string', + :default => 'false' + +attribute 'authorization/sudo/include_sudoers_d', + :display_name => 'Include sudoers.d', + :description => 'Whether to create the sudoers.d includedir', + :type => 'string', + :default => 'false' diff --git a/cookbooks/sudo/providers/default.rb b/cookbooks/sudo/providers/default.rb new file mode 100644 index 0000000..be68133 --- /dev/null +++ b/cookbooks/sudo/providers/default.rb @@ -0,0 +1,148 @@ +# +# Author:: Bryan W. Berry () +# Author:: Seth Vargo () +# Cookbook Name:: sudo +# Provider:: default +# +# Copyright 2011, Bryan w. Berry +# Copyright 2012, Seth Vargo +# +# 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. +# + +# This LWRP supports whyrun mode +def whyrun_supported? + true +end + +# Ensure that the inputs are valid (we cannot just use the resource for this) +def check_inputs(user, group, foreign_template, foreign_vars) + # if group, user, and template are nil, throw an exception + if user.nil? && group.nil? && foreign_template.nil? + fail 'You must provide a user, group, or template!' + elsif !user.nil? && !group.nil? && !template.nil? + fail 'You cannot specify user, group, and template!' + end +end + +# Validate the given resource (template) by writing it out to a file and then +# ensuring that file's contents pass `visudo -c` +def validate_fragment!(resource) + file = Tempfile.new('sudoer') + + begin + file.write(capture(resource)) + file.rewind + + cmd = Mixlib::ShellOut.new("visudo -cf #{file.path}").run_command + unless cmd.exitstatus == 0 + Chef::Log.error("Fragment validation failed: \n\n") + Chef::Log.error(file.read) + Chef::Application.fatal!("Template #{file.path} failed fragment validation!") + end + ensure + file.close + file.unlink + end +end + +# Render a single sudoer template. This method has two modes: +# 1. using the :template option - the user can specify a template +# that exists in the local cookbook for writing out the attributes +# 2. using the built-in template (recommended) - simply pass the +# desired variables to the method and the correct template will be +# written out for the user +def render_sudoer + if new_resource.template + Chef::Log.debug('Template attribute provided, all other attributes ignored.') + + resource = template "#{node['authorization']['sudo']['prefix']}/sudoers.d/#{new_resource.name}" do + source new_resource.template + owner 'root' + group node['root_group'] + mode '0440' + variables new_resource.variables + action :nothing + end + else + sudoer = new_resource.user || "%#{new_resource.group}".squeeze('%') + + resource = template "#{node['authorization']['sudo']['prefix']}/sudoers.d/#{new_resource.name}" do + source 'sudoer.erb' + cookbook 'sudo' + owner 'root' + group node['root_group'] + mode '0440' + variables :sudoer => sudoer, + :host => new_resource.host, + :runas => new_resource.runas, + :nopasswd => new_resource.nopasswd, + :commands => new_resource.commands, + :command_aliases => new_resource.command_aliases, + :defaults => new_resource.defaults + action :nothing + end + end + + # Ensure that, adding this sudoer, would not break sudo + validate_fragment!(resource) + + resource.run_action(:create) + + # Return whether the resource was updated so we can notify in the action + resource.updated_by_last_action? +end + +# Default action - install a single sudoer +action :install do + target = "#{node['authorization']['sudo']['prefix']}/sudoers.d/" + + unless ::File.exists?(target) + sudoers_dir = directory target + sudoers_dir.run_action(:create) + end + + new_resource.updated_by_last_action(true) if render_sudoer +end + +# Removes a user from the sudoers group +action :remove do + resource = file "#{node['authorization']['sudo']['prefix']}/sudoers.d/#{new_resource.name}" do + action :nothing + end + resource.run_action(:delete) + new_resource.updated_by_last_action(true) if resource.updated_by_last_action? +end + +private + +# Capture a template to a string +def capture(template) + context = {} + context.merge!(template.variables) + context[:node] = node + + eruby = Erubis::Eruby.new(::File.read(template_location(template))) + eruby.evaluate(context) +end + +# Find the template +def template_location(template) + if template.local + template.source + else + context = template.instance_variable_get('@run_context') + cookbook = context.cookbook_collection[template.cookbook || template.cookbook_name] + cookbook.preferred_filename_on_disk_location(node, :templates, template.source) + end +end diff --git a/cookbooks/sudo/recipes/default.rb b/cookbooks/sudo/recipes/default.rb new file mode 100644 index 0000000..afb3574 --- /dev/null +++ b/cookbooks/sudo/recipes/default.rb @@ -0,0 +1,55 @@ +# +# Cookbook Name:: sudo +# Recipe:: default +# +# Copyright 2008-2013, Opscode, Inc. +# +# 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. +# + +prefix = node['authorization']['sudo']['prefix'] + +package 'sudo' do + not_if 'which sudo' +end + +if node['authorization']['sudo']['include_sudoers_d'] + directory "#{prefix}/sudoers.d" do + mode '0755' + owner 'root' + group node['root_group'] + end + + cookbook_file "#{prefix}/sudoers.d/README" do + source 'README' + mode '0440' + owner 'root' + group node['root_group'] + end +end + +template "#{prefix}/sudoers" do + source 'sudoers.erb' + mode '0440' + owner 'root' + group node['root_group'] + variables( + :sudoers_groups => node['authorization']['sudo']['groups'], + :sudoers_users => node['authorization']['sudo']['users'], + :passwordless => node['authorization']['sudo']['passwordless'], + :include_sudoers_d => node['authorization']['sudo']['include_sudoers_d'], + :agent_forwarding => node['authorization']['sudo']['agent_forwarding'], + :sudoers_defaults => node['authorization']['sudo']['sudoers_defaults'], + :command_aliases => node['authorization']['sudo']['command_aliases'] + ) +end diff --git a/cookbooks/sudo/resources/default.rb b/cookbooks/sudo/resources/default.rb new file mode 100644 index 0000000..1b18858 --- /dev/null +++ b/cookbooks/sudo/resources/default.rb @@ -0,0 +1,50 @@ +# +# Author:: Bryan W. Berry () +# Cookbook Name:: sudo +# Resource:: default +# +# Copyright 2011-2013, Bryan w. Berry +# +# 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. + +actions :install, :remove +default_action :install + +attribute :user, :kind_of => String, :default => nil +attribute :group, :kind_of => String, :default => nil +attribute :commands, :kind_of => Array, :default => ['ALL'] +attribute :host, :kind_of => String, :default => 'ALL' +attribute :runas, :kind_of => String, :default => 'ALL' +attribute :nopasswd, :equal_to => [true, false], :default => false +attribute :template, :kind_of => String, :default => nil +attribute :variables, :kind_of => Hash, :default => nil +attribute :defaults, :kind_of => Array, :default => [] +attribute :command_aliases, :kind_of => Array, :default => [] + +# Set default for the supports attribute in initializer since it is +# a 'reserved' attribute name +def initialize(*args) + super + @action = :install + @supports = { :report => true, :exception => true } +end + +state_attrs :commands, + :group, + :host, + :nopasswd, + :runas, + :template, + :user, + :variables, + :command_aliases diff --git a/cookbooks/sudo/templates/default/sudoer.erb b/cookbooks/sudo/templates/default/sudoer.erb new file mode 100644 index 0000000..2f6fd21 --- /dev/null +++ b/cookbooks/sudo/templates/default/sudoer.erb @@ -0,0 +1,14 @@ +# This file is managed by Chef. +# Do NOT modify this file directly. + +<% @command_aliases.each do |a| -%> +Cmnd_Alias <%= a[:name].upcase %> = <%= a[:command_list].join(', ') %> +<% end -%> + +<% @commands.each do |command| -%> +<%= @sudoer %> <%= @host %>=(<%= @runas %>) <%= 'NOPASSWD:' if @nopasswd %><%= command %> +<% end -%> + +<% unless @defaults.empty? %> +Defaults:<%= @sudoer %> <%= @defaults.join(',') %> +<% end -%> diff --git a/cookbooks/sudo/templates/default/sudoers.erb b/cookbooks/sudo/templates/default/sudoers.erb new file mode 100644 index 0000000..19be1ef --- /dev/null +++ b/cookbooks/sudo/templates/default/sudoers.erb @@ -0,0 +1,27 @@ +# This file is managed by Chef. +# Do NOT modify this file directly. + +<% @sudoers_defaults.each do |defaults| -%> +Defaults <%= defaults %> +<% end -%> +<% if @agent_forwarding -%> +Defaults env_keep+=SSH_AUTH_SOCK +<% end -%> + +# User privilege specification +root ALL=(ALL) ALL + +<% @command_aliases.each do |a| -%> +Cmnd_Alias <%= a[:name].upcase %> = <%= a[:command_list].join(', ') %> +<% end -%> + +<% @sudoers_users.each do |user| -%> +<%= user %> ALL=(ALL) <%= "NOPASSWD:" if @passwordless %>ALL +<% end -%> + +<% @sudoers_groups.each do |group| -%> +# Members of the group '<%= group %>' may gain root privileges +%<%= group %> ALL=(ALL) <%= "NOPASSWD:" if @passwordless %>ALL +<% end -%> + +<%= "#includedir #{node['authorization']['sudo']['prefix']}/sudoers.d" if @include_sudoers_d %> diff --git a/cookbooks/sudo/templates/mac_os_x/sudoers.erb b/cookbooks/sudo/templates/mac_os_x/sudoers.erb new file mode 100644 index 0000000..c83e4ee --- /dev/null +++ b/cookbooks/sudo/templates/mac_os_x/sudoers.erb @@ -0,0 +1,23 @@ +# This file is managed by Chef. +# Do NOT modify this file directly. + +<% @sudoers_defaults.each do |defaults| -%> +Defaults <%= defaults %> +<% end -%> +<% if @agent_forwarding -%> +Defaults env_keep+=SSH_AUTH_SOCK +<% end -%> + +# User privilege specification +root ALL=(ALL) ALL +%admin ALL=(ALL) ALL +<% @sudoers_users.each do |user| -%> +<%= user %> ALL=(ALL) <%= "NOPASSWD:" if @passwordless %>ALL +<% end -%> + +<% @sudoers_groups.each do |group| -%> +# Members of the group '<%= group %>' may gain root privileges +%<%= group %> ALL=(ALL) <%= "NOPASSWD:" if @passwordless %>ALL +<% end -%> + +<%= "#includedir #{node['authorization']['sudo']['prefix']}/sudoers.d" if @include_sudoers_d %> diff --git a/cookbooks/ufw/CHANGELOG.md b/cookbooks/ufw/CHANGELOG.md new file mode 100644 index 0000000..8386ebf --- /dev/null +++ b/cookbooks/ufw/CHANGELOG.md @@ -0,0 +1,30 @@ +ufw Cookbook CHANGELOG +====================== +This file is used to list changes made in each version of the ufw cookbook. + + +v0.7.4 +------ +No change. Version bump for toolchain + + +v0.7.2 +------ +Updating metadata to depend on firewall >= 0.9 + + +v0.7.0 +------ +[COOK-3592] - allow source ports to be defined as a range in ufw + + +v0.6.4 +------ +### Bug +- **[COOK-3316](https://tickets.opscode.com/browse/COOK-3316)** - Fix README.md example + +v0.6.2 +------ +### Bug +- [COOK-2487]: when setting a node attribute you must specify the precedence +- [COOK-2982]: ufw cookbook has foodcritic failures diff --git a/cookbooks/ufw/README.md b/cookbooks/ufw/README.md new file mode 100644 index 0000000..a11f57a --- /dev/null +++ b/cookbooks/ufw/README.md @@ -0,0 +1,144 @@ +Description +=========== +Configures Uncomplicated Firewall (ufw) on Ubuntu. Including the `ufw` recipe in a run list means the firewall will be enabled and will deny everything except SSH and ICMP ping by default. + +Rules may be added to the node by adding them to the `['firewall']['rules']` attributes in roles or on the node directly. The `firewall` cookbook has an LWRP that may be used to apply rules directly from other recipes as well. There is no need to explicitly remove rules, they are reevaluated on changes and reset. Rules are applied in the order of the run list, unless ordering is explictly added. + +Requirements +============ +Tested with Ubuntu 10.04 and 11.04. + +Recipes +======= +default +------- +The `default` recipe looks for the list of firewall rules to apply from the `['firewall']['rules']` attribute added to roles and on the node itself. The list of rules is then applied to the node in the order specified. + +disable +------- +The `disable` recipe is used if there is a need to disable the existing firewall, perhaps for testing. It disables the ufw firewall even if other ufw recipes attempt to enable it. + +If you remove this recipe, the firewall does not get automatically re-enabled. You will need clear the value of the `['firewall']['state']` to force a recalculation of the firewall rules. This can be done with `knife node edit`. + +databag +------- +The `databag` recipe looks in the `firewall` data bag for to apply firewall rules based on inspecting the runlist for roles and recipe names for keys that map to the data bag items and are applied in the the order specified. + +The `databag` recipe calls the `default` recipe after the `['firewall']['rules']` attribute is set to appy the rules, so you may mix roles with databag items if you want (roles apply first, then data bag contents). + +recipes +------- +The `recipes` recipe applies firewall rules based on inspecting the runlist for recipes that have node[]['firewall']['rules'] attributes. These are appended to node['firewall']['rules'] and applied to the node. Cookbooks may define attributes for recipes like so: + +# attributes/default.rb for test cookbook + default['test']['firewall']['rules'] = [ + {"test"=> { + "port"=> "27901", + "protocol"=> "udp" + } + } + ] + default['test::awesome']['firewall']['rules'] = [ + {"awesome"=> { + "port"=> "99427", + "protocol"=> "udp" + } + }, + {"awesome2"=> { + "port"=> "99428" + } + } + ] + +Note that the 'test::awesome' rules are only applied if that specific recipe is in the runlist. Recipe-applied firewall rules are applied after any rules defined in role attributes. + +securitylevel +------------- +The `securitylevel` recipe is used if there are any node['firewall']['securitylevel'] settings that need to be enforced. It is a reference implementation with nothing configured. + +Attributes +========== +Roles and the node may have the `['firewall']['rules']` attribute set. This attribute is a list of hashes, the key will be rule name, the value will be the hash of parameters. Application order is based on run list. + +# Example Role + name "fw_example" + description "Firewall rules for Examples" + override_attributes( + "firewall" => { + "rules" => [ + {"tftp" => {}}, + {"http" => { + "port" => "80" + } + }, + {"block tomcat from 192.168.1.0/24" => { + "port" => "8080", + "source" => "192.168.1.0/24", + "action" => "deny" + } + }, + {"Allow access to udp 1.2.3.4 port 5469 from 1.2.3.5 port 5469" => { + "protocol" => "udp", + "port" => "5469", + "source" => "1.2.3.4", + "destination" => "1.2.3.5", + "dest_port" => "5469" + } + }, + {"allow to tcp ports 8000-8010 from 192.168.1.0/24" => { + "port_range" => "8000..8010", + "source" => "192.168.1.0/24", + "protocol" => "tcp" //protocol is mandatory when using port ranges + } + } + ] + } + ) + +Data Bags +========= +The `firewall` data bag may be used with the `databag` recipe. It will contain items that map to role names (eg. the 'apache' role will map to the 'apache' item in the 'firewall' data bag). Either roles or recipes may be keys (role[webserver] is 'webserver', recipe[apache2] is 'apache2'). If you have recipe-specific firewall rules, you will need to replace the '::' with '__' (double underscores) (eg. recipe[apache2::mod_ssl] is 'apache2__mod_ssl' in the data bag item). + +The items in the data bag will contain a 'rules' array of hashes to apply to the `['firewall']['rules']` attribute. + + % knife data bag create firewall + % knife data bag from file firewall examples/data_bags/firewall/apache2.json + % knife data bag from file firewall examples/data_bags/firewall/apache2__mod_ssl.json + +# Example 'firewall' data bag item + + { + "id": "apache2", + "rules": [ + {"http": { + "port": "80" + }}, + {"block http from 192.168.1.0/24": { + "port": "80", + "source": "192.168.1.0/24", + "action": "deny" + }} + ] + } + +Resources/Providers +=================== +The `firewall` cookbook provides the `firewall` and `firewall_rule` LWRPs, for which there is a ufw provider. + +License and Author +================== +Author:: Matt Ray () + +Copyright:: 2011 Opscode, Inc. + +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/cookbooks/ufw/attributes/default.rb b/cookbooks/ufw/attributes/default.rb new file mode 100644 index 0000000..b8e079d --- /dev/null +++ b/cookbooks/ufw/attributes/default.rb @@ -0,0 +1,2 @@ +default['firewall']['rules'] = [] +default['firewall']['securitylevel'] = "" diff --git a/cookbooks/ufw/metadata.json b/cookbooks/ufw/metadata.json new file mode 100644 index 0000000..7f9b26c --- /dev/null +++ b/cookbooks/ufw/metadata.json @@ -0,0 +1,40 @@ +{ + "name": "ufw", + "version": "0.7.4", + "description": "Installs/Configures ufw", + "long_description": "Description\n===========\nConfigures Uncomplicated Firewall (ufw) on Ubuntu. Including the `ufw` recipe in a run list means the firewall will be enabled and will deny everything except SSH and ICMP ping by default.\n\nRules may be added to the node by adding them to the `['firewall']['rules']` attributes in roles or on the node directly. The `firewall` cookbook has an LWRP that may be used to apply rules directly from other recipes as well. There is no need to explicitly remove rules, they are reevaluated on changes and reset. Rules are applied in the order of the run list, unless ordering is explictly added.\n\nRequirements\n============\nTested with Ubuntu 10.04 and 11.04.\n\nRecipes\n=======\ndefault\n-------\nThe `default` recipe looks for the list of firewall rules to apply from the `['firewall']['rules']` attribute added to roles and on the node itself. The list of rules is then applied to the node in the order specified.\n\ndisable\n-------\nThe `disable` recipe is used if there is a need to disable the existing firewall, perhaps for testing. It disables the ufw firewall even if other ufw recipes attempt to enable it.\n\nIf you remove this recipe, the firewall does not get automatically re-enabled. You will need clear the value of the `['firewall']['state']` to force a recalculation of the firewall rules. This can be done with `knife node edit`.\n\ndatabag\n-------\nThe `databag` recipe looks in the `firewall` data bag for to apply firewall rules based on inspecting the runlist for roles and recipe names for keys that map to the data bag items and are applied in the the order specified.\n\nThe `databag` recipe calls the `default` recipe after the `['firewall']['rules']` attribute is set to appy the rules, so you may mix roles with databag items if you want (roles apply first, then data bag contents).\n\nrecipes\n-------\nThe `recipes` recipe applies firewall rules based on inspecting the runlist for recipes that have node[]['firewall']['rules'] attributes. These are appended to node['firewall']['rules'] and applied to the node. Cookbooks may define attributes for recipes like so:\n\n# attributes/default.rb for test cookbook\n default['test']['firewall']['rules'] = [\n {\"test\"=> {\n \"port\"=> \"27901\",\n \"protocol\"=> \"udp\"\n }\n }\n ]\n default['test::awesome']['firewall']['rules'] = [\n {\"awesome\"=> {\n \"port\"=> \"99427\",\n \"protocol\"=> \"udp\"\n }\n },\n {\"awesome2\"=> {\n \"port\"=> \"99428\"\n }\n }\n ]\n\nNote that the 'test::awesome' rules are only applied if that specific recipe is in the runlist. Recipe-applied firewall rules are applied after any rules defined in role attributes.\n\nsecuritylevel\n-------------\nThe `securitylevel` recipe is used if there are any node['firewall']['securitylevel'] settings that need to be enforced. It is a reference implementation with nothing configured.\n\nAttributes\n==========\nRoles and the node may have the `['firewall']['rules']` attribute set. This attribute is a list of hashes, the key will be rule name, the value will be the hash of parameters. Application order is based on run list.\n\n# Example Role\n name \"fw_example\"\n description \"Firewall rules for Examples\"\n override_attributes(\n \"firewall\" => {\n \"rules\" => [\n {\"tftp\" => {}},\n {\"http\" => {\n \"port\" => \"80\"\n }\n },\n {\"block tomcat from 192.168.1.0/24\" => {\n \"port\" => \"8080\",\n \"source\" => \"192.168.1.0/24\",\n \"action\" => \"deny\"\n }\n },\n {\"Allow access to udp 1.2.3.4 port 5469 from 1.2.3.5 port 5469\" => {\n \"protocol\" => \"udp\",\n \"port\" => \"5469\",\n \"source\" => \"1.2.3.4\",\n \"destination\" => \"1.2.3.5\",\n \"dest_port\" => \"5469\"\n }\n },\n {\"allow to tcp ports 8000-8010 from 192.168.1.0/24\" => {\n \"port_range\" => \"8000..8010\",\n \"source\" => \"192.168.1.0/24\",\n \"protocol\" => \"tcp\" //protocol is mandatory when using port ranges\n }\n }\n ]\n }\n )\n\nData Bags\n=========\nThe `firewall` data bag may be used with the `databag` recipe. It will contain items that map to role names (eg. the 'apache' role will map to the 'apache' item in the 'firewall' data bag). Either roles or recipes may be keys (role[webserver] is 'webserver', recipe[apache2] is 'apache2'). If you have recipe-specific firewall rules, you will need to replace the '::' with '__' (double underscores) (eg. recipe[apache2::mod_ssl] is 'apache2__mod_ssl' in the data bag item).\n\nThe items in the data bag will contain a 'rules' array of hashes to apply to the `['firewall']['rules']` attribute.\n\n % knife data bag create firewall\n % knife data bag from file firewall examples/data_bags/firewall/apache2.json\n % knife data bag from file firewall examples/data_bags/firewall/apache2__mod_ssl.json\n\n# Example 'firewall' data bag item\n\n {\n \"id\": \"apache2\",\n \"rules\": [\n {\"http\": {\n \"port\": \"80\"\n }},\n {\"block http from 192.168.1.0/24\": {\n \"port\": \"80\",\n \"source\": \"192.168.1.0/24\",\n \"action\": \"deny\"\n }}\n ]\n }\n\nResources/Providers\n===================\nThe `firewall` cookbook provides the `firewall` and `firewall_rule` LWRPs, for which there is a ufw provider.\n\nLicense and Author\n==================\nAuthor:: Matt Ray ()\n\nCopyright:: 2011 Opscode, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n", + "maintainer": "Opscode, Inc.", + "maintainer_email": "matt@opscode.com", + "license": "Apache 2.0", + "platforms": { + "ubuntu": ">= 0.0.0" + }, + "dependencies": { + "firewall": ">= 0.9.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + "firewall/rules": { + "display_name": "List of firewall rules for the node.", + "description": "List of firewall rules for the node. Possibly set by node, roles or data bags.", + "type": "array" + }, + "firewall/securitylevel": { + "display_name": "Security level of the node.", + "description": "Security level of the node, may be set by node, roles or environment." + } + }, + "groupings": { + }, + "recipes": { + } +} \ No newline at end of file diff --git a/cookbooks/ufw/metadata.rb b/cookbooks/ufw/metadata.rb new file mode 100644 index 0000000..93c2e9e --- /dev/null +++ b/cookbooks/ufw/metadata.rb @@ -0,0 +1,21 @@ +name "ufw" +maintainer "Opscode, Inc." +maintainer_email "matt@opscode.com" +license "Apache 2.0" +description "Installs/Configures ufw" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version "0.7.4" +depends "firewall", ">= 0.9" + +%w{ ubuntu }.each do |os| + supports os +end + +attribute "firewall/rules", + :display_name => "List of firewall rules for the node.", + :description => "List of firewall rules for the node. Possibly set by node, roles or data bags.", + :type => "array" + +attribute "firewall/securitylevel", + :display_name => "Security level of the node.", + :description => "Security level of the node, may be set by node, roles or environment." diff --git a/cookbooks/ufw/recipes/databag.rb b/cookbooks/ufw/recipes/databag.rb new file mode 100644 index 0000000..0a643ec --- /dev/null +++ b/cookbooks/ufw/recipes/databag.rb @@ -0,0 +1,58 @@ +# +# Author:: Matt Ray +# Cookbook Name:: ufw +# Recipe:: databag +# +# Copyright 2011, Opscode, Inc +# +# 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. +# + +#flatten the run_list to just the names of the roles and recipes in order +def run_list_names(run_list) + names = [] + run_list.each do |entry| + Chef::Log.debug "ufw::databag:run_list_names+name: #{entry.name}" + if entry.name.index('::') #cookbook::recipe + names.push(entry.name.sub('::', '__')) + else + names.push(entry.name) + end + if entry.role? + rol = search(:role, "name:#{entry.name}")[0] + names.concat(run_list_names(rol.run_list)) + end + end + Chef::Log.debug "ufw::databag:run_list_names+names: #{names}" + return names +end + +rlist = run_list_names(node.run_list) +rlist.uniq! +Chef::Log.debug "ufw::databag:rlist: #{rlist}" + +fw_db = data_bag('firewall') +Chef::Log.debug "ufw::databag:firewall:#{fw_db}" + +rlist.each do |entry| + Chef::Log.debug "ufw::databag: \"#{entry}\"" + if fw_db.member?(entry) + #add the list of firewall rules to the current list + item = data_bag_item('firewall', entry) + rules = item['rules'] + node.set['firewall']['rules'].concat(rules) unless rules.nil? + end +end + +#now go apply the rules +include_recipe "ufw::default" diff --git a/cookbooks/ufw/recipes/default.rb b/cookbooks/ufw/recipes/default.rb new file mode 100644 index 0000000..5c4981d --- /dev/null +++ b/cookbooks/ufw/recipes/default.rb @@ -0,0 +1,90 @@ +# +# Author:: Matt Ray +# Cookbook Name:: ufw +# Recipe:: default +# +# Copyright 2011, Opscode, Inc +# +# 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. +# + +package "ufw" + +old_state = node['firewall']['state'] +new_state = node['firewall']['rules'].to_s +Chef::Log.debug "Old firewall state:#{old_state}" +Chef::Log.debug "New firewall state:#{new_state}" + +#check to see if the firewall rules changed. +#the rules are always changed the first run +if old_state == new_state + Chef::Log.info "Firewall rules unchanged." +else + Chef::Log.info "Firewall rules updated." + node.set['firewall']['state'] = new_state + + #drop rules and re-enable + execute "ufw --force reset" + + firewall "ufw" do + action :enable + end + + #leave this on by default + firewall_rule "ssh" do + port 22 + action :allow + end + + node['firewall']['rules'].each do |rule_mash| + Chef::Log.debug "ufw:rule \"#{rule_mash}\"" + rule_mash.keys.each do |rule| + Chef::Log.debug "ufw:rule:name \"#{rule}\"" + params = rule_mash[rule] + Chef::Log.debug "ufw:rule:parameters \"#{params}\"" + Chef::Log.debug "ufw:rule:name #{params['name']}" if params['name'] + Chef::Log.debug "ufw:rule:protocol #{params['protocol']}" if params['protocol'] + Chef::Log.debug "ufw:rule:direction #{params['direction']}" if params['direction'] + Chef::Log.debug "ufw:rule:interface #{params['interface']}" if params['interface'] + Chef::Log.debug "ufw:rule:logging #{params['logging']}" if params['logging'] + Chef::Log.debug "ufw:rule:port #{params['port']}" if params['port'] + Chef::Log.debug "ufw:rule:port_range #{params['port_range']}" if params['port_range'] + Chef::Log.debug "ufw:rule:source #{params['source']}" if params['source'] + Chef::Log.debug "ufw:rule:destination #{params['destination']}" if params['destination'] + Chef::Log.debug "ufw:rule:dest_port #{params['dest_port']}" if params['dest_port'] + Chef::Log.debug "ufw:rule:position #{params['position']}" if params['position'] + act = params['action'] + act ||= "allow" + raise "ufw: port_range was specified to firewall_rule without protocol" if params['port_range'] && !params['protocol'] + Chef::Log.debug "ufw:rule:action :#{act}" + firewall_rule rule do + name params['name'] if params['name'] + protocol params['protocol'].to_sym if params['protocol'] + direction params['direction'].to_sym if params['direction'] + interface params['interface'] if params['interface'] + logging params['logging'].to_sym if params['logging'] + port params['port'].to_i if params['port'] + if params['port_range'] + ends = params['port_range'].split('..').map{|d| Integer(d)} + port_range ends[0]..ends[1] + end + source params['source'] if params['source'] + destination params['destination'] if params['destination'] + dest_port params['dest_port'].to_i if params['dest_port'] + position params['position'].to_i if params['position'] + action act + end + end + end + +end diff --git a/cookbooks/ufw/recipes/disable.rb b/cookbooks/ufw/recipes/disable.rb new file mode 100644 index 0000000..132b696 --- /dev/null +++ b/cookbooks/ufw/recipes/disable.rb @@ -0,0 +1,23 @@ +# +# Author:: Matt Ray +# Cookbook Name:: ufw +# Recipe:: disable +# +# Copyright 2011, Opscode, Inc +# +# 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. +# + +firewall "ufw" do + action :disable +end diff --git a/cookbooks/ufw/recipes/recipes.rb b/cookbooks/ufw/recipes/recipes.rb new file mode 100644 index 0000000..d35188b --- /dev/null +++ b/cookbooks/ufw/recipes/recipes.rb @@ -0,0 +1,41 @@ +# +# Author:: Matt Ray +# Cookbook Name:: ufw +# Recipe:: recipes +# +# Copyright 2011, Opscode, Inc +# +# 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. +# + +# expand and parse the node's runlist for recipes and find attributes of the form node[]['firewall']['rules'] +# append them to the node['firewall']['rules'] array attribute +node.expand!.recipes.each do |recipe| + Chef::Log.debug "ufw::recipes: #{recipe}" + cookbook = recipe.split('::')[0] + #get the cookbook attributes if there are any + if recipe != cookbook and node[cookbook] and node[cookbook]['firewall'] and node[cookbook]['firewall']['rules'] + rules = node[cookbook]['firewall']['rules'] + Chef::Log.debug "ufw::recipes:#{cookbook}:rules #{rules}" + node.set['firewall']['rules'].concat(rules) unless rules.nil? + end + #get the recipe attributes if there are any + if node[recipe] and node[recipe]['firewall'] and node[recipe]['firewall']['rules'] + rules = node[recipe]['firewall']['rules'] + Chef::Log.debug "ufw::recipes:#{recipe}:rules #{rules}" + node.set['firewall']['rules'].concat(rules) unless rules.nil? + end +end + +#now go apply the rules +include_recipe "ufw::default" diff --git a/cookbooks/ufw/recipes/securitylevel.rb b/cookbooks/ufw/recipes/securitylevel.rb new file mode 100644 index 0000000..c642574 --- /dev/null +++ b/cookbooks/ufw/recipes/securitylevel.rb @@ -0,0 +1,41 @@ +# +# Author:: Matt Ray +# Cookbook Name:: ufw +# Recipe:: securitylevel +# +# Copyright 2011, Opscode, Inc +# +# 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. +# + +securitylevel = node['firewall']['securitylevel'] + +Chef::Log.info "ufw::securitylevel:#{securitylevel}" + +#verify that only 1 "color" security group is applied" +count = node.expand!.roles.count {|role| role =~ /SecurityLevel-(Red|Green|Yellow)/ } +if count > 1 + raise Chef::Exceptions::AmbiguousRunlistSpecification, "conflicting SecurityLevel-'color' roles, only 1 may be applied." +end + +case securitylevel +when 'red' + #put special stuff for red here +when 'yellow' + #put special stuff for red here +when 'green' + #put special stuff for red here +end + +#now go apply the rules +include_recipe "ufw::default" diff --git a/cookbooks/unattended-upgrades/CHANGELOG.md b/cookbooks/unattended-upgrades/CHANGELOG.md new file mode 100644 index 0000000..ff191d0 --- /dev/null +++ b/cookbooks/unattended-upgrades/CHANGELOG.md @@ -0,0 +1,22 @@ +unattended-upgrades +=================== + +v0.1.2 (2014-07-01) +------------------- + +Fixes + +* Fixed missing auto-upgrades.conf, preventing cron triggering these upgrades + +Changes + +* no longer installs mailutils - a warning will be emitted if a mailer can't be detected instead + +Features + +* Now with unit and integration tests + + +v0.1.0 (2014-05-08) +------------------- +- First officially published release. Ubuntu support tested on 12.04 diff --git a/cookbooks/unattended-upgrades/README.md b/cookbooks/unattended-upgrades/README.md new file mode 100644 index 0000000..783de80 --- /dev/null +++ b/cookbooks/unattended-upgrades/README.md @@ -0,0 +1,95 @@ +# unattended-upgrades cookbook + +This cookbook configures the unattended-upgrades package which performs automatic package updates on debian systems. + +Build status: + +[![Build Status](https://travis-ci.org/jeremyolliver/cookbook-unattended-upgrades.png?branch=master)](https://travis-ci.org/jeremyolliver/cookbook-unattended-upgrades) + +CI automatically runs linting and unit tests. You may also run more thorough integration tests via Vagrant as well. See below for details on how to do that. + +# Requirements + +Debian or Ubuntu Operating System and the `apt` cookbook + +# Usage + +Simply include the cookbook "unattended-upgrades". Common config that you may want to change: + +`node['unattended-upgrades']['admin_email']` Defaults to `'root@localhost'` Set to nil to disable email notification, or any other external email + +`node['unattended-upgrades']['allowed_origins']` + +Default value (at default precedence) is: + + { + 'security' => true, + 'updates' => false, + 'proposed' => false, + 'backports' => false + } + +You can change this to enable non-critical updates by setting in a role or environment: + + "default_attributes": { + "unattended-upgrades": { + "allowed_origins": { + "updates": true + } + } + } + +Please note that if you set your own changes at an `override` precedence, then the two hashes will not be merged together, and the full list should be specified again. e.g. alternately: + + "override_attributes": { + "unattended-upgrades": { + "allowed_origins": { + "security": true, + "updates": true, + "proposed": false, + "backports": false + } + } + } + +TODO: Third party PPA's are not yet supported in the allowed origins section + +`node['unattended-upgrades']['mail_only_on_error']` Set this to `true` if you want to skip mails for successful updates, however it can be helpful for troubleshooting to have a record of when packages were updated if you need to correlate when an error started occurring with the time packages were updated. + +`node['unattended-upgrades']['minimal_steps']` Set this to `true` if you expect to be able to reboot the server with minimal interruption and the updates might be running at the time. With this left on the default value of false, the server will wait for all updates to complete before shutting down. See the full attributes list and the comments in the template file for more information. This cookbook has strived to provide configurable attributes for as many options as possible to allow maximum flexibility. + +# Attributes + +* `['unattended-upgrades']['admin_email']` +* `['unattended-upgrades']['package_blacklist']` +* `['unattended-upgrades']['autofix_dpkg']` +* `['unattended-upgrades']['minimal_steps']` +* `['unattended-upgrades']['install_on_shutdown']` +* `['unattended-upgrades']['mail_only_on_error']` +* `['unattended-upgrades']['remove_unused_dependencies']` +* `['unattended-upgrades']['automatic_reboot']` +* `['unattended-upgrades']['download_limit']` +* `['unattended_upgrades']['update_package_lists_interval']` +* `['unattended_upgrades']['upgrade_interval']` +* `['unattended_upgrades']['download_upgradeable_interval']` +* `['unattended_upgrades']['autoclean_interval']` + +# Recipes + +`unattended-upgrades::default` + +# Cookbook Development + +Running the tests for this cookbook involves: + +Requires: +* ruby 1.9.2+ +* bundler (`gem install bundler` and `bundle install`) +* Vagrant 1.2+ (and Virtualbox) +* `vagrant plugin install vagrant-berkshelf` + +Run the lint tests via: `bundle exec rake style`. Run the full integration tests via: `bundle exec kitchen converge all` and `bundle exec kitchen verify all`. To remove the VM's `bundle exec kitchen destroy all` + +# Author + +Author:: Jeremy Olliver () diff --git a/cookbooks/unattended-upgrades/attributes/default.rb b/cookbooks/unattended-upgrades/attributes/default.rb new file mode 100644 index 0000000..0120756 --- /dev/null +++ b/cookbooks/unattended-upgrades/attributes/default.rb @@ -0,0 +1,24 @@ +default['unattended-upgrades']['admin_email'] = 'root@localhost' # Set to nil to disable, or override to another value +default['unattended-upgrades']['package_blacklist'] = [] +default['unattended-upgrades']['autofix_dpkg'] = true # Strongly advised not to change +default['unattended-upgrades']['minimal_steps'] = false # Set to true to split upgrade into steps making it easier to interrupt +default['unattended-upgrades']['install_on_shutdown'] = false +default['unattended-upgrades']['mail_only_on_error'] = false +default['unattended-upgrades']['remove_unused_dependencies'] = false +default['unattended-upgrades']['automatic_reboot'] = false +default['unattended-upgrades']['download_limit'] = nil # Set to Integer representing kb/sec limit + +default['unattended-upgrades']['allowed_origins'] = { + 'security' => true, + 'updates' => false, + 'proposed' => false, + 'backports' => false +} + +default['unattended-upgrades']['apt_recipe'] = 'default' + +# interval settings in days +default['unattended-upgrades']['update_package_lists_interval'] = 1 +default['unattended-upgrades']['upgrade_interval'] = 1 # In order for unattended upgrades to run at all, this must be set to an integer greater than or equal to 1 +default['unattended-upgrades']['download_upgradeable_interval'] = nil +default['unattended-upgrades']['autoclean_interval'] = nil diff --git a/cookbooks/unattended-upgrades/files/default/test/default_test.rb b/cookbooks/unattended-upgrades/files/default/test/default_test.rb new file mode 100644 index 0000000..c5cf68c2 --- /dev/null +++ b/cookbooks/unattended-upgrades/files/default/test/default_test.rb @@ -0,0 +1,45 @@ +require File.expand_path('../support/helpers', __FILE__) + +describe_recipe 'unattended-upgrades::default' do + + include Helpers::Unattended_upgrades + + describe 'packages' do + it 'installs unattended-upgrades' do + package("unattended-upgrades").must_be_installed + end + end + + describe 'files' do + let(:config) { file("/etc/apt/apt.conf.d/50unattended-upgrades") } + let(:autoconfig) { file("/etc/apt/apt.conf.d/20auto-upgrades") } + + it 'should have correct file permissions' do + config.must_have(:mode, "644") + autoconfig.must_have(:mode, "644") + end + it 'should have correct owner' do + config.must_have(:owner, "root") + autoconfig.must_have(:owner, "root") + end + it 'should have correct group' do + config.must_have(:group, "root") + autoconfig.must_have(:group, "root") + end + + it 'should contain the correct config' do + config.must_include "Unattended-Upgrade::Mail \"#{node['unattended-upgrades']['admin_email']}\";" + end + + it 'should contain the security updates origin' do + # Although this test may fail on a setup with minitest-handler running on a live server - security updates really shouldn't be turned off + config.must_include '"${distro_id}:${distro_codename}-security";' + end + + it 'should run unattended upgrades according to the schedule' do + # Test might fail if unattended upgrades is disabled via run interval setting - but why run this test if the software is turned off? + autoconfig.must_include "APT::Periodic::Unattended-Upgrade \"#{node['unattended-upgrades']['upgrade_interval']}\";" + end + end + +end diff --git a/cookbooks/unattended-upgrades/files/default/test/support/helpers.rb b/cookbooks/unattended-upgrades/files/default/test/support/helpers.rb new file mode 100644 index 0000000..aa8a189 --- /dev/null +++ b/cookbooks/unattended-upgrades/files/default/test/support/helpers.rb @@ -0,0 +1,9 @@ +require 'minitest/spec' + +module Helpers + module Unattended_upgrades + include MiniTest::Chef::Assertions + include MiniTest::Chef::Context + include MiniTest::Chef::Resources + end +end diff --git a/cookbooks/unattended-upgrades/metadata.json b/cookbooks/unattended-upgrades/metadata.json new file mode 100644 index 0000000..5464d7e --- /dev/null +++ b/cookbooks/unattended-upgrades/metadata.json @@ -0,0 +1,32 @@ +{ + "name": "unattended-upgrades", + "version": "0.1.2", + "description": "Installs/Configures unattended-upgrades", + "long_description": "# unattended-upgrades cookbook\n\nThis cookbook configures the unattended-upgrades package which performs automatic package updates on debian systems.\n\nBuild status:\n\n[![Build Status](https://travis-ci.org/jeremyolliver/cookbook-unattended-upgrades.png?branch=master)](https://travis-ci.org/jeremyolliver/cookbook-unattended-upgrades)\n\nCI automatically runs linting and unit tests. You may also run more thorough integration tests via Vagrant as well. See below for details on how to do that.\n\n# Requirements\n\nDebian or Ubuntu Operating System and the `apt` cookbook\n\n# Usage\n\nSimply include the cookbook \"unattended-upgrades\". Common config that you may want to change:\n\n`node['unattended-upgrades']['admin_email']` Defaults to `'root@localhost'` Set to nil to disable email notification, or any other external email\n\n`node['unattended-upgrades']['allowed_origins']`\n\nDefault value (at default precedence) is:\n\n {\n 'security' => true,\n 'updates' => false,\n 'proposed' => false,\n 'backports' => false\n }\n\nYou can change this to enable non-critical updates by setting in a role or environment:\n\n \"default_attributes\": {\n \"unattended-upgrades\": {\n \"allowed_origins\": {\n \"updates\": true\n }\n }\n }\n\nPlease note that if you set your own changes at an `override` precedence, then the two hashes will not be merged together, and the full list should be specified again. e.g. alternately:\n\n \"override_attributes\": {\n \"unattended-upgrades\": {\n \"allowed_origins\": {\n \"security\": true,\n \"updates\": true,\n \"proposed\": false,\n \"backports\": false\n }\n }\n }\n\nTODO: Third party PPA's are not yet supported in the allowed origins section\n\n`node['unattended-upgrades']['mail_only_on_error']` Set this to `true` if you want to skip mails for successful updates, however it can be helpful for troubleshooting to have a record of when packages were updated if you need to correlate when an error started occurring with the time packages were updated.\n\n`node['unattended-upgrades']['minimal_steps']` Set this to `true` if you expect to be able to reboot the server with minimal interruption and the updates might be running at the time. With this left on the default value of false, the server will wait for all updates to complete before shutting down. See the full attributes list and the comments in the template file for more information. This cookbook has strived to provide configurable attributes for as many options as possible to allow maximum flexibility.\n\n# Attributes\n\n* `['unattended-upgrades']['admin_email']`\n* `['unattended-upgrades']['package_blacklist']`\n* `['unattended-upgrades']['autofix_dpkg']`\n* `['unattended-upgrades']['minimal_steps']`\n* `['unattended-upgrades']['install_on_shutdown']`\n* `['unattended-upgrades']['mail_only_on_error']`\n* `['unattended-upgrades']['remove_unused_dependencies']`\n* `['unattended-upgrades']['automatic_reboot']`\n* `['unattended-upgrades']['download_limit']`\n* `['unattended_upgrades']['update_package_lists_interval']`\n* `['unattended_upgrades']['upgrade_interval']`\n* `['unattended_upgrades']['download_upgradeable_interval']`\n* `['unattended_upgrades']['autoclean_interval']`\n\n# Recipes\n\n`unattended-upgrades::default`\n\n# Cookbook Development\n\nRunning the tests for this cookbook involves:\n\nRequires:\n* ruby 1.9.2+\n* bundler (`gem install bundler` and `bundle install`)\n* Vagrant 1.2+ (and Virtualbox)\n* `vagrant plugin install vagrant-berkshelf`\n\nRun the lint tests via: `bundle exec rake style`. Run the full integration tests via: `bundle exec kitchen converge all` and `bundle exec kitchen verify all`. To remove the VM's `bundle exec kitchen destroy all`\n\n# Author\n\nAuthor:: Jeremy Olliver ()\n", + "maintainer": "Jeremy Olliver", + "maintainer_email": "jeremy.olliver@gmail.com", + "license": "Apache 2.0", + "platforms": { + "ubuntu": ">= 0.0.0" + }, + "dependencies": { + "apt": ">= 0.0.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + "unattended-upgrades::default": ">= 0.0.0" + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + } +} \ No newline at end of file diff --git a/cookbooks/unattended-upgrades/metadata.rb b/cookbooks/unattended-upgrades/metadata.rb new file mode 100644 index 0000000..064ce32 --- /dev/null +++ b/cookbooks/unattended-upgrades/metadata.rb @@ -0,0 +1,14 @@ +name "unattended-upgrades" +maintainer "Jeremy Olliver" +maintainer_email "jeremy.olliver@gmail.com" +license "Apache 2.0" +description "Installs/Configures unattended-upgrades" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version "0.1.2" + +# supports "debian" # Untested +supports "ubuntu" + +depends "apt" + +provides "unattended-upgrades::default" diff --git a/cookbooks/unattended-upgrades/recipes/default.rb b/cookbooks/unattended-upgrades/recipes/default.rb new file mode 100644 index 0000000..daa551a --- /dev/null +++ b/cookbooks/unattended-upgrades/recipes/default.rb @@ -0,0 +1,63 @@ +# +# Cookbook Name:: unattended-upgrades +# Recipe:: default +# +# Copyright (C) 2013 Jeremy Olliver +# +# 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. +# + +# include apt::default (or an alternate apt recipe) +include_recipe "apt::#{node['unattended-upgrades']['apt_recipe']}" + +package 'unattended-upgrades' + +# Stock systems should already have a compatible mail delivery mechanism (e.g. mailx binary) installed - warn if one is not detected +ruby_block 'warn-on-missing-mailer' do + block do + Chef::Log.warn("No mail package detected. If you want to be able to mail the output of unattended-upgrades, you should a package provides the `mailx` such as 'mailutils' or 'heirloom-mailx'") + end + not_if 'which mailx' +end + +template '/etc/apt/apt.conf.d/50unattended-upgrades' do + source 'unattended-upgrades.conf.erb' + owner 'root' + group 'root' + mode '0644' + variables( + :allowed_origins => node['unattended-upgrades']['allowed_origins'], + :package_blacklist => node['unattended-upgrades']['package_blacklist'], + :autofix_dpkg => node['unattended-upgrades']['autofix_dpkg'], + :minimal_steps => node['unattended-upgrades']['minimal_steps'], + :install_on_shutdown => node['unattended-upgrades']['install_on_shutdown'], + :admin_email => node['unattended-upgrades']['admin_email'], + :mail_only_on_error => node['unattended-upgrades']['mail_only_on_error'], + :remove_unused_dependencies => node['unattended-upgrades']['remove_unused_dependencies'], + :automatic_reboot => node['unattended-upgrades']['automatic_reboot'], + :download_limit => node['unattended-upgrades']['download_limit'] + ) +end + +template '/etc/apt/apt.conf.d/20auto-upgrades' do + source 'auto-upgrades.conf.erb' + owner 'root' + group 'root' + mode '0644' + variables( + :update_package_lists_interval => node['unattended-upgrades']['update_package_lists_interval'], + :upgrade_interval => node['unattended-upgrades']['upgrade_interval'], + :download_upgradeable_interval => node['unattended-upgrades']['download_upgradeable_interval'], + :autoclean_interval => node['unattended-upgrades']['autoclean_interval'], + ) +end diff --git a/cookbooks/unattended-upgrades/templates/default/auto-upgrades.conf.erb b/cookbooks/unattended-upgrades/templates/default/auto-upgrades.conf.erb new file mode 100644 index 0000000..57f8aa0 --- /dev/null +++ b/cookbooks/unattended-upgrades/templates/default/auto-upgrades.conf.erb @@ -0,0 +1,4 @@ +<% if @update_package_lists_interval -%>APT::Periodic::Update-Package-Lists "<%= @update_package_lists_interval %>";<% end -%> +<% if @upgrade_interval -%>APT::Periodic::Unattended-Upgrade "<%= @upgrade_interval %>";<% end -%> +<% if @download_upgradeable_interval -%>APT::Periodic::Download-Upgradeable-Packages "<%= @download_upgradeable_interval %>";<% end -%> +<% if @autoclean_interval -%>APT::Periodic::AutocleanInterval "<%= @autoclean_interval%>";<% end -%> diff --git a/cookbooks/unattended-upgrades/templates/default/unattended-upgrades.conf.erb b/cookbooks/unattended-upgrades/templates/default/unattended-upgrades.conf.erb new file mode 100644 index 0000000..09ff914 --- /dev/null +++ b/cookbooks/unattended-upgrades/templates/default/unattended-upgrades.conf.erb @@ -0,0 +1,67 @@ +// File configured by chef - don't edit manually + +// Automatically upgrade packages from these (origin:archive) pairs +Unattended-Upgrade::Allowed-Origins { +<% @allowed_origins.each do |origin, enabled| %> +<%= '//' unless enabled %> "${distro_id}:${distro_codename}-<%= origin %>"; +<% end %> +}; + +// List of packages to not update +Unattended-Upgrade::Package-Blacklist { +// "vim"; +// "libc6"; +// "libc6-dev"; +// "libc6-i686"; +<% @package_blacklist.each do |pkg| %> + "<%= pkg %>"; +<% end %> +}; + +// This option allows you to control if on a unclean dpkg exit +// unattended-upgrades will automatically run +// dpkg --force-confold --configure -a +// The default is true, to ensure updates keep getting installed +//Unattended-Upgrade::AutoFixInterruptedDpkg "false"; +Unattended-Upgrade::AutoFixInterruptedDpkg "<%= @autofix_dpkg %>"; + +// Split the upgrade into the smallest possible chunks so that +// they can be interrupted with SIGUSR1. This makes the upgrade +// a bit slower but it has the benefit that shutdown while a upgrade +// is running is possible (with a small delay) +//Unattended-Upgrade::MinimalSteps "true"; +Unattended-Upgrade::MinimalSteps "<%= @minimal_steps %>"; + +// Install all unattended-upgrades when the machine is shuting down +// instead of doing it in the background while the machine is running +// This will (obviously) make shutdown slower +//Unattended-Upgrade::InstallOnShutdown "true"; +Unattended-Upgrade::InstallOnShutdown "<%= @install_on_shutdown %>"; + +// Send email to this address for problems or packages upgrades +// If empty or unset then no email is sent, make sure that you +// have a working mail setup on your system. A package that provides +// 'mailx' must be installed. +//Unattended-Upgrade::Mail "root@localhost"; +<% if @admin_email %>Unattended-Upgrade::Mail "<%= @admin_email %>";<% end %> + +// Set this value to "true" to get emails only on errors. Default +// is to always send a mail if Unattended-Upgrade::Mail is set +//Unattended-Upgrade::MailOnlyOnError "true"; +Unattended-Upgrade::MailOnlyOnError "<%= @mail_only_on_error %>"; + +// Do automatic removal of new unused dependencies after the upgrade +// (equivalent to apt-get autoremove) +//Unattended-Upgrade::Remove-Unused-Dependencies "false"; +Unattended-Upgrade::Remove-Unused-Dependencies "<%= @remove_unused_dependencies %>"; + +// Automatically reboot *WITHOUT CONFIRMATION* if a +// the file /var/run/reboot-required is found after the upgrade +//Unattended-Upgrade::Automatic-Reboot "false"; +Unattended-Upgrade::Automatic-Reboot "<%= @automatic_reboot %>"; + + +// Use apt bandwidth limit feature, this example limits the download +// speed to 70kb/sec +//Acquire::http::Dl-Limit "70"; +<% if @download_limit %>Acquire::http::Dl-Limit "<%= @download_limit %>";<% end %> diff --git a/cookbooks/users/CHANGELOG.md b/cookbooks/users/CHANGELOG.md new file mode 100644 index 0000000..f812933 --- /dev/null +++ b/cookbooks/users/CHANGELOG.md @@ -0,0 +1,77 @@ +users Cookbook CHANGELOG +======================== +This file is used to list changes made in each version of the users cookbook. + +v1.8.2 (2015-03-18) +------------------- +- No changes, just republishing 1.8.1 + +v1.8.1 (2015-03-12) +------------------- +- Add `source_url` and `issues_url` to the metadata.rb so Supermarket can display +appropriate links + +v1.8.0 (2015-03-09) +------------------- +- Expose LWRP state attributes +- [COOK-4401] - Add unit tests with ChefSpec +- [COOK-4404] - Determine file system and add manage_nfs_home_dirs attribute to disable +managing NFS mounted home directories +- Remove `converge_by` when creating home directory, the directory resource +already handles this +- Do not manage home directory if the path does not exist +- Add integration with TravisCI +- "Opscode" to "Chef" replacements +- Retire unsupported Ruby 1.9.3 and add Ruby 2.2 to the Travis integration tests +- Updates for RSpec 3 + +v1.7.0 (2014-02-14) +------------------- +- [COOK-4139] - users_manage resource always notifies +- [COOK-4078] - users cookbook fails in why-run mode for .ssh directory +- [COOK-3959] - Add support for Mac OS X to users cookbook + + +v1.6.0 +------ +### Bug +- **[COOK-3744](https://tickets.opscode.com/browse/COOK-3744)** - Allow passing an action option via the `data_bag` to the user resource + + +v1.5.2 +------ +### Bug +- **[COOK-3215](https://tickets.opscode.com/browse/COOK-3215)** - Make `group_id` optional + +v1.5.0 +------ +- [COOK-2427] - Mistakenly released instead of sudo :-). + +v1.4.0 +------ +- [COOK-2479] - Permit users cookbook to work with chef-solo if edelight/chef-solo-search is installed +- [COOK-2486] - specify precedence when setting node attribute + +v1.3.0 +------ +- [COOK-1842] - allow specifying private SSH keys +- [COOK-2021] - Empty default recipe for including users LWRPs + +v1.2.0 +------ +- [COOK-1398] - Provider manage.rb ignores username attribute +- [COOK-1582] - ssh_keys should take an array in addition to a string separated by new lines + +v1.1.4 +------ +- [COOK-1396] - removed users get recreated +- [COOK-1433] - resolve foodcritic warnings +- [COOK-1583] - set passwords for users + +v1.1.2 +------ +- [COOK-1076] - authorized_keys template not found in another cookbook + +v1.1.0 +------ +- [COOK-623] - LWRP conversion diff --git a/cookbooks/users/README.md b/cookbooks/users/README.md new file mode 100644 index 0000000..8fef5b4 --- /dev/null +++ b/cookbooks/users/README.md @@ -0,0 +1,193 @@ +users Cookbook +============== +![Build Status](https://travis-ci.org/opscode-cookbooks/users.svg?branch=master) + +Creates users from a databag search. + + +Requirements +------------ +### Platforms +- Debian, Ubuntu +- CentOS, Red Hat, Fedora +- FreeBSD + +A data bag populated with user objects must exist. The default data +bag in this recipe is `users`. See USAGE. + + +Usage +----- +To include just the LWRPs in your cookbook, use: + +```ruby +include_recipe "users" +``` + +Otherwise, this cookbook is specific for setting up `sysadmin` group and users with the sysadmins recipe for now. + +```ruby +include_recipe "users::sysadmins" +``` + +Use knife to create a data bag for users. + +```bash +$ knife data bag create users +``` + +Create a user in the data_bag/users/ directory. + +When using an [Omnibus ruby](http://tickets.opscode.com/browse/CHEF-2848), one can specify an optional password hash. This will be used as the user's password. + +The hash can be generated with the following command. + +```bash +$ openssl passwd -1 "plaintextpassword" +``` + +Note: The ssh_keys attribute below can be either a String or an Array. However, we are recommending the use of an Array. + +```javascript +{ + "id": "bofh", + "ssh_keys": "ssh-rsa AAAAB3Nz...yhCw== bofh", +} +``` + +```javascript +{ + "id": "bofh", + "password": "$1$d...HgH0", + "ssh_keys": [ + "ssh-rsa AAA123...xyz== foo", + "ssh-rsa AAA456...uvw== bar" + ], + "groups": [ "sysadmin", "dba", "devops" ], + "uid": 2001, + "shell": "\/bin\/bash", + "comment": "BOFH", + "nagios": { + "pager": "8005551212@txt.att.net", + "email": "bofh@example.com" + }, + "openid": "bofh.myopenid.com" +} +``` + +You can pass any action listed in the [user](http://docs.chef.io/chef/resources.html#user) resource for Chef via the "action" option. For Example: + +Lock a user, johndoe1. + +```bash +$ knife data bag edit users johndoe1 +``` + +And then change the action to "lock": + +```javascript +{ + "id": "johndoe1", + "groups": ["sysadmin", "dba", "devops"], + "uid": 2002, + "action": "lock", // <-- + "comment": "User violated access policy" +} +``` + +Remove a user, johndoe1. + +```bash +$ knife data bag edit users johndoe1 +``` + +And then change the action to "remove": + +```javascript +{ + "id": "johndoe1", + "groups": [ "sysadmin", "dba", "devops" ], + "uid": 2002, + "action": "remove", // <-- + "comment": "User quit, retired, or fired." +} +``` + +* Note only user bags with the "action : remove" and a search-able "group" attribute will be purged by the :remove action. + +The sysadmins recipe makes use of the `users_manage` Lightweight Resource Provider (LWRP), and looks like this: + +```ruby +users_manage "sysadmin" do + group_id 2300 + action [ :remove, :create ] +end +``` + +Note this LWRP searches the `users` data bag for the `sysadmin` group attribute, and adds those users to a Unix security group `sysadmin`. The only required attribute is group_id, which represents the numeric Unix gid and *must* be unique. The default action for the LWRP is `:create` only. + +If you have different requirements, for example: + + * You want to search a different data bag specific to a role such as + mail. You may change the data_bag searched. + - data_bag `mail` + * You want to search for a different group attribute named + `postmaster`. You may change the search_group attribute. This + attribute defaults to the LWRP resource name. + - search_group `postmaster` + * You want to add the users to a security group other than the + lightweight resource name. You may change the group_name attribute. + This attribute also defaults to the LWRP resource name. + - group_name `wheel` + +Putting these requirements together our recipe might look like this: + +```ruby +users_manage "postmaster" do + data_bag "mail" + group_name "wheel" + group_id 10 +end +``` + +The latest version of knife supports reading data bags from a file and automatically looks in a directory called +data_bags+ in the current directory. The "bag" should be a directory with JSON files of each item. For the above: + +```bash +$ mkdir data_bags/users +$EDITOR data_bags/users/bofh.json +``` + +Paste the user's public SSH key into the ssh_keys value. Also make sure the uid is unique, and if you're not using bash, that the shell is installed. The default search, and Unix group is sysadmin. + +The recipe, by default, will also create the sysadmin group. If you're using the chef sudo cookbook, they'll have sudo access in the default site-cookbooks template. They won't have passwords though, so the sudo cookbook's template needs to be adjusted so the sysadmin group has NOPASSWD. + +The sysadmin group will be created with GID 2300. This may become an attribute at a later date. + +The Apache cookbook can set up authentication using OpenIDs, which is set up using the openid key here. See the Chef Software 'apache2' cookbook for more information about this. + + +Chef Solo +--------- +As of version 1.4.0, this cookbook might work with Chef Solo when using [chef-solo-search by edelight](https://github.com/edelight/chef-solo-search). That cookbook is not a dependency of this one as Chef solo doesn't support dependency resolution using cookbook metadata - all cookbooks must be provided to the node manually when using Chef Solo. + + +License & Authors +----------------- +- Author:: Joshua Timberman () +- Author:: Seth Chisamore () + +```text +Copyright:: 2009-2015, Chef Software, Inc + +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/cookbooks/users/libraries/helpers.rb b/cookbooks/users/libraries/helpers.rb new file mode 100644 index 0000000..ae46a6d --- /dev/null +++ b/cookbooks/users/libraries/helpers.rb @@ -0,0 +1,29 @@ +require 'mixlib/shellout' + +module Users + # Helpers for Users + module Helpers + # Checks fs type. + # + # @return [String] + def fs_type(mount) + begin + # Doesn't support macosx + stat = Mixlib::ShellOut.new("stat -f -L -c %T #{mount} 2>&1").run_command + stat.stdout.chomp + rescue + 'none' + end + end + + # Determines if provided mount point is remote. + # + # @return [Boolean] + def fs_remote?(mount) + fs_type(mount) == 'nfs' ? true : false + end + end +end + +Chef::Resource.send(:include, ::Users::Helpers) +Chef::Provider.send(:include, ::Users::Helpers) diff --git a/cookbooks/users/libraries/matchers.rb b/cookbooks/users/libraries/matchers.rb new file mode 100644 index 0000000..1070fd7 --- /dev/null +++ b/cookbooks/users/libraries/matchers.rb @@ -0,0 +1,15 @@ +# Matchers for chefspec 3 + +if defined?(ChefSpec) + def create_users_manage(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:users_manage, + :create, + resource_name) + end + + def remove_users_manage(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:users_manage, + :remove, + resource_name) + end +end diff --git a/cookbooks/users/metadata.json b/cookbooks/users/metadata.json new file mode 100644 index 0000000..d62a9d2 --- /dev/null +++ b/cookbooks/users/metadata.json @@ -0,0 +1,40 @@ +{ + "name": "users", + "version": "1.8.2", + "description": "Creates users from a databag search", + "long_description": "users Cookbook\n==============\n![Build Status](https://travis-ci.org/opscode-cookbooks/users.svg?branch=master)\n\nCreates users from a databag search.\n\n\nRequirements\n------------\n### Platforms\n- Debian, Ubuntu\n- CentOS, Red Hat, Fedora\n- FreeBSD\n\nA data bag populated with user objects must exist. The default data\nbag in this recipe is `users`. See USAGE.\n\n\nUsage\n-----\nTo include just the LWRPs in your cookbook, use:\n\n```ruby\ninclude_recipe \"users\"\n```\n\nOtherwise, this cookbook is specific for setting up `sysadmin` group and users with the sysadmins recipe for now.\n\n```ruby\ninclude_recipe \"users::sysadmins\"\n```\n\nUse knife to create a data bag for users.\n\n```bash\n$ knife data bag create users\n```\n\nCreate a user in the data_bag/users/ directory.\n\nWhen using an [Omnibus ruby](http://tickets.opscode.com/browse/CHEF-2848), one can specify an optional password hash. This will be used as the user's password.\n\nThe hash can be generated with the following command.\n\n```bash\n$ openssl passwd -1 \"plaintextpassword\"\n```\n\nNote: The ssh_keys attribute below can be either a String or an Array. However, we are recommending the use of an Array.\n\n```javascript\n{\n \"id\": \"bofh\",\n \"ssh_keys\": \"ssh-rsa AAAAB3Nz...yhCw== bofh\",\n}\n```\n\n```javascript\n{\n \"id\": \"bofh\",\n \"password\": \"$1$d...HgH0\",\n \"ssh_keys\": [\n \"ssh-rsa AAA123...xyz== foo\",\n \"ssh-rsa AAA456...uvw== bar\"\n ],\n \"groups\": [ \"sysadmin\", \"dba\", \"devops\" ],\n \"uid\": 2001,\n \"shell\": \"\\/bin\\/bash\",\n \"comment\": \"BOFH\",\n \"nagios\": {\n \"pager\": \"8005551212@txt.att.net\",\n \"email\": \"bofh@example.com\"\n },\n \"openid\": \"bofh.myopenid.com\"\n}\n```\n\nYou can pass any action listed in the [user](http://docs.chef.io/chef/resources.html#user) resource for Chef via the \"action\" option. For Example:\n\nLock a user, johndoe1.\n\n```bash\n$ knife data bag edit users johndoe1\n```\n\nAnd then change the action to \"lock\":\n\n```javascript\n{\n \"id\": \"johndoe1\",\n \"groups\": [\"sysadmin\", \"dba\", \"devops\"],\n \"uid\": 2002,\n \"action\": \"lock\", // <--\n \"comment\": \"User violated access policy\"\n}\n```\n\nRemove a user, johndoe1.\n\n```bash\n$ knife data bag edit users johndoe1\n```\n\nAnd then change the action to \"remove\":\n\n```javascript\n{\n \"id\": \"johndoe1\",\n \"groups\": [ \"sysadmin\", \"dba\", \"devops\" ],\n \"uid\": 2002,\n \"action\": \"remove\", // <--\n \"comment\": \"User quit, retired, or fired.\"\n}\n```\n\n* Note only user bags with the \"action : remove\" and a search-able \"group\" attribute will be purged by the :remove action.\n\nThe sysadmins recipe makes use of the `users_manage` Lightweight Resource Provider (LWRP), and looks like this:\n\n```ruby\nusers_manage \"sysadmin\" do\n group_id 2300\n action [ :remove, :create ]\nend\n```\n\nNote this LWRP searches the `users` data bag for the `sysadmin` group attribute, and adds those users to a Unix security group `sysadmin`. The only required attribute is group_id, which represents the numeric Unix gid and *must* be unique. The default action for the LWRP is `:create` only.\n\nIf you have different requirements, for example:\n\n * You want to search a different data bag specific to a role such as\n mail. You may change the data_bag searched.\n - data_bag `mail`\n * You want to search for a different group attribute named\n `postmaster`. You may change the search_group attribute. This\n attribute defaults to the LWRP resource name.\n - search_group `postmaster`\n * You want to add the users to a security group other than the\n lightweight resource name. You may change the group_name attribute.\n This attribute also defaults to the LWRP resource name.\n - group_name `wheel`\n\nPutting these requirements together our recipe might look like this:\n\n```ruby\nusers_manage \"postmaster\" do\n data_bag \"mail\"\n group_name \"wheel\"\n group_id 10\nend\n```\n\nThe latest version of knife supports reading data bags from a file and automatically looks in a directory called +data_bags+ in the current directory. The \"bag\" should be a directory with JSON files of each item. For the above:\n\n```bash\n$ mkdir data_bags/users\n$EDITOR data_bags/users/bofh.json\n```\n\nPaste the user's public SSH key into the ssh_keys value. Also make sure the uid is unique, and if you're not using bash, that the shell is installed. The default search, and Unix group is sysadmin.\n\nThe recipe, by default, will also create the sysadmin group. If you're using the chef sudo cookbook, they'll have sudo access in the default site-cookbooks template. They won't have passwords though, so the sudo cookbook's template needs to be adjusted so the sysadmin group has NOPASSWD.\n\nThe sysadmin group will be created with GID 2300. This may become an attribute at a later date.\n\nThe Apache cookbook can set up authentication using OpenIDs, which is set up using the openid key here. See the Chef Software 'apache2' cookbook for more information about this.\n\n\nChef Solo\n---------\nAs of version 1.4.0, this cookbook might work with Chef Solo when using [chef-solo-search by edelight](https://github.com/edelight/chef-solo-search). That cookbook is not a dependency of this one as Chef solo doesn't support dependency resolution using cookbook metadata - all cookbooks must be provided to the node manually when using Chef Solo.\n\n\nLicense & Authors\n-----------------\n- Author:: Joshua Timberman ()\n- Author:: Seth Chisamore ()\n\n```text\nCopyright:: 2009-2015, Chef Software, Inc\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "source_url": "https://github.com/opscode-cookbooks/users", + "issues_url": "https://github.com/opscode-cookbooks/users/issues", + "maintainer": "Chef Software, Inc.", + "maintainer_email": "cookbooks@chef.io", + "license": "Apache 2.0", + "platforms": { + "ubuntu": ">= 0.0.0", + "debian": ">= 0.0.0", + "redhat": ">= 0.0.0", + "centos": ">= 0.0.0", + "fedora": ">= 0.0.0", + "freebsd": ">= 0.0.0", + "mac_os_x": ">= 0.0.0" + }, + "dependencies": { + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + "users": "Empty recipe for including LWRPs", + "users::sysadmins": "Create and manage sysadmin group" + } +} \ No newline at end of file diff --git a/cookbooks/users/providers/manage.rb b/cookbooks/users/providers/manage.rb new file mode 100644 index 0000000..2132549 --- /dev/null +++ b/cookbooks/users/providers/manage.rb @@ -0,0 +1,180 @@ +# +# Cookbook Name:: users +# Provider:: manage +# +# Copyright 2011, Eric G. Wolfe +# Copyright 2009-2011, Chef Software, Inc. +# +# 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. +# + +use_inline_resources if defined?(use_inline_resources) + +def whyrun_supported? + true +end + +def initialize(*args) + super + @action = :create +end + +def chef_solo_search_installed? + klass = ::Search::const_get('Helper') + return klass.is_a?(Class) +rescue NameError + return false +end + +action :remove do + if Chef::Config[:solo] and not chef_solo_search_installed? + Chef::Log.warn("This recipe uses search. Chef Solo does not support search unless you install the chef-solo-search cookbook.") + else + search(new_resource.data_bag, "groups:#{new_resource.search_group} AND action:remove") do |rm_user| + user rm_user['username'] ||= rm_user['id'] do + action :remove + end + end + end +end + +action :create do + security_group = Array.new + + if Chef::Config[:solo] and not chef_solo_search_installed? + Chef::Log.warn("This recipe uses search. Chef Solo does not support search unless you install the chef-solo-search cookbook.") + else + search(new_resource.data_bag, "groups:#{new_resource.search_group} AND NOT action:remove") do |u| + u['username'] ||= u['id'] + security_group << u['username'] + + if node['apache'] and node['apache']['allowed_openids'] + Array(u['openid']).compact.each do |oid| + node.default['apache']['allowed_openids'] << oid unless node['apache']['allowed_openids'].include?(oid) + end + end + + # Set home_basedir based on platform_family + case node['platform_family'] + when 'mac_os_x' + home_basedir = '/Users' + when 'debian', 'rhel', 'fedora', 'arch', 'suse', 'freebsd' + home_basedir = '/home' + end + + # Set home to location in data bag, + # or a reasonable default ($home_basedir/$user). + if u['home'] + home_dir = u['home'] + else + home_dir = "#{home_basedir}/#{u['username']}" + end + + # The user block will fail if the group does not yet exist. + # See the -g option limitations in man 8 useradd for an explanation. + # This should correct that without breaking functionality. + if u['gid'] and u['gid'].kind_of?(Numeric) + group u['username'] do + gid u['gid'] + end + end + + # Create user object. + # Do NOT try to manage null home directories. + user u['username'] do + uid u['uid'] + if u['gid'] + gid u['gid'] + end + shell u['shell'] + comment u['comment'] + password u['password'] if u['password'] + if home_dir == "/dev/null" + supports :manage_home => false + else + supports :manage_home => true + end + home home_dir + action u['action'] if u['action'] + end + + if manage_home_files?(home_dir, u['username']) + Chef::Log.debug("Managing home files for #{u['username']}") + + directory "#{home_dir}/.ssh" do + owner u['username'] + group u['gid'] || u['username'] + mode "0700" + end + + if u['ssh_keys'] + template "#{home_dir}/.ssh/authorized_keys" do + source "authorized_keys.erb" + cookbook new_resource.cookbook + owner u['username'] + group u['gid'] || u['username'] + mode "0600" + variables :ssh_keys => u['ssh_keys'] + end + end + + if u['ssh_private_key'] + key_type = u['ssh_private_key'].include?("BEGIN RSA PRIVATE KEY") ? "rsa" : "dsa" + template "#{home_dir}/.ssh/id_#{key_type}" do + source "private_key.erb" + cookbook new_resource.cookbook + owner u['id'] + group u['gid'] || u['id'] + mode "0400" + variables :private_key => u['ssh_private_key'] + end + end + + if u['ssh_public_key'] + key_type = u['ssh_public_key'].include?("ssh-rsa") ? "rsa" : "dsa" + template "#{home_dir}/.ssh/id_#{key_type}.pub" do + source "public_key.pub.erb" + cookbook new_resource.cookbook + owner u['id'] + group u['gid'] || u['id'] + mode "0400" + variables :public_key => u['ssh_public_key'] + end + end + else + Chef::Log.debug("Not managing home files for #{u['username']}") + end + end + end + + group new_resource.group_name do + if new_resource.group_id + gid new_resource.group_id + end + members security_group + end +end + +private + +def manage_home_files?(home_dir, user) + # Don't manage home dir if it's NFS mount + # and manage_nfs_home_dirs is disabled + if home_dir == "/dev/null" + false + elsif fs_remote?(home_dir) + new_resource.manage_nfs_home_dirs ? true : false + else + true + end +end diff --git a/cookbooks/users/recipes/default.rb b/cookbooks/users/recipes/default.rb new file mode 100644 index 0000000..7c4e78d --- /dev/null +++ b/cookbooks/users/recipes/default.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: users +# Recipe:: default +# +# Copyright 2009-2012, Chef Software, Inc. +# +# 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. +# + +# Empty default recipe for including LWRPs. diff --git a/cookbooks/users/recipes/sysadmins.rb b/cookbooks/users/recipes/sysadmins.rb new file mode 100644 index 0000000..8e65053 --- /dev/null +++ b/cookbooks/users/recipes/sysadmins.rb @@ -0,0 +1,26 @@ +# +# Cookbook Name:: users +# Recipe:: sysadmins +# +# Copyright 2011, Eric G. Wolfe +# Copyright 2009-2011, Chef Software, Inc. +# +# 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. +# + +# Searches data bag "users" for groups attribute "sysadmin". +# Places returned users in Unix group "sysadmin" with GID 2300. +users_manage "sysadmin" do + group_id 2300 + action [ :remove, :create ] +end diff --git a/cookbooks/users/resources/manage.rb b/cookbooks/users/resources/manage.rb new file mode 100644 index 0000000..a74c84b --- /dev/null +++ b/cookbooks/users/resources/manage.rb @@ -0,0 +1,44 @@ +# +# Cookbook Name:: users +# Resources:: manage +# +# Copyright 2011, Eric G. Wolfe +# +# 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. +# + +# Data bag user object needs an "action": "remove" tag to actually be removed by the action. +actions :create, :remove + +state_attrs :cookbook, + :data_bag, + :group_id, + :group_name, + :search_group + +# :data_bag is the object to search +# :search_group is the groups name to search for, defaults to resource name +# :group_name is the string name of the group to create, defaults to resource name +# :group_id is the numeric id of the group to create, default is to allow the OS to pick next +# :cookbook is the name of the cookbook that the authorized_keys template should be found in +attribute :data_bag, :kind_of => String, :default => "users" +attribute :search_group, :kind_of => String, :name_attribute => true +attribute :group_name, :kind_of => String, :name_attribute => true +attribute :group_id, :kind_of => Integer +attribute :cookbook, :kind_of => String, :default => "users" +attribute :manage_nfs_home_dirs, :kind_of => [TrueClass, FalseClass], :default => true + +def initialize(*args) + super + @action = :create +end diff --git a/cookbooks/users/templates/default/authorized_keys.erb b/cookbooks/users/templates/default/authorized_keys.erb new file mode 100644 index 0000000..bd969f1 --- /dev/null +++ b/cookbooks/users/templates/default/authorized_keys.erb @@ -0,0 +1,6 @@ +# Generated by Chef for <%= node['fqdn'] %> +# Local modifications will be overwritten. + +<% Array(@ssh_keys).each do |key| %> +<%= key %> +<% end -%> diff --git a/cookbooks/users/templates/default/private_key.erb b/cookbooks/users/templates/default/private_key.erb new file mode 100644 index 0000000..f931c04 --- /dev/null +++ b/cookbooks/users/templates/default/private_key.erb @@ -0,0 +1 @@ +<%= @private_key %> diff --git a/cookbooks/users/templates/default/public_key.pub.erb b/cookbooks/users/templates/default/public_key.pub.erb new file mode 100644 index 0000000..31a7285 --- /dev/null +++ b/cookbooks/users/templates/default/public_key.pub.erb @@ -0,0 +1 @@ +<%= @public_key %> diff --git a/cookbooks/windows/CHANGELOG.md b/cookbooks/windows/CHANGELOG.md new file mode 100644 index 0000000..a352183 --- /dev/null +++ b/cookbooks/windows/CHANGELOG.md @@ -0,0 +1,320 @@ +windows Cookbook CHANGELOG +======================= +This file is used to list changes made in each version of the windows cookbook. + +v1.36.6 (2014-12-18) +-------------------- +- reverting all chef_gem compile_time work + +v1.36.5 (2014-12-18) +-------------------- +- Fix zipfile provider + +v1.36.4 (2014-12-18) +-------------------- +- Fix Chef chef_gem with Chef::Resource::ChefGem.method_defined?(:compile_time) + +v1.36.3 (2014-12-18) +-------------------- +- Fix Chef chef_gem below 12.1.0 + +v1.36.2 (2014-12-17) +-------------------- +- Being explicit about usage of the chef_gem's compile_time property. +- Eliminating future deprecation warnings in Chef 12.1.0 + +v1.36.1 (2014-12-17) +-------------------- +- [PR 160](https://github.com/chef-cookbooks/windows/pull/160) - Fix Chef 11.10 / versions without windows_package in core + +v1.36.0 (2014-12-16) +-------------------- +- [PR 145](https://github.com/chef-cookbooks/windows/pull/145) - do not fail on non-existant task +- [PR 144](https://github.com/chef-cookbooks/windows/pull/144) - Add a zip example to the README +- [PR 110](https://github.com/chef-cookbooks/windows/pull/110) - More zip documentation +- [PR 148](https://github.com/chef-cookbooks/windows/pull/148) - Add an LWRP for font installation +- [PR 151](https://github.com/chef-cookbooks/windows/pull/151) - Fix windows_package on Chef 12, add integration tests +- [PR 129](https://github.com/chef-cookbooks/windows/pull/129) - Add enable/disable actions to task LWRP +- [PR 115](https://github.com/chef-cookbooks/windows/pull/115) - require Chef::Mixin::PowershellOut before using it +- [PR 88](https://github.com/chef-cookbooks/windows/pull/88) - Code 1003 from servermanagercmd.exe is valid + +v1.34.8 (2014-10-31) +-------------------- +- [Issue 137](https://github.com/chef-cookbooks/windows/issues/137) - windows_path resource breaks with ruby 2.x + +v1.34.6 (2014-09-22) +-------------------- +- [Chef-2009](https://github.com/chef/chef/issues/2009) - Patch to work around a regression in [Chef](https://github.com/chef/chef) + +v1.34.2 (2014-08-12) +-------------------- +- [Issue 99](https://github.com/chef-cookbooks/windows/issues/99) - Remove rubygems / Internet wmi-lite dependency (PR #108) + +v1.34.0 (2014-08-04) +-------------------- +- [Issue 99](https://github.com/chef-cookbooks/windows/issues/99) - Use wmi-lite to fix Chef 11.14.2 break in rdp-ruby-wmi dependency + +v1.32.1 (2014-07-15) +-------------------- +- Fixes broken cookbook release + +v1.32.0 (2014-07-11) +-------------------- +- Add ChefSpec resource methods to allow notification testing (@sneal) +- Add use_inline_resources to providers (@micgo) +- [COOK-4728] - Allow reboot handler to be used as an exception handler +- [COOK-4620] - Ensure win_friendly_path doesn't error out when ALT_SEPARATOR is nil + +v1.31.0 (2014-05-07) +-------------------- +- [COOK-2934] - Add windows_feature support for 2 new DISM attributes: all, source + +v1.30.2 (2014-04-02) +-------------------- +- [COOK-4414] - Adding ChefSpec matchers + +v1.30.0 (2014-02-14) +-------------------- +- [COOK-3715] - Unable to create a startup task with no login +- [COOK-4188] - Add powershell_version method to return Powershell version + +v1.12.8 (2014-01-21) +-------------------- +- [COOK-3988] Don't unescape URI before constructing it. + +v1.12.6 (2014-01-03) +-------------------- +- [COOK-4168] Circular dep on powershell - moving powershell libraries into windows. removing dependency on powershell + +v1.12.4 +------- +Fixing depend/depends typo in metadata.rb + + +v1.12.2 +------- +### Bug +- **[COOK-4110](https://tickets.chef.io/browse/COOK-4110)** - feature_servermanager installed? method regex bug + + +v1.12.0 +------- +### Bug +- **[COOK-3793](https://tickets.chef.io/browse/COOK-3793)** - parens inside parens of README.md don't render + +### New Feature +- **[COOK-3714](https://tickets.chef.io/browse/COOK-3714)** - Powershell features provider and delete support. + + +v1.11.0 +------- +### Improvement +- **[COOK-3724](https://tickets.chef.io/browse/COOK-3724)** - Rrecommend built-in resources over cookbook resources +- **[COOK-3515](https://tickets.chef.io/browse/COOK-3515)** - Remove unprofessional comment from library +- **[COOK-3455](https://tickets.chef.io/browse/COOK-3455)** - Add Windows Server 2012R2 to windows cookbook version helper + +### Bug +- **[COOK-3542](https://tickets.chef.io/browse/COOK-3542)** - Fix an issue where `windows_zipfile` fails with LoadError +- **[COOK-3447](https://tickets.chef.io/browse/COOK-3447)** - Allow Overriding Of The Default Reboot Timeout In windows_reboot_handler +- **[COOK-3382](https://tickets.chef.io/browse/COOK-3382)** - Allow windows_task to create `on_logon` tasks +- **[COOK-2098](https://tickets.chef.io/browse/COOK-2098)** - Fix and issue where the `windows_reboot` handler is ignoring the reboot time + +### New Feature +- **[COOK-3458](https://tickets.chef.io/browse/COOK-3458)** - Add support for `start_date` and `start_time` in `windows_task` + + +v1.10.0 +------- +### Improvement + +- [COOK-3126]: `windows_task` should support the on start frequency +- [COOK-3127]: Support the force option on task create and delete + +v1.9.0 +------ +### Bug + +- [COOK-2899]: windows_feature fails when a feature install requires a + reboot +- [COOK-2914]: Foodcritic failures in Cookbooks +- [COOK-2983]: windows cookbook has foodcritic failures + +### Improvement + +- [COOK-2686]: Add Windows Server 2012 to version.rb so other + depending chef scripts can detect Windows Server 2012 + +v1.8.10 +------- +When using Windows qualified filepaths (C:/foo), the #absolute? method +for URI returns true, because "C" is the scheme. + +This change checks that the URI is http or https scheme, so it can be +passed off to remote_file appropriately. + +* [COOK-2729] - allow only http, https URI schemes + +v1.8.8 +------ +* [COOK-2729] - helper should use URI rather than regex and bare string + +v1.8.6 +------ +* [COOK-968] - `windows_package` provider should gracefully handle paths with spaces +* [COOK-222] - `windows_task` resource does not declare :change action +* [COOK-241] - Windows cookbook should check for redefined constants +* [COOK-248] - Windows package install type is case sensitive + +v1.8.4 +------ +* [COOK-2336] - MSI That requires reboot returns with RC 3010 and + causes chef run failure +* [COOK-2368] - `version` attribute of the `windows_package` provider + should be documented + +v1.8.2 +------ +**Important**: Use powershell in nodes expanded run lists to ensure + powershell is downloaded, as powershell has a dependency on this + cookbook; v1.8.0 created a circular dependency. + +* [COOK-2301] - windows 1.8.0 has circular dependency on powershell + +v1.8.0 +------ +* [COOK-2126] - Add checksum attribute to `windows_zipfile` +* [COOK-2142] - Add printer and `printer_port` LWRPs +* [COOK-2149] - Chef::Log.debug Windows Package command line +* [COOK-2155] -`windows_package` does not send checksum to + `cached_file` in `installer_type` + +v1.7.0 +------ +* [COOK-1745] - allow for newer versions of rubyzip + +v1.6.0 +------ +* [COOK-2048] - undefined method for Falseclass on task :change when + action is :nothing (and task doesn't exist) +* [COOK-2049] - Add `windows_pagefile` resource + +v1.5.0 +------ +* [COOK-1251] - Fix LWRP "NotImplementedError" +* [COOK-1921] - Task LWRP will return true for resource exists when no + other scheduled tasks exist +* [COOK-1932] - Include :change functionality to windows task lwrp + +v1.4.0: +------ +* [COOK-1571] - `windows_package` resource (with msi provider) does not +accept spaces in filename +* [COOK-1581] - Windows cookbook needs a scheduled tasks LWRP +* [COOK-1584] - `windows_registry` should support all registry types + +v1.3.4 +------ +* [COOK-1173] - `windows_registry` throws Win32::Registry::Error for + action :remove on a nonexistent key +* [COOK-1182] - windows package sets start window title instead of + quoting a path +* [COOK-1476] - zipfile lwrp should support :zip action +* [COOK-1485] - package resource fails to perform install correctly + when "source" contains quote +* [COOK-1519] - add action :remove for path lwrp + +v1.3.2 +------ +* [COOK-1033] - remove the `libraries/ruby_19_patches.rb` file which + causes havoc on non-Windows systems. +* [COOK-811] - add a timeout parameter attribute for `windows_package` + +v1.3.0 +------ +* [COOK-1323] - Update for changes in Chef 0.10.10. + - Setting file mode doesn't make sense on Windows (package provider + - and `reboot_handler` recipe) + - Prefix ::Win32 to avoid namespace collision with Chef::Win32 + - (`registry_helper` library) + - Use chef_gem instead of gem_package so gems get installed correctly + under the Ruby environment Chef runs in (reboot_handler recipe, + zipfile provider) + +v1.2.12 +------- +* [COOK-1037] - specify version for rubyzip gem +* [COOK-1007] - `windows_feature` does not work to remove features with + dism +* [COOK-667] - shortcut resource + provider for Windows platforms + +v1.2.10 +------- +* [COOK-939] - add `type` parameter to `windows_registry` to allow binary registry keys. +* [COOK-940] - refactor logic so multiple values get created. + +v1.2.8 +------ +* FIX: Older Windows (Windows Server 2003) sometimes return 127 on successful forked commands +* FIX: `windows_package`, ensure we pass the WOW* registry redirection flags into reg.open + +v1.2.6 +------ +* patch to fix [CHEF-2684], Open4 is named Open3 in Ruby 1.9 +* Ruby 1.9's Open3 returns 0 and 42 for successful commands +* retry keyword can only be used in a rescue block in Ruby 1.9 + +v1.2.4 +------ +* `windows_package` - catch Win32::Registry::Error that pops up when searching certain keys + +v1.2.2 +------ +* combined numerous helper libarires for easier sharing across libaries/LWRPs +* renamed Chef::Provider::WindowsFeature::Base file to the more descriptive `feature_base.rb` +* refactored `windows_path` LWRP + * :add action should MODIFY the the underlying ENV variable (vs CREATE) + * deleted greedy :remove action until it could be made more idempotent +* added a `windows_batch` resource/provider for running batch scripts remotely + +v1.2.0 +------ +* [COOK-745] gracefully handle required server restarts on Windows platform + * WindowsRebootHandler for requested and pending reboots + * `windows_reboot` LWRP for requesting (receiving notifies) reboots + * `reboot_handler` recipe for enabling WindowsRebootHandler as a report handler +* [COOK-714] Correct initialize misspelling +* RegistryHelper - new `get_values` method which returns all values for a particular key. + +v1.0.8 +------ +* [COOK-719] resource/provider for managing windows features +* [COOK-717] remove `windows_env_vars` resource as env resource exists in core chef +* new `Windows::Version` helper class +* refactored `Windows::Helper` mixin + +v1.0.6 +------ +* added `force_modify` action to `windows_registry` resource +* add `win_friendly_path` helper +* re-purpose default recipe to install useful supporting windows related gems + +v1.0.4 +------ +* [COOK-700] new resources and improvements to the `windows_registry` provider (thanks Paul Morton!) + * Open the registry in the bitednes of the OS + * Provide convenience methods to check if keys and values exit + * Provide convenience method for reading registry values + * NEW - `windows_auto_run` resource/provider + * NEW - `windows_env_vars` resource/provider + * NEW - `windows_path` resource/provider +* re-write of the `windows_package` logic for determining current installed packages +* new checksum attribute for `windows_package` resource...useful for remote packages + +v1.0.2 +------ +* [COOK-647] account for Wow6432Node registry redirecter +* [COOK-656] begin/rescue on win32/registry + +v1.0.0 +------ +* [COOK-612] initial release diff --git a/cookbooks/windows/README.md b/cookbooks/windows/README.md new file mode 100644 index 0000000..3559281 --- /dev/null +++ b/cookbooks/windows/README.md @@ -0,0 +1,749 @@ +Windows Cookbook +================ +Provides a set of Windows-specific primitives (Chef resources) meant to aid in the creation of cookbooks/recipes targeting the Windows platform. + + +Requirements +------------- +Version 1.3.0+ of this cookbook requires Chef 0.10.10+. + + +### Platforms +* Windows XP +* Windows Vista +* Windows Server 2003 R2 +* Windows 7 +* Windows Server 2008 (R1, R2) + +The `windows_task` LWRP requires Windows Server 2008 due to its API usage. + +### Cookbooks +The following cookbooks provided by Chef Software are required as noted: + +* chef_handler (`windows::reboot_handler` leverages the chef_handler LWRP) + +Attributes +---------- +* `node['windows']['allow_pending_reboots']` - used to configure the `WindowsRebootHandler` (via the `windows::reboot_handler` recipe) to act on pending reboots. default is true (ie act on pending reboots). The value of this attribute only has an effect if the `windows::reboot_handler` is in a node's run list. +* `node['windows']['allow_reboot_on_failure']` - used to register the `WindowsRebootHandler` (via the `windows::reboot_handler` recipe) as an exception handler too to act on reboots not only at the end of successful Chef runs, but even at the end of failed runs. default is false (ie reboot only after successful runs). The value of this attribute only has an effect if the `windows::reboot_handler` is in a node's run list. + + +Resource/Provider +----------------- +### windows_auto_run +#### Actions +- :create: Create an item to be run at login +- :remove: Remove an item that was previously setup to run at login + +#### Attribute Parameters +- :name: Name attribute. The name of the value to be stored in the registry +- :program: The program to be run at login +- :args: The arguments for the program + +#### Examples +Run BGInfo at login + +```ruby +windows_auto_run 'BGINFO' do + program 'C:/Sysinternals/bginfo.exe' + args '\'C:/Sysinternals/Config.bgi\' /NOLICPROMPT /TIMER:0' + not_if { Registry.value_exists?(AUTO_RUN_KEY, 'BGINFO') } + action :create +end +``` + +### windows_batch +(Chef 11.6.0 includes a built-in [batch](http://docs.chef.io/resource_batch.html) resource, so use that in preference to `windows_batch` if possible.) + +Execute a batch script using the cmd.exe interpreter (much like the script resources for bash, csh, powershell, perl, python and ruby). A temporary file is created and executed like other script resources, rather than run inline. By their nature, Script resources are not idempotent, as they are completely up to the user's imagination. Use the `not_if` or `only_if` meta parameters to guard the resource for idempotence. + +#### Actions +- :run: run the batch file + +#### Attribute Parameters +- command: name attribute. Name of the command to execute. +- code: quoted string of code to execute. +- creates: a file this command creates - if the file exists, the command will not be run. +- cwd: current working directory to run the command from. +- flags: command line flags to pass to the interpreter when invoking. +- user: A user name or user ID that we should change to before running this command. +- group: A group name or group ID that we should change to before running this command. + +#### Examples +```ruby +windows_batch 'unzip_and_move_ruby' do + code <<-EOH + 7z.exe x #{Chef::Config[:file_cache_path]}/ruby-1.8.7-p352-i386-mingw32.7z -oC:\\source -r -y + xcopy C:\\source\\ruby-1.8.7-p352-i386-mingw32 C:\\ruby /e /y + EOH +end +``` + +```ruby +windows_batch 'echo some env vars' do + code <<-EOH + echo %TEMP% + echo %SYSTEMDRIVE% + echo %PATH% + echo %WINDIR% + EOH +end +``` + +### windows_feature +Windows Roles and Features can be thought of as built-in operating system packages that ship with the OS. A server role is a set of software programs that, when they are installed and properly configured, lets a computer perform a specific function for multiple users or other computers within a network. A Role can have multiple Role Services that provide functionality to the Role. Role services are software programs that provide the functionality of a role. Features are software programs that, although they are not directly parts of roles, can support or augment the functionality of one or more roles, or improve the functionality of the server, regardless of which roles are installed. Collectively we refer to all of these attributes as 'features'. + +This resource allows you to manage these 'features' in an unattended, idempotent way. + +There are two providers for the `windows_features` which map into Microsoft's two major tools for managing roles/features: [Deployment Image Servicing and Management (DISM)](http://msdn.microsoft.com/en-us/library/dd371719%28v=vs.85%29.aspx) and [Servermanagercmd](http://technet.microsoft.com/en-us/library/ee344834%28WS.10%29.aspx) (The CLI for Server Manager). As Servermanagercmd is deprecated, Chef will set the default provider to `Chef::Provider::WindowsFeature::DISM` if DISM is present on the system being configured. The default provider will fall back to `Chef::Provider::WindowsFeature::ServerManagerCmd`. + +For more information on Roles, Role Services and Features see the [Microsoft TechNet article on the topic](http://technet.microsoft.com/en-us/library/cc754923.aspx). For a complete list of all features that are available on a node type either of the following commands at a command prompt: + +```text +dism /online /Get-Features +servermanagercmd -query +``` + +#### Actions +- :install: install a Windows role/feature +- :remove: remove a Windows role/feature + +#### Attribute Parameters +- feature_name: name of the feature/role to install. The same feature may have different names depending on the provider used (ie DHCPServer vs DHCP; DNS-Server-Full-Role vs DNS). +- all: Boolean. Optional. Default: false. DISM provider only. Forces all dependencies to be installed. +- source: String. Optional. DISM provider only. Uses local repository for feature install. + +#### Providers +- **Chef::Provider::WindowsFeature::DISM**: Uses Deployment Image Servicing and Management (DISM) to manage roles/features. +- **Chef::Provider::WindowsFeature::ServerManagerCmd**: Uses Server Manager to manage roles/features. +- **Chef::Provider::WindowsFeaturePowershell**: Uses Powershell to manage roles/features. (see [COOK-3714](https://tickets.chef.io/browse/COOK-3714) + +#### Examples +Enable the node as a DHCP Server + +```ruby +windows_feature 'DHCPServer' do + action :install +end +``` + +Enable TFTP + +```ruby +windows_feature 'TFTP' do + action :install +end +``` + +Enable .Net 3.5.1 on Server 2012 using repository files on DVD and +install all dependencies + +```ruby +windows_feature "NetFx3" do + action :install + all true + source "d:\sources\sxs" +end +``` + +Disable Telnet client/server + +```ruby +%w[TelnetServer TelnetClient].each do |feature| + windows_feature feature do + action :remove + end +end +``` + +### windows_font +Installs a font. + +Font files should be included in the cookbooks + +#### Actions +- :install: install a font to the system fonts directory. + +#### Attribute Parameters +- file: The name of the font file name to install. It should exist in the files/default directory of the cookbook you're calling windows_font from. Defaults to the resource name. + +#### Examples + +```ruby +windows_font 'Code New Roman.otf' +``` + +### windows_package +Manage Windows application packages in an unattended, idempotent way. + +The following application installers are currently supported: + +* MSI packages +* InstallShield +* Wise InstallMaster +* Inno Setup +* Nullsoft Scriptable Install System + +If the proper installer type is not passed into the resource's installer_type attribute, the provider will do it's best to identify the type by introspecting the installation package. If the installation type cannot be properly identified the `:custom` value can be passed into the installer_type attribute along with the proper flags for silent/quiet installation (using the `options` attribute..see example below). + +__PLEASE NOTE__ - For proper idempotence the resource's `package_name` should be the same as the 'DisplayName' registry value in the uninstallation data that is created during package installation. The easiest way to definitively find the proper 'DisplayName' value is to install the package on a machine and search for the uninstall information under the following registry keys: + +* `HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall` +* `HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall` +* `HKEY_LOCAL_MACHINE\Software\Wow6464Node\Microsoft\Windows\CurrentVersion\Uninstall` + +For maximum flexibility the `source` attribute supports both remote and local installation packages. + +#### Actions +- :install: install a package +- :remove: remove a package. The remove action is completely hit or miss as many application uninstallers do not support a full silent/quiet mode. + +#### Attribute Parameters +- package_name: name attribute. The 'DisplayName' of the application installation package. +- source: The source of the windows installer. This can either be a URI or a local path. +- installer_type: They type of windows installation package. valid values are: :msi, :inno, :nsis, :wise, :installshield, :custom. If this value is not provided, the provider will do it's best to identify the installer type through introspection of the file. +- checksum: useful if source is remote, the SHA-256 checksum of the file--if the local file matches the checksum, Chef will not download it +- options: Additional options to pass the underlying installation command +- timeout: set a timeout for the package download (default 600 seconds) +- version: The version number of this package, as indicated by the 'DisplayVersion' value in one of the 'Uninstall' registry keys. If the given version number does equal the 'DisplayVersion' in the registry, the package will be installed. +- success_codes: set an array of possible successful installation + return codes. Previously this was hardcoded, but certain MSIs may + have a different return code, e.g. 3010 for reboot required. Must be + an array, and defaults to `[0, 42, 127]`. + +#### Examples + +Install PuTTY (InnoSetup installer) +```ruby +windows_package 'PuTTY version 0.60' do + source 'http://the.earth.li/~sgtatham/putty/latest/x86/putty-0.60-installer.exe' + installer_type :inno + action :install +end +``` + +Install 7-Zip (MSI installer) +```ruby +windows_package '7-Zip 9.20 (x64 edition)' do + source 'http://downloads.sourceforge.net/sevenzip/7z920-x64.msi' + action :install +end +``` + +Install Notepad++ (Y U No Emacs?) using a local installer +```ruby +windows_package 'Notepad++' do + source 'c:/installation_files/npp.5.9.2.Installer.exe' + action :install +end +``` + +Install VLC for that Xvid (NSIS installer) +```ruby +windows_package 'VLC media player 1.1.10' do + source 'http://superb-sea2.dl.sourceforge.net/project/vlc/1.1.10/win32/vlc-1.1.10-win32.exe' + action :install +end +``` + +Install Firefox as custom installer and manually set the silent install flags +```ruby +windows_package 'Mozilla Firefox 5.0 (x86 en-US)' do + source 'http://archive.mozilla.org/pub/mozilla.org/mozilla.org/firefox/releases/5.0/win32/en-US/Firefox%20Setup%205.0.exe' + options '-ms' + installer_type :custom + action :install +end +``` + +Google Chrome FTW (MSI installer) +```ruby +windows_package 'Google Chrome' do + source 'https://dl-ssl.google.com/tag/s/appguid%3D%7B8A69D345-D564-463C-AFF1-A69D9E530F96%7D%26iid%3D%7B806F36C0-CB54-4A84-A3F3-0CF8A86575E0%7D%26lang%3Den%26browser%3D3%26usagestats%3D0%26appname%3DGoogle%2520Chrome%26needsadmin%3Dfalse/edgedl/chrome/install/GoogleChromeStandaloneEnterprise.msi' + action :install +end +``` + +Remove Google Chrome +```ruby +windows_package 'Google Chrome' do + action :remove +end +``` + +Remove 7-Zip +```ruby +windows_package '7-Zip 9.20 (x64 edition)' do + action :remove +end +``` + +### windows_printer_port + +Create and delete TCP/IPv4 printer ports. + +#### Actions +- :create: Create a TCIP/IPv4 printer port. This is the default action. +- :delete: Delete a TCIP/IPv4 printer port + +#### Attribute Parameters +- :ipv4_address: Name attribute. Required. IPv4 address, e.g. '10.0.24.34' +- :port_name: Port name. Optional. Defaults to 'IP_' + :ipv4_address +- :port_number: Port number. Optional. Defaults to 9100. +- :port_description: Port description. Optional. +- :snmp_enabled: Boolean. Optional. Defaults to false. +- :port_protocol: Port protocol, 1 (RAW), or 2 (LPR). Optional. Defaults to 1. + +#### Examples + +Create a TCP/IP printer port named 'IP_10.4.64.37' with all defaults +```ruby +windows_printer_port '10.4.64.37' do +end +``` + +Delete a printer port +```ruby +windows_printer_port '10.4.64.37' do + action :delete +end +``` + +Delete a port with a custom port_name +```ruby +windows_printer_port '10.4.64.38' do + port_name 'My awesome port' + action :delete +end +``` + +Create a port with more options +```ruby +windows_printer_port '10.4.64.39' do + port_name 'My awesome port' + snmp_enabled true + port_protocol 2 +end +``` + +### windows_printer + +Create Windows printer. Note that this doesn't currently install a printer +driver. You must already have the driver installed on the system. + +The Windows Printer LWRP will automatically create a TCP/IP printer port for you using the `ipv4_address` property. If you want more granular control over the printer port, just create it using the `windows_printer_port` LWRP before creating the printer. + +#### Actions +- :create: Create a new printer +- :delete: Delete a new printer + +#### Attribute Parameters +- :device_id: Name attribute. Required. Printer queue name, e.g. 'HP LJ 5200 in fifth floor copy room' +- :comment: Optional string describing the printer queue. +- :default: Boolean. Optional. Defaults to false. Note that Windows sets the first printer defined to the default printer regardless of this setting. +- :driver_name: String. Required. Exact name of printer driver. Note that the printer driver must already be installed on the node. +- :location: Printer location, e.g. 'Fifth floor copy room', or 'US/NYC/Floor42/Room4207' +- :shared: Boolean. Defaults to false. +- :share_name: Printer share name. +- :ipv4_address: Printer IPv4 address, e.g. '10.4.64.23'. You don't have to be able to ping the IP addresss to set it. Required. + +An error of "Set-WmiInstance : Generic failure" is most likely due to the printer driver name not matching or not being installed. + +#### Examples + +Create a printer +```ruby +windows_printer 'HP LaserJet 5th Floor' do + driver_name 'HP LaserJet 4100 Series PCL6' + ipv4_address '10.4.64.38' +end +``` + +Delete a printer. Note: this doesn't delete the associated printer port. See `windows_printer_port` above for how to delete the port. +```ruby +windows_printer 'HP LaserJet 5th Floor' do + action :delete +end +``` + +### windows_reboot +Sets required data in the node's run_state to notify `WindowsRebootHandler` a reboot is requested. If Chef run completes successfully a reboot will occur if the `WindowsRebootHandler` is properly registered as a report handler. As an action of `:request` will cause a node to reboot every Chef run, this resource is usually notified by other resources...ie restart node after a package is installed (see example below). + +#### Actions +- :request: requests a reboot at completion of successful Cher run. requires `WindowsRebootHandler` to be registered as a report handler. +- :cancel: remove reboot request from node.run_state. this will cancel *ALL* previously requested reboots as this is a binary state. + +#### Attribute Parameters +- :timeout: Name attribute. timeout delay in seconds to wait before proceeding with the requested reboot. default is 60 seconds +- :reason: comment on the reason for the reboot. default is 'Chef Software Chef initiated reboot' + +#### Examples +If the package installs, schedule a reboot at end of chef run +```ruby +windows_reboot 60 do + reason 'cause chef said so' + action :nothing +end + +windows_package 'some_package' do + action :install + notifies :request, 'windows_reboot[60]' +end +``` + +Cancel the previously requested reboot +```ruby +windows_reboot 60 do + action :cancel +end +``` + +### windows_registry +(Chef 11.6.0 includes a built-in [registry_key](http://docs.chef.io/resource_registry_key.html) resource, so use that in preference to `windows_registry` if possible.) + +Creates and modifies Windows registry keys. + +*Change in v1.3.0: The Win32 classes use `::Win32` to avoid namespace conflict with `Chef::Win32` (introduced in Chef 0.10.10).* + +#### Actions +- :create: create a new registry key with the provided values. +- :modify: modify an existing registry key with the provided values. +- :force_modify: modify an existing registry key with the provided values. ensures the value is actually set by checking multiple times. useful for fighting race conditions where two processes are trying to set the same registry key. This will be updated in the near future to use 'RegNotifyChangeKeyValue' which is exposed by the WinAPI and allows a process to register for notification on a registry key change. +- :remove: removes a value from an existing registry key + +#### Attribute Parameters +- key_name: name attribute. The registry key to create/modify. +- values: hash of the values to set under the registry key. The individual hash items will become respective 'Value name' => 'Value data' items in the registry key. +- type: Type of key to create, defaults to REG_SZ. Must be a symbol, see the overview below for valid values. + +#### Registry key types +- :binary: REG_BINARY +- :string: REG_SZ +- :multi_string: REG_MULTI_SZ +- :expand_string: REG_EXPAND_SZ +- :dword: REG_DWORD +- :dword_big_endian: REG_DWORD_BIG_ENDIAN +- :qword: REG_QWORD + +#### Examples + +Make the local windows proxy match the one set for Chef +```ruby +proxy = URI.parse(Chef::Config[:http_proxy]) +windows_registry 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' do + values 'ProxyEnable' => 1, 'ProxyServer' => "#{proxy.host}:#{proxy.port}", 'ProxyOverride' => '' +end +``` + +Enable Remote Desktop and poke the firewall hole +```ruby +windows_registry 'HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server' do + values 'FdenyTSConnections' => 0 +end +``` + +Delete an item from the registry +```ruby +windows_registry 'HKCU\Software\Test' do + #Key is the name of the value that you want to delete the value is always empty + values 'ValueToDelete' => '' + action :remove +end +``` + +Add a REG_MULTI_SZ value to the registry +```ruby +windows_registry 'HKCU\Software\Test' do + values 'MultiString' => ['line 1', 'line 2', 'line 3'] + type :multi_string +end +``` + +#### Library Methods + +```ruby +Registry.value_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run','BGINFO') +Registry.key_exists?('HKLM\SOFTWARE\Microsoft') +BgInfo = Registry.get_value('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run','BGINFO') +``` + +### windows_path +#### Actions +- :add: Add an item to the system path +- :remove: Remove an item from the system path + +#### Attribute Parameters +- :path: Name attribute. The name of the value to add to the system path + +#### Examples + +Add Sysinternals to the system path +```ruby +windows_path 'C:\Sysinternals' do + action :add +end +``` + +Remove 7-Zip from the system path +```ruby +windows_path 'C:\7-Zip' do + action :remove +end +``` + +### windows_task +Creates, deletes or runs a Windows scheduled task. Requires Windows +Server 2008 due to API usage. + +#### Actions +- :create: creates a task +- :delete: deletes a task +- :run: runs a task +- :change: changes the un/pw or command of a task +- :enable: enable a task +- :disable: disable a task + +#### Attribute Parameters +- name: name attribute, The task name. +- command: The command the task will run. +- cwd: The directory the task will be run from. +- user: The user to run the task as. (requires password) +- password: The user's password. (requires user) +- run_level: Run with limited or highest privileges. +- frequency: Frequency with which to run the task. (hourly, daily, ect.) +- frequency_modifier: Multiple for frequency. (15 minutes, 2 days) +- start_day: Specifies the first date on which the task runs. Optional string (MM/DD/YYYY) +- start_time: Specifies the start time to run the task. Optional string (HH:mm) + +#### Examples + +Run Chef every 15 minutes +```ruby +windows_task 'Chef client' do + user 'Administrator' + password '$ecR3t' + cwd 'C:\chef\bin' + command 'chef-client -L C:\tmp\' + run_level :highest + frequency :minute + frequency_modifier 15 +end +``` + +Update Chef Client task with new password and log location +```ruby +windows_task 'Chef client' do + user 'Administrator' + password 'N3wPassW0Rd' + cwd 'C:\chef\bin' + command 'chef-client -L C:\chef\logs\' + action :change +end +``` + +Delete a taks named 'old task' +```ruby +windows_task 'old task' do + action :delete +end +``` + +Enable a task named 'Chef client' +```ruby +windows_task 'Chef client' do + action :enable +end +``` + +Disable a task named 'Chef client' +```ruby +windows_task 'Chef client' do + action :disable +end +``` + +### windows_zipfile +Most version of Windows do not ship with native cli utility for managing compressed files. This resource provides a pure-ruby implementation for managing zip files. Be sure to use the `not_if` or `only_if` meta parameters to guard the resource for idempotence or action will be taken every Chef run. + +#### Actions +- :unzip: unzip a compressed file +- :zip: zip a directory (recursively) + +#### Attribute Parameters +- path: name attribute. The path where files will be (un)zipped to. +- source: source of the zip file (either a URI or local path) for :unzip, or directory to be zipped for :zip. +- overwrite: force an overwrite of the files if they already exist. +- checksum: for :unzip, useful if source is remote, if the local file matches the SHA-256 checksum, Chef will not download it. + +#### Examples + +Unzip a remote zip file locally +```ruby +windows_zipfile 'c:/bin' do + source 'http://download.sysinternals.com/Files/SysinternalsSuite.zip' + action :unzip + not_if {::File.exists?('c:/bin/PsExec.exe')} +end +``` + +Unzip a local zipfile +```ruby +windows_zipfile 'c:/the_codez' do + source 'c:/foo/baz/the_codez.zip' + action :unzip +end +``` + +Create a local zipfile +```ruby +windows_zipfile 'c:/foo/baz/the_codez.zip' do + source 'c:/the_codez' + action :zip +end +``` + +Libraries +------------------------- +### WindowsHelper + +Helper that allows you to use helpful functions in windows + +#### installed_packages +Returns a hash of all DisplayNames installed +```ruby +# usage in a recipe +::Chef::Recipe.send(:include, Windows::Helper) +hash_of_installed_packages = installed_packages +``` + +#### is_package_installed? +- `package_name`: The name of the package you want to query to see if it is installed +- `returns`: true if the package is installed, false if it the package is not installed + +Download a file if a package isn't installed +```ruby +# usage in a recipe to not download a file if package is already installed +::Chef::Recipe.send(:include, Windows::Helper) +is_win_sdk_installed = is_package_installed?('Windows Software Development Kit') + +remote_file 'C:\windows\temp\windows_sdk.zip' do + source 'http://url_to_download/windows_sdk.zip' + action :create_if_missing + not_if {is_win_sdk_installed} +end +``` +Do something if a package is installed +```ruby +# usage in a provider +include Windows::Helper +if is_package_installed?('Windows Software Development Kit') + # do something if package is installed +end +``` + +Exception/Report Handlers +------------------------- +### WindowsRebootHandler +Required reboots are a necessary evil of configuring and managing Windows nodes. This report handler (ie fires at the end of Chef runs) acts on requested (Chef initiated) or pending (as determined by the OS per configuration action we performed) reboots. The `allow_pending_reboots` initialization argument should be set to false if you do not want the handler to automatically reboot a node if it has been determined a reboot is pending. Reboots can still be requested explicitly via the `windows_reboot` LWRP. + +### Initialization Arguments +- `allow_pending_reboots`: indicator on whether the handler should act on a the Window's 'pending reboot' state. default is true +- `timeout`: timeout delay in seconds to wait before proceeding with the reboot. default is 60 seconds +- `reason`: comment on the reason for the reboot. default is 'Chef Software Chef initiated reboot' + + +Windows ChefSpec Matchers +------------------------- +The Windows cookbook includes custom [ChefSpec](https://github.com/sethvargo/chefspec) matchers you can use to test your own cookbooks that consume Windows cookbook LWRPs. + +###Example Matcher Usage +```ruby +expect(chef_run).to install_windows_package('Node.js').with( + source: 'http://nodejs.org/dist/v0.10.26/x64/node-v0.10.26-x64.msi') +``` + +###Windows Cookbook Matchers +* install_windows_package +* remove_windows_package +* install_windows_feature +* remove_windows_feature +* delete_windows_feature +* create_windows_task +* delete_windows_task +* run_windows_task +* change_windows_task +* add_windows_path +* remove_windows_path +* run_windows_batch +* set_windows_pagefile +* unzip_windows_zipfile_to +* zip_windows_zipfile_to +* create_windows_shortcut +* create_windows_auto_run +* remove_windows_auto_run +* create_windows_printer +* delete_windows_printer +* create_windows_printer_port +* delete_windows_printer_port +* request_windows_reboot +* cancel_windows_reboot +* create_windows_shortcut + + +Usage +----- + +Place an explicit dependency on this cookbook (using depends in the cookbook's metadata.rb) from any cookbook where you would like to use the Windows-specific resources/providers that ship with this cookbook. + +```ruby +depends 'windows' +``` + +### default +Convenience recipe that installs supporting gems for many of the resources/providers that ship with this cookbook. + +*Change in v1.3.0: Uses chef_gem instead of gem_package to ensure gem installation in Chef 0.10.10.* + +### reboot_handler +Leverages the `chef_handler` LWRP to register the `WindowsRebootHandler` report handler that ships as part of this cookbook. By default this handler is set to automatically act on pending reboots. If you would like to change this behavior override `node['windows']['allow_pending_reboots']` and set the value to false. For example: + +```ruby +name 'base' +description 'base role' +override_attributes( + 'windows' => { + 'allow_pending_reboots' => false + } +) +``` + +This will still allow a reboot to be explicitly requested via the `windows_reboot` LWRP. + +By default, the handler will only be registered as a report handler, meaning that it will only fire at the end of successful Chef runs. If the run fails, pending or requested reboots will be ignored. This can lead to a situation where some package was installed and notified a reboot request via the `windows_reboot` LWRP, and then the run fails for some unrelated reason, and the reboot request gets dropped because the resource that notified the reboot request will already be up-to-date at the next run and will not request a reboot again, and thus the requested reboot will never be performed. To change this behavior and register the handler as an exception handler that fires at the end of failed runs too, override `node['windows']['allow_reboot_on_failure']` and set the value to true. + + +License & Authors +----------------- +- Author:: Seth Chisamore () +- Author:: Doug MacEachern () +- Author:: Paul Morton () +- Author:: Doug Ireton () + +```text +Copyright 2011-2013, Chef Software, Inc. +Copyright 2010, VMware, Inc. +Copyright 2011, Business Intelligence Associates, Inc +Copyright 2012, Nordstrom, Inc. + +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/cookbooks/windows/attributes/default.rb b/cookbooks/windows/attributes/default.rb new file mode 100644 index 0000000..f76f65c --- /dev/null +++ b/cookbooks/windows/attributes/default.rb @@ -0,0 +1,24 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Attribute:: default +# +# Copyright 2011, Chef Software, Inc +# +# 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. +# + +default['windows']['allow_pending_reboots'] = true +default['windows']['allow_reboot_on_failure'] = false +default['windows']['rubyzipversion'] = nil +default['windows']['reboot_timeout'] = 60 diff --git a/cookbooks/windows/files/default/handlers/windows_reboot_handler.rb b/cookbooks/windows/files/default/handlers/windows_reboot_handler.rb new file mode 100644 index 0000000..95382ec --- /dev/null +++ b/cookbooks/windows/files/default/handlers/windows_reboot_handler.rb @@ -0,0 +1,76 @@ +# +# Author:: Seth Chisamore () +# Copyright:: Copyright (c) 2011 Chef Software, Inc +# 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. +# + +class WindowsRebootHandler < Chef::Handler + include Chef::Mixin::ShellOut + + def initialize(allow_pending_reboots = true, timeout = 60, reason = "Chef Software Chef initiated reboot") + @allow_pending_reboots = allow_pending_reboots + @timeout = timeout + @reason = reason + end + + def report + log_message, reboot = begin + if reboot_requested? + ["chef_handler[#{self.class}] requested reboot will occur in #{timeout} seconds", true] + elsif reboot_pending? + if @allow_pending_reboots + ["chef_handler[#{self.class}] reboot pending - automatic reboot will occur in #{timeout} seconds", true] + else + ["chef_handler[#{self.class}] reboot pending but handler not configured to act on pending reboots - please reboot node manually", false] + end + else + ["chef_handler[#{self.class}] no reboot requested or pending", false] + end + end + + Chef::Log.warn(log_message) + shell_out!("shutdown /r /t #{timeout} /c \"#{reason}\"") if reboot + end + + private + # reboot cause CHEF says so: + # reboot explicitly requested in our cookbook code + def reboot_requested? + node.run_state[:reboot_requested] == true + end + + # reboot cause WIN says so: + # reboot pending because of some configuration action we performed + def reboot_pending? + # Any files listed here means reboot needed + (Registry.key_exists?('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations') && + Registry.get_value('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager','PendingFileRenameOperations').any?) || + # 1 for any value means reboot pending + # "9306cdfc-c4a1-4a22-9996-848cb67eddc3"=1 + (Registry.key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') && + Registry.get_values('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired').select{|v| v[2] == 1 }.any?) || + # 1 or 2 for 'Flags' value means reboot pending + (Registry.key_exists?('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile') && + [1,2].include?(Registry::get_value('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile','Flags'))) + end + + def timeout + node.run_state[:reboot_timeout] || node['windows']['reboot_timeout'] || @timeout + end + + def reason + node.run_state[:reboot_reason] || @reason + end +end diff --git a/cookbooks/windows/libraries/feature_base.rb b/cookbooks/windows/libraries/feature_base.rb new file mode 100644 index 0000000..a25c56f --- /dev/null +++ b/cookbooks/windows/libraries/feature_base.rb @@ -0,0 +1,59 @@ +class Chef + class Provider + class WindowsFeature + module Base + + def action_install + unless installed? + install_feature(@new_resource.feature_name) + @new_resource.updated_by_last_action(true) + Chef::Log.info("#{@new_resource} installed feature") + else + Chef::Log.debug("#{@new_resource} is already installed - nothing to do") + end + end + + def action_remove + if installed? + remove_feature(@new_resource.feature_name) + @new_resource.updated_by_last_action(true) + Chef::Log.info("#{@new_resource} removed") + else + Chef::Log.debug("#{@new_resource} feature does not exist - nothing to do") + end + end + + def action_delete + if available? + delete_feature(@new_resource.feature_name) + @new_resource.updated_by_last_action(true) + Chef::Log.info("#{@new_resource} deleted") + else + Chef::Log.debug("#{@new_resource} feature is not installed - nothing to do") + end + end + + def install_feature(name) + raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :install" + end + + def remove_feature(name) + raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :remove" + end + + def delete_feature(name) + raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :delete" + end + + def installed? + raise Chef::Exceptions::Override, "You must override installed? in #{self.to_s}" + end + + def available? + raise Chef::Exceptions::Override, "You must override available? in #{self.to_s}" + end + end + end + end +end + diff --git a/cookbooks/windows/libraries/matchers.rb b/cookbooks/windows/libraries/matchers.rb new file mode 100644 index 0000000..6aeb516 --- /dev/null +++ b/cookbooks/windows/libraries/matchers.rb @@ -0,0 +1,465 @@ +if defined?(ChefSpec) + chefspec_version = Gem.loaded_specs["chefspec"].version + if chefspec_version < Gem::Version.new('4.1.0') + define_method = ChefSpec::Runner.method(:define_runner_method) + else + define_method = ChefSpec.method(:define_matcher) + end + + define_method.call :windows_package + define_method.call :windows_feature + define_method.call :windows_task + define_method.call :windows_path + define_method.call :windows_batch + define_method.call :windows_pagefile + define_method.call :windows_zipfile + define_method.call :windows_shortcut + define_method.call :windows_auto_run + define_method.call :windows_printer + define_method.call :windows_printer_port + define_method.call :windows_reboot + # + # Assert that a +windows_package+ resource exists in the Chef run with the + # action +:install+. Given a Chef Recipe that installs "Node.js" as a + # +windows_package+: + # + # windows_package 'Node.js' do + # source 'http://nodejs.org/dist/v0.10.26/x64/node-v0.10.26-x64.msi' + # action :install + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_package+ resource with ChefSpec. + # + # @example Assert that a +windows_package+ was installed + # expect(chef_run).to install_windows_package('Node.js') + # + # @example Assert that a +windows_package+ was _not_ installed + # expect(chef_run).to_not install_windows_package('7-zip') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def install_windows_package(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_package, :install, resource_name) + end + + # + # Assert that a +windows_package+ resource exists in the Chef run with the + # action +:remove+. Given a Chef Recipe that removes "Node.js" as a + # +windows_package+: + # + # windows_package 'Node.js' do + # action :remove + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_package+ resource with ChefSpec. + # + # @example Assert that a +windows_package+ was installed + # expect(chef_run).to remove_windows_package('Node.js') + # + # @example Assert that a +windows_package+ was _not_ removed + # expect(chef_run).to_not remove_windows_package('7-zip') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def remove_windows_package(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_package, :remove, resource_name) + end + + + + # + # Assert that a +windows_feature+ resource exists in the Chef run with the + # action +:install+. Given a Chef Recipe that installs "NetFX3" as a + # +windows_feature+: + # + # windows_feature 'NetFX3' do + # action :install + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_feature+ resource with ChefSpec. + # + # @example Assert that a +windows_feature+ was installed + # expect(chef_run).to install_windows_feature('NetFX3') + # + # @example Assert that a +windows_feature+ was _not_ installed + # expect(chef_run).to_not install_windows_feature('NetFX3') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def install_windows_feature(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_feature, :install, resource_name) + end + + # + # Assert that a +windows_feature+ resource exists in the Chef run with the + # action +:remove+. Given a Chef Recipe that removes "NetFX3" as a + # +windows_feature+: + # + # windows_feature 'NetFX3' do + # action :remove + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_feature+ resource with ChefSpec. + # + # @example Assert that a +windows_feature+ was removed + # expect(chef_run).to remove_windows_feature('NetFX3') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def remove_windows_feature(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_feature, :remove, resource_name) + end + + # + # Assert that a +windows_feature+ resource exists in the Chef run with the + # action +:delete+. Given a Chef Recipe that deletes "NetFX3" as a + # +windows_feature+: + # + # windows_feature 'NetFX3' do + # action :delete + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_feature+ resource with ChefSpec. + # + # @example Assert that a +windows_feature+ was deleted + # expect(chef_run).to delete_windows_feature('NetFX3') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def delete_windows_feature(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_feature, :delete, resource_name) + end + + + + # + # Assert that a +windows_task+ resource exists in the Chef run with the + # action +:create+. Given a Chef Recipe that creates "mytask" as a + # +windows_task+: + # + # windows_task 'mytask' do + # command 'mybatch.bat' + # action :create + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_task+ resource with ChefSpec. + # + # @example Assert that a +windows_task+ was created + # expect(chef_run).to create_windows_task('mytask') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def create_windows_task(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_task, :create, resource_name) + end + + # + # Assert that a +windows_task+ resource exists in the Chef run with the + # action +:delete+. Given a Chef Recipe that deletes "mytask" as a + # +windows_task+: + # + # windows_task 'mytask' do + # action :delete + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_task+ resource with ChefSpec. + # + # @example Assert that a +windows_task+ was deleted + # expect(chef_run).to delete_windows_task('mytask') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def delete_windows_task(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_task, :delete, resource_name) + end + + # + # Assert that a +windows_task+ resource exists in the Chef run with the + # action +:run+. Given a Chef Recipe that runs "mytask" as a + # +windows_task+: + # + # windows_task 'mytask' do + # action :run + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_task+ resource with ChefSpec. + # + # @example Assert that a +windows_task+ was run + # expect(chef_run).to run_windows_task('mytask') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def run_windows_task(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_task, :run, resource_name) + end + + # + # Assert that a +windows_task+ resource exists in the Chef run with the + # action +:change+. Given a Chef Recipe that changes "mytask" as a + # +windows_task+: + # + # windows_task 'mytask' do + # action :change + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_task+ resource with ChefSpec. + # + # @example Assert that a +windows_task+ was changed + # expect(chef_run).to change_windows_task('mytask') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def change_windows_task(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_task, :change, resource_name) + end + + + + # + # Assert that a +windows_path+ resource exists in the Chef run with the + # action +:add+. Given a Chef Recipe that adds "C:\7-Zip" to the Windows + # PATH env var + # + # windows_path 'C:\7-Zip' do + # action :add + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_path+ resource with ChefSpec. + # + # @example Assert that a +windows_path+ was added + # expect(chef_run).to add_windows_path('C:\7-Zip') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def add_windows_path(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_path, :add, resource_name) + end + + # + # Assert that a +windows_path+ resource exists in the Chef run with the + # action +:remove+. Given a Chef Recipe that removes "C:\7-Zip" from the + # Windows PATH env var + # + # windows_path 'C:\7-Zip' do + # action :remove + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_path+ resource with ChefSpec. + # + # @example Assert that a +windows_path+ was removed + # expect(chef_run).to remove_windows_path('C:\7-Zip') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def remove_windows_path(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_path, :remove, resource_name) + end + + + + # + # Assert that a +windows_batch+ resource exists in the Chef run with the + # action +:run+. Given a Chef Recipe that runs a batch script + # + # windows_batch "unzip_and_move_ruby" do + # code <<-EOH + # 7z.exe x #{Chef::Config[:file_cache_path]}/ruby-1.8.7-p352-i386-mingw32.7z + # -oC:\\source -r -y + # xcopy C:\\source\\ruby-1.8.7-p352-i386-mingw32 C:\\ruby /e /y + # EOH + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_path+ resource with ChefSpec. + # + # @example Assert that a +windows_path+ was removed + # expect(chef_run).to run_windows_batch('unzip_and_move_ruby') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def run_windows_batch(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_batch, :run, resource_name) + end + + + + # + # Assert that a +windows_pagefile+ resource exists in the Chef run with the + # action +:set+. Given a Chef Recipe that sets a pagefile + # + # windows_pagefile "pagefile" do + # system_managed true + # initial_size 1024 + # maximum_size 4096 + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_pagefile+ resource with ChefSpec. + # + # @example Assert that a +windows_pagefile+ was set + # expect(chef_run).to set_windows_pagefile('pagefile').with( + # initial_size: 1024) + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def set_windows_pagefile(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_pagefile, :set, resource_name) + end + + + + # + # Assert that a +windows_zipfile+ resource exists in the Chef run with the + # action +:unzip+. Given a Chef Recipe that extracts "SysinternalsSuite.zip" + # to c:/bin + # + # windows_zipfile "c:/bin" do + # source "http://download.sysinternals.com/Files/SysinternalsSuite.zip" + # action :unzip + # not_if {::File.exists?("c:/bin/PsExec.exe")} + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_zipfile+ resource with ChefSpec. + # + # @example Assert that a +windows_zipfile+ was unzipped + # expect(chef_run).to unzip_windows_zipfile_to('c:/bin') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def unzip_windows_zipfile_to(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_zipfile, :unzip, resource_name) + end + + # + # Assert that a +windows_zipfile+ resource exists in the Chef run with the + # action +:zip+. Given a Chef Recipe that zips "c:/src" + # to c:/code.zip + # + # windows_zipfile "c:/code.zip" do + # source "c:/src" + # action :zip + # end + # + # The Examples section demonstrates the different ways to test a + # +windows_zipfile+ resource with ChefSpec. + # + # @example Assert that a +windows_zipfile+ was zipped + # expect(chef_run).to zip_windows_zipfile_to('c:/code.zip') + # + # + # @param [String, Regex] resource_name + # the name of the resource to match + # + # @return [ChefSpec::Matchers::ResourceMatcher] + # + def zip_windows_zipfile_to(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_zipfile, :zip, resource_name) + end + + + # All the other less commonly used LWRPs + def create_windows_shortcut(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_shortcut, :create, resource_name) + end + + def create_windows_auto_run(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_auto_run, :create, resource_name) + end + + def remove_windows_auto_run(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_auto_run, :remove, resource_name) + end + + def create_windows_printer(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_printer, :create, resource_name) + end + + def delete_windows_printer(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_printer, :delete, resource_name) + end + + def create_windows_printer_port(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_printer_port, :create, resource_name) + end + + def delete_windows_printer_port(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_printer_port, :delete, resource_name) + end + + def request_windows_reboot(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_reboot, :request, resource_name) + end + + def cancel_windows_reboot(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:windows_reboot, :cancel, resource_name) + end + +end diff --git a/cookbooks/windows/libraries/powershell_helper.rb b/cookbooks/windows/libraries/powershell_helper.rb new file mode 100644 index 0000000..0e42574 --- /dev/null +++ b/cookbooks/windows/libraries/powershell_helper.rb @@ -0,0 +1,59 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Library:: helper +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +require 'chef/mixin/shell_out' + +module Powershell + module Helper + include Chef::Mixin::ShellOut + + def powershell_installed? + !powershell_version.nil? + end + + def interpreter + # force 64-bit powershell from 32-bit ruby process + if ::File.exist?("#{ENV['WINDIR']}\\sysnative\\WindowsPowershell\\v1.0\\powershell.exe") + "#{ENV['WINDIR']}\\sysnative\\WindowsPowershell\\v1.0\\powershell.exe" + elsif ::File.exist?("#{ENV['WINDIR']}\\system32\\WindowsPowershell\\v1.0\\powershell.exe") + "#{ENV['WINDIR']}\\system32\\WindowsPowershell\\v1.0\\powershell.exe" + else + "powershell.exe" + end + end + + def powershell_version + begin + cmd = shell_out("#{interpreter} -InputFormat none -Command \"& echo $PSVersionTable.psversion.major\"") + if cmd.stdout.empty? # PowerShell 1.0 doesn't have a $PSVersionTable + 1 + else + if cmd.stdout =~ /^(\d+)/ + $1.to_i + else + nil + end + end + rescue Errno::ENOENT + nil + end + end + end +end diff --git a/cookbooks/windows/libraries/powershell_out.rb b/cookbooks/windows/libraries/powershell_out.rb new file mode 100644 index 0000000..9edeb57 --- /dev/null +++ b/cookbooks/windows/libraries/powershell_out.rb @@ -0,0 +1,79 @@ +class Chef + module Mixin + module PowershellOut + include Chef::Mixin::ShellOut + + begin + include Chef::Mixin::WindowsArchitectureHelper + rescue + # nothing to do, as the include will happen when windows_architecture_helper.rb + # is loaded. This is for ease of removal of that library when either + # powershell_out is core chef or powershell cookbook depends upon version + # of chef that has Chef::Mixin::WindowsArchitectureHelper in core chef + end + + def powershell_out(*command_args) + script = command_args.first + options = command_args.last.is_a?(Hash) ? command_args.last : nil + + run_command(script, options) + end + + def powershell_out!(*command_args) + cmd = powershell_out(*command_args) + cmd.error! + cmd + end + + private + def run_command(script, options) + if options && options[:architecture] + architecture = options[:architecture] + options.delete(:architecture) + else + architecture = node_windows_architecture(node) + end + + disable_redirection = wow64_architecture_override_required?(node, architecture) + + if disable_redirection + original_redirection_state = disable_wow64_file_redirection(node) + end + + command = build_command(script) + + if options + cmd = shell_out(command, options) + else + cmd = shell_out(command) + end + + if disable_redirection + restore_wow64_file_redirection(node, original_redirection_state) + end + + cmd + end + + def build_command(script) + flags = [ + # Hides the copyright banner at startup. + "-NoLogo", + # Does not present an interactive prompt to the user. + "-NonInteractive", + # Does not load the Windows PowerShell profile. + "-NoProfile", + # always set the ExecutionPolicy flag + # see http://technet.microsoft.com/en-us/library/ee176961.aspx + "-ExecutionPolicy RemoteSigned", + # Powershell will hang if STDIN is redirected + # http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected + "-InputFormat None" + ] + + command = "powershell.exe #{flags.join(' ')} -Command \"#{script}\"" + command + end + end + end +end diff --git a/cookbooks/windows/libraries/registry_helper.rb b/cookbooks/windows/libraries/registry_helper.rb new file mode 100644 index 0000000..a63df45 --- /dev/null +++ b/cookbooks/windows/libraries/registry_helper.rb @@ -0,0 +1,364 @@ +# +# Author:: Doug MacEachern () +# Author:: Seth Chisamore () +# Author:: Paul Morton () +# Cookbook Name:: windows +# Provider:: registry +# +# Copyright:: 2010, VMware, Inc. +# Copyright:: 2011, Chef Software, Inc. +# Copyright:: 2011, Business Intelligence Associates, Inc +# +# 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. +# + +if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + require 'win32/registry' + require_relative 'wmi_helper' +end + +module Windows + module RegistryHelper + + @@native_registry_constant = ENV['PROCESSOR_ARCHITEW6432'] == 'AMD64' ? 0x0100 : 0x0200 + + def get_hive_name(path) + Chef::Log.debug("Resolving registry shortcuts to full names") + + reg_path = path.split("\\") + hive_name = reg_path.shift + + hkey = { + "HKLM" => "HKEY_LOCAL_MACHINE", + "HKCU" => "HKEY_CURRENT_USER", + "HKU" => "HKEY_USERS" + }[hive_name] || hive_name + + Chef::Log.debug("Hive resolved to #{hkey}") + return hkey + end + + def get_hive(path) + + Chef::Log.debug("Getting hive for #{path}") + reg_path = path.split("\\") + hive_name = reg_path.shift + + hkey = get_hive_name(path) + + hive = { + "HKEY_LOCAL_MACHINE" => ::Win32::Registry::HKEY_LOCAL_MACHINE, + "HKEY_USERS" => ::Win32::Registry::HKEY_USERS, + "HKEY_CURRENT_USER" => ::Win32::Registry::HKEY_CURRENT_USER + }[hkey] + + unless hive + Chef::Application.fatal!("Unsupported registry hive '#{hive_name}'") + end + + + Chef::Log.debug("Registry hive resolved to #{hkey}") + return hive + end + + def unload_hive(path) + hive = get_hive(path) + if hive == ::Win32::Registry::HKEY_USERS + reg_path = path.split("\\") + priv = Chef::WindowsPrivileged.new + begin + priv.reg_unload_key(reg_path[1]) + rescue + end + end + end + + def set_value(mode,path,values,type=nil) + hive, reg_path, hive_name, root_key, hive_loaded = get_reg_path_info(path) + key_name = reg_path.join("\\") + + Chef::Log.debug("Creating #{path}") + + if !key_exists?(path,true) + create_key(path) + end + + hive.send(mode, key_name, ::Win32::Registry::KEY_ALL_ACCESS | @@native_registry_constant) do |reg| + changed_something = false + values.each do |k,val| + key = k.to_s #wtf. avoid "can't modify frozen string" in win32/registry.rb + cur_val = nil + begin + cur_val = reg[key] + rescue + #subkey does not exist (ok) + end + if cur_val != val + Chef::Log.debug("setting #{key}=#{val}") + + if type.nil? + type = :string + end + + reg_type = { + :binary => ::Win32::Registry::REG_BINARY, + :string => ::Win32::Registry::REG_SZ, + :multi_string => ::Win32::Registry::REG_MULTI_SZ, + :expand_string => ::Win32::Registry::REG_EXPAND_SZ, + :dword => ::Win32::Registry::REG_DWORD, + :dword_big_endian => ::Win32::Registry::REG_DWORD_BIG_ENDIAN, + :qword => ::Win32::Registry::REG_QWORD + }[type] + + reg.write(key, reg_type, val) + + ensure_hive_unloaded(hive_loaded) + + changed_something = true + end + end + return changed_something + end + return false + end + + def get_value(path,value) + hive, reg_path, hive_name, root_key, hive_loaded = get_reg_path_info(path) + key = reg_path.join("\\") + + hive.open(key, ::Win32::Registry::KEY_ALL_ACCESS | @@native_registry_constant) do | reg | + begin + return reg[value] + rescue + return nil + ensure + ensure_hive_unloaded(hive_loaded) + end + end + end + + def get_values(path) + hive, reg_path, hive_name, root_key, hive_loaded = get_reg_path_info(path) + key = reg_path.join("\\") + hive.open(key, ::Win32::Registry::KEY_ALL_ACCESS | @@native_registry_constant) do | reg | + values = [] + begin + reg.each_value do |name, type, data| + values << [name, type, data] + end + rescue + ensure + ensure_hive_unloaded(hive_loaded) + end + values + end + end + + def delete_value(path,values) + hive, reg_path, hive_name, root_key, hive_loaded = get_reg_path_info(path) + key = reg_path.join("\\") + Chef::Log.debug("Deleting values in #{path}") + hive.open(key, ::Win32::Registry::KEY_ALL_ACCESS | @@native_registry_constant) do | reg | + values.each_key { |key| + name = key.to_s + # Ensure delete operation is idempotent. + if value_exists?(path, key) + Chef::Log.debug("Deleting value #{name} in #{path}") + reg.delete_value(name) + else + Chef::Log.debug("Value #{name} in #{path} does not exist, skipping.") + end + } + end + + end + + def create_key(path) + hive, reg_path, hive_name, root_key, hive_loaded = get_reg_path_info(path) + key = reg_path.join("\\") + Chef::Log.debug("Creating registry key #{path}") + hive.create(key) + end + + def value_exists?(path,value) + if key_exists?(path,true) + + hive, reg_path, hive_name, root_key , hive_loaded = get_reg_path_info(path) + key = reg_path.join("\\") + + Chef::Log.debug("Attempting to open #{key}"); + Chef::Log.debug("Native Constant #{@@native_registry_constant}") + Chef::Log.debug("Hive #{hive}") + + hive.open(key, ::Win32::Registry::KEY_READ | @@native_registry_constant) do | reg | + begin + rtn_value = reg[value] + return true + rescue + return false + ensure + ensure_hive_unloaded(hive_loaded) + end + end + + end + return false + end + + # TODO: Does not load user registry... + def key_exists?(path, load_hive = false) + if load_hive + hive, reg_path, hive_name, root_key , hive_loaded = get_reg_path_info(path) + key = reg_path.join("\\") + else + hive = get_hive(path) + reg_path = path.split("\\") + hive_name = reg_path.shift + root_key = reg_path[0] + key = reg_path.join("\\") + hive_loaded = false + end + + begin + hive.open(key, ::Win32::Registry::Constants::KEY_READ | @@native_registry_constant ) + return true + rescue + return false + ensure + ensure_hive_unloaded(hive_loaded) + end + end + + def get_user_hive_location(sid) + reg_key = "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\#{sid}" + Chef::Log.debug("Looking for profile at #{reg_key}") + if key_exists?(reg_key) + return get_value(reg_key,'ProfileImagePath') + else + return nil + end + + end + + def resolve_user_to_sid(username) + begin + user_query = execute_wmi_query("select * from Win32_UserAccount where Name='#{username}'") + sid = nil + + user_query.each do |user| + sid = wmi_object_property(user, 'sid') + break + end + + Chef::Log.debug("Resolved user SID to #{sid}") + return sid + rescue + return nil + end + end + + def hive_loaded?(path) + hive = get_hive(path) + reg_path = path.split("\\") + hive_name = reg_path.shift + user_hive = path[0] + + if is_user_hive?(hive) + return key_exists?("#{hive_name}\\#{user_hive}") + else + return true + end + end + + def is_user_hive?(hive) + if hive == ::Win32::Registry::HKEY_USERS + return true + else + return true + end + end + + def get_reg_path_info(path) + hive = get_hive(path) + reg_path = path.split("\\") + hive_name = reg_path.shift + root_key = reg_path[0] + hive_loaded = false + + if is_user_hive?(hive) && !key_exists?("#{hive_name}\\#{root_key}") + reg_path, hive_loaded = load_user_hive(hive,reg_path,root_key) + root_key = reg_path[0] + Chef::Log.debug("Resolved user (#{path}) to (#{reg_path.join('/')})") + end + + return hive, reg_path, hive_name, root_key, hive_loaded + end + + def load_user_hive(hive,reg_path,user_hive) + Chef::Log.debug("Reg Path #{reg_path}") + # See if the hive is loaded. Logged in users will have a key that is named their SID + # if the user has specified the a path by SID and the user is logged in, this function + # should not be executed. + if is_user_hive?(hive) && !key_exists?("HKU\\#{user_hive}") + Chef::Log.debug("The user is not logged in and has not been specified by SID") + sid = resolve_user_to_sid(user_hive) + Chef::Log.debug("User SID resolved to (#{sid})") + # Now that the user has been resolved to a SID, check and see if the hive exists. + # If this exists by SID, the user is logged in and we should use that key. + # TODO: Replace the username with the sid and send it back because the username + # does not exist as the key location. + load_reg = false + if key_exists?("HKU\\#{sid}") + reg_path[0] = sid #use the active profile (user is logged on) + Chef::Log.debug("HKEY_USERS Mapped: #{user_hive} -> #{sid}") + else + Chef::Log.debug("User is not logged in") + load_reg = true + end + + # The user is not logged in, so we should load the registry from disk + if load_reg + profile_path = get_user_hive_location(sid) + if profile_path != nil + ntuser_dat = "#{profile_path}\\NTUSER.DAT" + if ::File.exists?(ntuser_dat) + priv = Chef::WindowsPrivileged.new + if priv.reg_load_key(sid,ntuser_dat) + Chef::Log.debug("RegLoadKey(#{sid}, #{user_hive}, #{ntuser_dat})") + reg_path[0] = sid + else + Chef::Log.debug("Failed RegLoadKey(#{sid}, #{user_hive}, #{ntuser_dat})") + end + end + end + end + end + + return reg_path, load_reg + + end + + private + def ensure_hive_unloaded(hive_loaded=false) + if(hive_loaded) + Chef::Log.debug("Hive was loaded, we really should unload it") + unload_hive(path) + end + end + end +end + +module Registry + module_function + extend Windows::RegistryHelper +end diff --git a/cookbooks/windows/libraries/version.rb b/cookbooks/windows/libraries/version.rb new file mode 100644 index 0000000..5dc802f --- /dev/null +++ b/cookbooks/windows/libraries/version.rb @@ -0,0 +1,207 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Library:: version +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + require_relative 'wmi_helper' + require 'Win32API' +end + +module Windows + class Version + + # http://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx + + # Suite Masks + # Microsoft BackOffice components are installed. + VER_SUITE_BACKOFFICE = 0x00000004.freeze unless defined?(VER_SUITE_BACKOFFICE) + # Windows Server 2003, Web Edition is installed. + VER_SUITE_BLADE = 0x00000400.freeze unless defined?(VER_SUITE_BLADE) + # Windows Server 2003, Compute Cluster Edition is installed. + VER_SUITE_COMPUTE_SERVER = 0x00004000.freeze unless defined?(VER_SUITE_COMPUTE_SERVER) + # Windows Server 2008 Datacenter, Windows Server 2003, Datacenter Edition, or Windows 2000 Datacenter Server is installed. + VER_SUITE_DATACENTER = 0x00000080.freeze unless defined?(VER_SUITE_DATACENTER) + # Windows Server 2008 Enterprise, Windows Server 2003, Enterprise Edition, or Windows 2000 Advanced Server is installed. Refer to the Remarks section for more information about this bit flag. + VER_SUITE_ENTERPRISE = 0x00000002.freeze unless defined?(VER_SUITE_ENTERPRISE) + # Windows XP Embedded is installed. + VER_SUITE_EMBEDDEDNT = 0x00000040.freeze unless defined?(VER_SUITE_EMBEDDEDNT) + # Windows Vista Home Premium, Windows Vista Home Basic, or Windows XP Home Edition is installed. + VER_SUITE_PERSONAL = 0x00000200.freeze unless defined?(VER_SUITE_PERSONAL) + # Remote Desktop is supported, but only one interactive session is supported. This value is set unless the system is running in application server mode. + VER_SUITE_SINGLEUSERTS = 0x00000100.freeze unless defined?(VER_SUITE_SINGLEUSERTS) + # Microsoft Small Business Server was once installed on the system, but may have been upgraded to another version of Windows. Refer to the Remarks section for more information about this bit flag. + VER_SUITE_SMALLBUSINESS = 0x00000001.freeze unless defined?(VER_SUITE_SMALLBUSINESS) + # Microsoft Small Business Server is installed with the restrictive client license in force. Refer to the Remarks section for more information about this bit flag. + VER_SUITE_SMALLBUSINESS_RESTRICTED = 0x00000020.freeze unless defined?(VER_SUITE_SMALLBUSINESS_RESTRICTED) + # Windows Storage Server 2003 R2 or Windows Storage Server 2003is installed. + VER_SUITE_STORAGE_SERVER = 0x00002000.freeze unless defined?(VER_SUITE_STORAGE_SERVER) + # Terminal Services is installed. This value is always set. + # If VER_SUITE_TERMINAL is set but VER_SUITE_SINGLEUSERTS is not set, the system is running in application server mode. + VER_SUITE_TERMINAL = 0x00000010.freeze unless defined?(VER_SUITE_TERMINAL) + # Windows Home Server is installed. + VER_SUITE_WH_SERVER = 0x00008000.freeze unless defined?(VER_SUITE_WH_SERVER) + + # Product Type + # The system is a domain controller and the operating system is Windows Server 2012, Windows Server 2008 R2, Windows Server 2008, Windows Server 2003, or Windows 2000 Server. + VER_NT_DOMAIN_CONTROLLER = 0x0000002.freeze unless defined?(VER_NT_DOMAIN_CONTROLLER) + # The operating system is Windows Server 2012, Windows Server 2008 R2, Windows Server 2008, Windows Server 2003, or Windows 2000 Server. + # Note that a server that is also a domain controller is reported as VER_NT_DOMAIN_CONTROLLER, not VER_NT_SERVER. + VER_NT_SERVER = 0x0000003.freeze unless defined?(VER_NT_SERVER) + # The operating system is Windows 7, Windows Vista, Windows XP Professional, Windows XP Home Edition, or Windows 2000 Professional. + VER_NT_WORKSTATION = 0x0000001.freeze unless defined?(VER_NT_WORKSTATION) + + # GetSystemMetrics + # The build number if the system is Windows Server 2003 R2; otherwise, 0. + SM_SERVERR2 = 89.freeze unless defined?(SM_SERVERR2) + + # http://msdn.microsoft.com/en-us/library/ms724358(v=vs.85).aspx + SKU = { + 0x00000006 => {:ms_const => 'PRODUCT_BUSINESS', :name => 'Business'}, + 0x00000010 => {:ms_const => 'PRODUCT_BUSINESS_N', :name => 'Business N'}, + 0x00000012 => {:ms_const => 'PRODUCT_CLUSTER_SERVER', :name => 'HPC Edition'}, + 0x00000008 => {:ms_const => 'PRODUCT_DATACENTER_SERVER', :name => 'Server Datacenter (full installation)'}, + 0x0000000C => {:ms_const => 'PRODUCT_DATACENTER_SERVER_CORE', :name => 'Server Datacenter (core installation)'}, + 0x00000027 => {:ms_const => 'PRODUCT_DATACENTER_SERVER_CORE_V', :name => 'Server Datacenter without Hyper-V (core installation)'}, + 0x00000025 => {:ms_const => 'PRODUCT_DATACENTER_SERVER_V', :name => 'Server Datacenter without Hyper-V (full installation)'}, + 0x00000004 => {:ms_const => 'PRODUCT_ENTERPRISE', :name => 'Enterprise'}, + 0x00000046 => {:ms_const => 'PRODUCT_ENTERPRISE_E', :name => 'Not supported'}, + 0x0000001B => {:ms_const => 'PRODUCT_ENTERPRISE_N', :name => 'Enterprise N'}, + 0x0000000A => {:ms_const => 'PRODUCT_ENTERPRISE_SERVER', :name => 'Server Enterprise (full installation)'}, + 0x0000000E => {:ms_const => 'PRODUCT_ENTERPRISE_SERVER_CORE', :name => 'Server Enterprise (core installation)'}, + 0x00000029 => {:ms_const => 'PRODUCT_ENTERPRISE_SERVER_CORE_V', :name => 'Server Enterprise without Hyper-V (core installation)'}, + 0x0000000F => {:ms_const => 'PRODUCT_ENTERPRISE_SERVER_IA64', :name => 'Server Enterprise for Itanium-based Systems'}, + 0x00000026 => {:ms_const => 'PRODUCT_ENTERPRISE_SERVER_V', :name => 'Server Enterprise without Hyper-V (full installation)'}, + 0x00000002 => {:ms_const => 'PRODUCT_HOME_BASIC', :name => 'Home Basic'}, + 0x00000043 => {:ms_const => 'PRODUCT_HOME_BASIC_E', :name => 'Not supported'}, + 0x00000005 => {:ms_const => 'PRODUCT_HOME_BASIC_N', :name => 'Home Basic N'}, + 0x00000003 => {:ms_const => 'PRODUCT_HOME_PREMIUM', :name => 'Home Premium'}, + 0x00000044 => {:ms_const => 'PRODUCT_HOME_PREMIUM_E', :name => 'Not supported'}, + 0x0000001A => {:ms_const => 'PRODUCT_HOME_PREMIUM_N', :name => 'Home Premium N'}, + 0x0000002A => {:ms_const => 'PRODUCT_HYPERV', :name => 'Microsoft Hyper-V Server'}, + 0x0000001E => {:ms_const => 'PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT', :name => 'Windows Essential Business Server Management Server'}, + 0x00000020 => {:ms_const => 'PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING', :name => 'Windows Essential Business Server Messaging Server'}, + 0x0000001F => {:ms_const => 'PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY', :name => 'Windows Essential Business Server Security Server'}, + 0x00000030 => {:ms_const => 'PRODUCT_PROFESSIONAL', :name => 'Professional'}, + 0x00000045 => {:ms_const => 'PRODUCT_PROFESSIONAL_E', :name => 'Not supported'}, + 0x00000031 => {:ms_const => 'PRODUCT_PROFESSIONAL_N', :name => 'Professional N'}, + 0x00000067 => {:ms_const => 'PRODUCT_PROFESSIONAL_WMC', :name => 'Professional with Media Center'}, + 0x00000018 => {:ms_const => 'PRODUCT_SERVER_FOR_SMALLBUSINESS', :name => 'Windows Server 2008 for Windows Essential Server Solutions'}, + 0x00000023 => {:ms_const => 'PRODUCT_SERVER_FOR_SMALLBUSINESS_V', :name => 'Windows Server 2008 without Hyper-V for Windows Essential Server Solutions'}, + 0x00000021 => {:ms_const => 'PRODUCT_SERVER_FOUNDATION', :name => 'Server Foundation'}, + 0x00000022 => {:ms_const => 'PRODUCT_HOME_PREMIUM_SERVER', :name => 'Windows Home Server 2011'}, + 0x00000032 => {:ms_const => 'PRODUCT_SB_SOLUTION_SERVER', :name => 'Windows Small Business Server 2011 Essentials'}, + 0x00000013 => {:ms_const => 'PRODUCT_HOME_SERVER', :name => 'Windows Storage Server 2008 R2 Essentials'}, + 0x00000009 => {:ms_const => 'PRODUCT_SMALLBUSINESS_SERVER', :name => 'Windows Small Business Server'}, + 0x00000038 => {:ms_const => 'PRODUCT_SOLUTION_EMBEDDEDSERVER', :name => 'Windows MultiPoint Server'}, + 0x00000007 => {:ms_const => 'PRODUCT_STANDARD_SERVER', :name => 'Server Standard (full installation)'}, + 0x0000000D => {:ms_const => 'PRODUCT_STANDARD_SERVER_CORE', :name => 'Server Standard (core installation)'}, + 0x00000028 => {:ms_const => 'PRODUCT_STANDARD_SERVER_CORE_V', :name => 'Server Standard without Hyper-V (core installation)'}, + 0x00000024 => {:ms_const => 'PRODUCT_STANDARD_SERVER_V', :name => 'Server Standard without Hyper-V (full installation)'}, + 0x0000000B => {:ms_const => 'PRODUCT_STARTER', :name => 'Starter'}, + 0x00000042 => {:ms_const => 'PRODUCT_STARTER_E', :name => 'Not supported'}, + 0x0000002F => {:ms_const => 'PRODUCT_STARTER_N', :name => 'Starter N'}, + 0x00000017 => {:ms_const => 'PRODUCT_STORAGE_ENTERPRISE_SERVER', :name => 'Storage Server Enterprise'}, + 0x00000014 => {:ms_const => 'PRODUCT_STORAGE_EXPRESS_SERVER', :name => 'Storage Server Express'}, + 0x00000015 => {:ms_const => 'PRODUCT_STORAGE_STANDARD_SERVER', :name => 'Storage Server Standard'}, + 0x00000016 => {:ms_const => 'PRODUCT_STORAGE_WORKGROUP_SERVER', :name => 'Storage Server Workgroup'}, + 0x00000000 => {:ms_const => 'PRODUCT_UNDEFINED', :name => 'An unknown product'}, + 0x00000001 => {:ms_const => 'PRODUCT_ULTIMATE', :name => 'Ultimate'}, + 0x00000047 => {:ms_const => 'PRODUCT_ULTIMATE_E', :name => 'Not supported'}, + 0x0000001C => {:ms_const => 'PRODUCT_ULTIMATE_N', :name => 'Ultimate N'}, + 0x00000011 => {:ms_const => 'PRODUCT_WEB_SERVER', :name => 'Web Server (full installation)'}, + 0x0000001D => {:ms_const => 'PRODUCT_WEB_SERVER_CORE', :name => 'Web Server (core installation)'} + }.freeze unless defined?(SKU) + + attr_reader :major_version, :minor_version, :build_number, :service_pack_major_version, :service_pack_minor_version + attr_reader :version, :product_type, :product_suite, :sku + + def initialize + unless RUBY_PLATFORM =~ /mswin|mingw32|windows/ + raise NotImplementedError, 'only valid on Windows platform' + end + @version, @product_type, @product_suite, @sku, @service_pack_major_version, @service_pack_minor_version = get_os_info + @major_version, @minor_version, @build_number = version.split('.').map{|v| v.to_i } + end + + WIN_VERSIONS = { + "Windows Server 2012 R2" => {:major => 6, :minor => 3, :callable => lambda{ @product_type != VER_NT_WORKSTATION }}, + "Windows 8" => {:major => 6, :minor => 2, :callable => lambda{ @product_type == VER_NT_WORKSTATION }}, + "Windows Server 2012" => {:major => 6, :minor => 2, :callable => lambda{ @product_type != VER_NT_WORKSTATION }}, + "Windows 7" => {:major => 6, :minor => 1, :callable => lambda{ @product_type == VER_NT_WORKSTATION }}, + "Windows Server 2008 R2" => {:major => 6, :minor => 1, :callable => lambda{ @product_type != VER_NT_WORKSTATION }}, + "Windows Server 2008" => {:major => 6, :minor => 0, :callable => lambda{ @product_type != VER_NT_WORKSTATION }}, + "Windows Vista" => {:major => 6, :minor => 0, :callable => lambda{ @product_type == VER_NT_WORKSTATION }}, + "Windows Server 2003 R2" => {:major => 5, :minor => 2, :callable => lambda{ Win32API.new('user32', 'GetSystemMetrics', 'I', 'I').call(SM_SERVERR2) != 0 }}, + "Windows Home Server" => {:major => 5, :minor => 2, :callable => lambda{ (@product_suite & VER_SUITE_WH_SERVER) == VER_SUITE_WH_SERVER }}, + "Windows Server 2003" => {:major => 5, :minor => 2, :callable => lambda{ Win32API.new('user32', 'GetSystemMetrics', 'I', 'I').call(SM_SERVERR2) == 0 }}, + "Windows XP" => {:major => 5, :minor => 1}, + "Windows 2000" => {:major => 5, :minor => 0} + }.freeze unless defined?(WIN_VERSIONS) + + marketing_names = Array.new + + # General Windows checks + WIN_VERSIONS.each do |k,v| + method_name = "#{k.gsub(/\s/, '_').downcase}?" + define_method(method_name) do + (@major_version == v[:major]) && + (@minor_version == v[:minor]) && + (v[:callable] ? v[:callable].call : true) + end + marketing_names << [k, method_name] + end + + define_method(:marketing_name) do + marketing_names.each do |mn| + break mn[0] if self.send(mn[1]) + end + end + + # Server Type checks + %w{ core full datacenter }.each do |m| + define_method("server_#{m}?") do + if @sku + !(SKU[@sku][:name] =~ /#{m}/i).nil? + else + false + end + end + end + + private + # Win32API call to GetSystemMetrics(SM_SERVERR2) + # returns: The build number if the system is Windows Server 2003 R2; otherwise, 0. + def sm_serverr2 + @sm_serverr2 ||= Win32API.new('user32', 'GetSystemMetrics', 'I', 'I').call(SM_SERVERR2) + end + + # query WMI Win32_OperatingSystem for required OS info + def get_os_info + cols = %w{ Version ProductType OSProductSuite OperatingSystemSKU ServicePackMajorVersion ServicePackMinorVersion } + os_info = execute_wmi_query("select * from Win32_OperatingSystem").each.next + cols.map do |c| + begin + wmi_object_property(os_info, c) + rescue # OperatingSystemSKU doesn't exist in all versions of Windows + nil + end + end + end + end +end diff --git a/cookbooks/windows/libraries/windows_architecture_helper.rb b/cookbooks/windows/libraries/windows_architecture_helper.rb new file mode 100644 index 0000000..e2c2213 --- /dev/null +++ b/cookbooks/windows/libraries/windows_architecture_helper.rb @@ -0,0 +1,87 @@ +# Try to include from core chef, if error then monkey patch it in. + +begin + include Chef::Mixin::WindowsArchitectureHelper +rescue + Chef::Log.debug("Chef::Mixin::WindowsArchitectureHelper not in core version, Monkey patching in.") + + require 'chef/exceptions' + require 'win32/api' if Chef::Platform.windows? + + class Chef + module Mixin + module WindowsArchitectureHelper + + def node_windows_architecture(node) + node['kernel']['machine'].to_sym + end + + def wow64_architecture_override_required?(node, desired_architecture) + is_i386_windows_process? && + node_windows_architecture(node) == :x86_64 && + desired_architecture == :x86_64 + end + + def node_supports_windows_architecture?(node, desired_architecture) + assert_valid_windows_architecture!(desired_architecture) + return (node_windows_architecture(node) == :x86_64 || + desired_architecture == :i386) ? true : false + end + + def valid_windows_architecture?(architecture) + return (architecture == :x86_64) || (architecture == :i386) + end + + def assert_valid_windows_architecture!(architecture) + if ! valid_windows_architecture?(architecture) + raise Chef::Exceptions::Win32ArchitectureIncorrect, + "The specified architecture was not valid. It must be one of :i386 or :x86_64" + end + end + + def is_i386_windows_process? + Chef::Platform.windows? && 'X86'.casecmp(ENV['PROCESSOR_ARCHITECTURE']) == 0 + end + + def disable_wow64_file_redirection(node) + original_redirection_state = ['0'].pack('P') + + if ((node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?) + win32_wow_64_disable_wow_64_fs_redirection = + ::Win32::API.new('Wow64DisableWow64FsRedirection', 'P', 'L', 'kernel32') + + succeeded = win32_wow_64_disable_wow_64_fs_redirection.call(original_redirection_state) + + if succeeded == 0 + raise Win32APIError "Failed to disable Wow64 file redirection" + end + + end + + original_redirection_state + end + + def restore_wow64_file_redirection(node, original_redirection_state) + if ( (node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?) + win32_wow_64_revert_wow_64_fs_redirection = + ::Win32::API.new('Wow64RevertWow64FsRedirection', 'P', 'L', 'kernel32') + + succeeded = win32_wow_64_revert_wow_64_fs_redirection.call(original_redirection_state) + + if succeeded == 0 + raise Win32APIError "Failed to revert Wow64 file redirection" + end + end + end + end + end + end +end + +# Making sure this library is available to Chef::Mixin::PowershellOut +# Required for clients that don't have Chef::Mixin::WindowsArchitectureHelper in +# core chef. +if ::Chef::Platform.windows? + require_relative 'powershell_out' + Chef::Mixin::PowershellOut.send(:include, Chef::Mixin::WindowsArchitectureHelper) +end diff --git a/cookbooks/windows/libraries/windows_helper.rb b/cookbooks/windows/libraries/windows_helper.rb new file mode 100644 index 0000000..f19cf62 --- /dev/null +++ b/cookbooks/windows/libraries/windows_helper.rb @@ -0,0 +1,148 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Library:: helper +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. + +require 'uri' +require 'Win32API' if Chef::Platform.windows? +require 'chef/exceptions' + +module Windows + module Helper + + AUTO_RUN_KEY = 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'.freeze unless defined?(AUTO_RUN_KEY) + ENV_KEY = 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'.freeze unless defined?(ENV_KEY) + ExpandEnvironmentStrings = Win32API.new('kernel32', 'ExpandEnvironmentStrings', ['P', 'P', 'L'], 'L') if Chef::Platform.windows? + + # returns windows friendly version of the provided path, + # ensures backslashes are used everywhere + def win_friendly_path(path) + path.gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR || '\\') if path + end + + # account for Window's wacky File System Redirector + # http://msdn.microsoft.com/en-us/library/aa384187(v=vs.85).aspx + # especially important for 32-bit processes (like Ruby) on a + # 64-bit instance of Windows. + def locate_sysnative_cmd(cmd) + if ::File.exists?("#{ENV['WINDIR']}\\sysnative\\#{cmd}") + "#{ENV['WINDIR']}\\sysnative\\#{cmd}" + elsif ::File.exists?("#{ENV['WINDIR']}\\system32\\#{cmd}") + "#{ENV['WINDIR']}\\system32\\#{cmd}" + else + cmd + end + end + + # Create a feature provider dependent value object. + # mainly created becasue Windows Feature names are + # different based on whether dism.exe or servicemanagercmd.exe + # is used for installation + def value_for_feature_provider(provider_hash) + p = Chef::Platform.find_provider_for_node(node, :windows_feature) + key = p.to_s.downcase.split('::').last + provider_hash[key] || provider_hash[key.to_sym] + end + + # singleton instance of the Windows Version checker + def win_version + @win_version ||= Windows::Version.new + end + + # if a file is local it returns a windows friendly path version + # if a file is remote it caches it locally + def cached_file(source, checksum=nil, windows_path=true) + @installer_file_path ||= begin + + if source =~ ::URI::ABS_URI && %w[ftp http https].include?(URI.parse(source).scheme) + uri = ::URI.parse(source) + cache_file_path = "#{Chef::Config[:file_cache_path]}/#{::File.basename(::URI.unescape(uri.path))}" + Chef::Log.debug("Caching a copy of file #{source} at #{cache_file_path}") + r = Chef::Resource::RemoteFile.new(cache_file_path, run_context) + r.source(source) + r.backup(false) + r.checksum(checksum) if checksum + r.run_action(:create) + else + cache_file_path = source + end + + windows_path ? win_friendly_path(cache_file_path) : cache_file_path + end + end + + # Expands the environment variables + def expand_env_vars(path) + # We pick 32k because that is the largest it could be: + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724265%28v=vs.85%29.aspx + buf = 0.chr * 32 * 1024 # 32k + if ExpandEnvironmentStrings.call(path.dup, buf, buf.length) == 0 + raise Chef::Exceptions::Win32APIError, "Failed calling ExpandEnvironmentStrings (received 0)" + end + buf.strip + end + + def is_package_installed?(package_name) + installed_packages.include?(package_name) + end + + def installed_packages + @installed_packages || begin + installed_packages = {} + # Computer\HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall + installed_packages.merge!(extract_installed_packages_from_key(::Win32::Registry::HKEY_LOCAL_MACHINE)) #rescue nil + # 64-bit registry view + # Computer\HKEY_LOCAL_MACHINE\Software\Wow6464Node\Microsoft\Windows\CurrentVersion\Uninstall + installed_packages.merge!(extract_installed_packages_from_key(::Win32::Registry::HKEY_LOCAL_MACHINE, (::Win32::Registry::Constants::KEY_READ | 0x0100))) #rescue nil + # 32-bit registry view + # Computer\HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall + installed_packages.merge!(extract_installed_packages_from_key(::Win32::Registry::HKEY_LOCAL_MACHINE, (::Win32::Registry::Constants::KEY_READ | 0x0200))) #rescue nil + # Computer\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall + installed_packages.merge!(extract_installed_packages_from_key(::Win32::Registry::HKEY_CURRENT_USER)) #rescue nil + installed_packages + end + end + + private + def extract_installed_packages_from_key(hkey = ::Win32::Registry::HKEY_LOCAL_MACHINE, desired = ::Win32::Registry::Constants::KEY_READ) + uninstall_subkey = 'Software\Microsoft\Windows\CurrentVersion\Uninstall' + packages = {} + begin + ::Win32::Registry.open(hkey, uninstall_subkey, desired) do |reg| + reg.each_key do |key, wtime| + begin + k = reg.open(key, desired) + display_name = k["DisplayName"] rescue nil + version = k["DisplayVersion"] rescue "NO VERSION" + uninstall_string = k["UninstallString"] rescue nil + if display_name + packages[display_name] = {:name => display_name, + :version => version, + :uninstall_string => uninstall_string} + end + rescue ::Win32::Registry::Error + end + end + end + rescue ::Win32::Registry::Error + end + packages + end + end +end + +Chef::Recipe.send(:include, Windows::Helper) diff --git a/cookbooks/windows/libraries/windows_package.rb b/cookbooks/windows/libraries/windows_package.rb new file mode 100644 index 0000000..cfa26a1 --- /dev/null +++ b/cookbooks/windows/libraries/windows_package.rb @@ -0,0 +1,224 @@ +require 'chef/resource/lwrp_base' +require 'chef/provider/lwrp_base' + +if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + require 'win32/registry' +end + +require 'chef/mixin/shell_out' +require 'chef/mixin/language' +class Chef + class Provider + class WindowsCookbookPackage < Chef::Provider::LWRPBase + include Chef::Mixin::ShellOut + include Windows::Helper + + # the logic in all action methods mirror that of + # the Chef::Provider::Package which will make + # refactoring into core chef easy + + action :install do + # If we specified a version, and it's not the current version, move to the specified version + if @new_resource.version != nil && @new_resource.version != @current_resource.version + install_version = @new_resource.version + # If it's not installed at all, install it + elsif @current_resource.version == nil + install_version = candidate_version + end + + if install_version + Chef::Log.info("Installing #{@new_resource} version #{install_version}") + status = install_package(@new_resource.package_name, install_version) + if status + new_resource.updated_by_last_action(true) + end + end + end + + action :upgrade do + if @current_resource.version != candidate_version + orig_version = @current_resource.version || "uninstalled" + Chef::Log.info("Upgrading #{@new_resource} version from #{orig_version} to #{candidate_version}") + status = upgrade_package(@new_resource.package_name, candidate_version) + if status + new_resource.updated_by_last_action(true) + end + end + end + + action :remove do + if removing_package? + Chef::Log.info("Removing #{@new_resource}") + remove_package(@current_resource.package_name, @new_resource.version) + new_resource.updated_by_last_action(true) + else + end + end + + def removing_package? + if @current_resource.version.nil? + false # nothing to remove + elsif @new_resource.version.nil? + true # remove any version of a package + elsif @new_resource.version == @current_resource.version + true # remove the version we have + else + false # we don't have the version we want to remove + end + end + + def expand_options(options) + options ? " #{options}" : "" + end + + # these methods are the required overrides of + # a provider that extends from Chef::Provider::Package + # so refactoring into core Chef should be easy + + def load_current_resource + @current_resource = Chef::Resource::WindowsPackage.new(@new_resource.name) + @current_resource.package_name(@new_resource.package_name) + @current_resource.version(nil) + + unless current_installed_version.nil? + @current_resource.version(current_installed_version) + end + + @current_resource + end + + def current_installed_version + @current_installed_version ||= begin + if installed_packages.include?(@new_resource.package_name) + installed_packages[@new_resource.package_name][:version] + end + end + end + + def candidate_version + @candidate_version ||= begin + @new_resource.version || 'latest' + end + end + + def install_package(name,version) + Chef::Log.debug("Processing #{@new_resource} as a #{installer_type} installer.") + install_args = [cached_file(@new_resource.source, @new_resource.checksum), expand_options(unattended_installation_flags), expand_options(@new_resource.options)] + Chef::Log.info("Starting installation...this could take awhile.") + Chef::Log.debug "Install command: #{ sprintf(install_command_template, *install_args) }" + shell_out!(sprintf(install_command_template, *install_args), {:timeout => @new_resource.timeout, :returns => @new_resource.success_codes}) + end + + def remove_package(name, version) + uninstall_string = installed_packages[@new_resource.package_name][:uninstall_string] + Chef::Log.info("Registry provided uninstall string for #{@new_resource} is '#{uninstall_string}'") + uninstall_command = begin + if uninstall_string =~ /msiexec/i + "#{uninstall_string} /qn" + else + uninstall_string.gsub!('"','') + "start \"\" /wait /d\"#{::File.dirname(uninstall_string)}\" #{::File.basename(uninstall_string)}#{expand_options(@new_resource.options)} /S & exit %%%%ERRORLEVEL%%%%" + end + end + Chef::Log.info("Removing #{@new_resource} with uninstall command '#{uninstall_command}'") + shell_out!(uninstall_command, {:returns => @new_resource.success_codes}) + end + + private + + def install_command_template + case installer_type + when :msi + "msiexec%2$s \"%1$s\"%3$s" + else + "start \"\" /wait \"%1$s\"%2$s%3$s & exit %%%%ERRORLEVEL%%%%" + end + end + + + # http://unattended.sourceforge.net/installers.php + def unattended_installation_flags + case installer_type + when :msi + # this is no-ui + "/qn /i" + when :installshield + "/s /sms" + when :nsis + "/S /NCRC" + when :inno + #"/sp- /silent /norestart" + "/verysilent /norestart" + when :wise + "/s" + else + end + end + + def installer_type + @installer_type || begin + if @new_resource.installer_type + @new_resource.installer_type + else + basename = ::File.basename(cached_file(@new_resource.source, @new_resource.checksum)) + if basename.split(".").last.downcase == "msi" # Microsoft MSI + :msi + else + # search the binary file for installer type + contents = ::Kernel.open(::File.expand_path(cached_file(@new_resource.source)), "rb") {|io| io.read } # TODO limit data read in + case contents + when /inno/i # Inno Setup + :inno + when /wise/i # Wise InstallMaster + :wise + when /nsis/i # Nullsoft Scriptable Install System + :nsis + else + # if file is named 'setup.exe' assume installshield + if basename == "setup.exe" + :installshield + else + raise Chef::Exceptions::AttributeNotFound, "installer_type could not be determined, please set manually" + end + end + end + end + end + end + end + end +end + + +class Chef + class Resource + class WindowsCookbookPackage < Chef::Resource::LWRPBase + if Gem::Version.new(Chef::VERSION) >= Gem::Version.new('12') + provides :windows_package, os: "windows" + end + actions :install, :remove + + default_action :install + + attribute :package_name, :kind_of => String, :name_attribute => true + attribute :source, :kind_of => String, :required => true + attribute :version, :kind_of => String + attribute :options, :kind_of => String + attribute :installer_type, :kind_of => Symbol, :default => nil, :equal_to => [:msi, :inno, :nsis, :wise, :installshield, :custom] + attribute :checksum, :kind_of => String + attribute :timeout, :kind_of => Integer, :default => 600 + attribute :success_codes, :kind_of => Array, :default => [0, 42, 127] + + self.resource_name = 'windows_package' + def initialize(*args) + super + @provider = Chef::Provider::WindowsCookbookPackage + end + end + end +end + +if Gem::Version.new(Chef::VERSION) < Gem::Version.new('12') + Chef::Resource.send(:remove_const, :WindowsPackage) if defined? Chef::Resource::WindowsPackage + Chef::Resource.const_set("WindowsPackage", Chef::Resource::WindowsCookbookPackage) +end diff --git a/cookbooks/windows/libraries/windows_privileged.rb b/cookbooks/windows/libraries/windows_privileged.rb new file mode 100644 index 0000000..f868835 --- /dev/null +++ b/cookbooks/windows/libraries/windows_privileged.rb @@ -0,0 +1,94 @@ +# +# Author:: Doug MacEachern +# Author:: Paul Morton () +# Cookbook Name:: windows +# Library:: windows_privileged +# +# Copyright:: 2010, VMware, Inc. +# Copyright:: 2011, Business Intelligence Associates, Inc +# +# 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. +# + +if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + require 'windows/error' + require 'windows/registry' + require 'windows/process' + require 'windows/security' +end + +#helpers for Windows API calls that require privilege adjustments +class Chef + class WindowsPrivileged + if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + include Windows::Error + include Windows::Registry + include Windows::Process + include Windows::Security + end + #File -> Load Hive... in regedit.exe + def reg_load_key(name, file) + run(SE_BACKUP_NAME, SE_RESTORE_NAME) do + rc = RegLoadKey(HKEY_USERS, name.to_s, file) + if rc == ERROR_SUCCESS + return true + elsif rc == ERROR_SHARING_VIOLATION + return false + else + raise get_last_error(rc) + end + end + end + + #File -> Unload Hive... in regedit.exe + def reg_unload_key(name) + run(SE_BACKUP_NAME, SE_RESTORE_NAME) do + rc = RegUnLoadKey(HKEY_USERS, name.to_s) + if rc != ERROR_SUCCESS + raise get_last_error(rc) + end + end + end + + def run(*privileges) + token = [0].pack('L') + + unless OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, token) + raise get_last_error + end + token = token.unpack('L')[0] + + privileges.each do |name| + unless adjust_privilege(token, name, SE_PRIVILEGE_ENABLED) + raise get_last_error + end + end + + begin + yield + ensure #disable privs + privileges.each do |name| + adjust_privilege(token, name, 0) + end + end + end + + def adjust_privilege(token, priv, attr=0) + luid = [0,0].pack('Ll') + if LookupPrivilegeValue(nil, priv, luid) + new_state = [1, luid.unpack('Ll'), attr].flatten.pack('LLlL') + AdjustTokenPrivileges(token, 0, new_state, new_state.size, 0, 0) + end + end + end +end diff --git a/cookbooks/windows/libraries/wmi_helper.rb b/cookbooks/windows/libraries/wmi_helper.rb new file mode 100644 index 0000000..e65c41a --- /dev/null +++ b/cookbooks/windows/libraries/wmi_helper.rb @@ -0,0 +1,32 @@ +# +# Author:: Adam Edwards () +# +# Copyright:: 2014, Chef Software, Inc. +# +# 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. +# + +if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + require 'win32ole' + + def execute_wmi_query(wmi_query) + wmi = ::WIN32OLE.connect("winmgmts://") + result = wmi.ExecQuery(wmi_query) + return nil unless result.each.count > 0 + result + end + + def wmi_object_property(wmi_object, wmi_property) + wmi_object.send(wmi_property) + end +end diff --git a/cookbooks/windows/metadata.json b/cookbooks/windows/metadata.json new file mode 100644 index 0000000..617ddea --- /dev/null +++ b/cookbooks/windows/metadata.json @@ -0,0 +1,31 @@ +{ + "name": "windows", + "version": "1.36.6", + "description": "Provides a set of useful Windows-specific primitives.", + "long_description": "Windows Cookbook\n================\nProvides a set of Windows-specific primitives (Chef resources) meant to aid in the creation of cookbooks/recipes targeting the Windows platform.\n\n\nRequirements\n-------------\nVersion 1.3.0+ of this cookbook requires Chef 0.10.10+.\n\n\n### Platforms\n* Windows XP\n* Windows Vista\n* Windows Server 2003 R2\n* Windows 7\n* Windows Server 2008 (R1, R2)\n\nThe `windows_task` LWRP requires Windows Server 2008 due to its API usage.\n\n### Cookbooks\nThe following cookbooks provided by Chef Software are required as noted:\n\n* chef_handler (`windows::reboot_handler` leverages the chef_handler LWRP)\n\nAttributes\n----------\n* `node['windows']['allow_pending_reboots']` - used to configure the `WindowsRebootHandler` (via the `windows::reboot_handler` recipe) to act on pending reboots. default is true (ie act on pending reboots). The value of this attribute only has an effect if the `windows::reboot_handler` is in a node's run list.\n* `node['windows']['allow_reboot_on_failure']` - used to register the `WindowsRebootHandler` (via the `windows::reboot_handler` recipe) as an exception handler too to act on reboots not only at the end of successful Chef runs, but even at the end of failed runs. default is false (ie reboot only after successful runs). The value of this attribute only has an effect if the `windows::reboot_handler` is in a node's run list.\n\n\nResource/Provider\n-----------------\n### windows_auto_run\n#### Actions\n- :create: Create an item to be run at login\n- :remove: Remove an item that was previously setup to run at login\n\n#### Attribute Parameters\n- :name: Name attribute. The name of the value to be stored in the registry\n- :program: The program to be run at login\n- :args: The arguments for the program\n\n#### Examples\nRun BGInfo at login\n\n```ruby\nwindows_auto_run 'BGINFO' do\n program 'C:/Sysinternals/bginfo.exe'\n args '\\'C:/Sysinternals/Config.bgi\\' /NOLICPROMPT /TIMER:0'\n not_if { Registry.value_exists?(AUTO_RUN_KEY, 'BGINFO') }\n action :create\nend\n```\n\n### windows_batch\n(Chef 11.6.0 includes a built-in [batch](http://docs.chef.io/resource_batch.html) resource, so use that in preference to `windows_batch` if possible.)\n\nExecute a batch script using the cmd.exe interpreter (much like the script resources for bash, csh, powershell, perl, python and ruby). A temporary file is created and executed like other script resources, rather than run inline. By their nature, Script resources are not idempotent, as they are completely up to the user's imagination. Use the `not_if` or `only_if` meta parameters to guard the resource for idempotence.\n\n#### Actions\n- :run: run the batch file\n\n#### Attribute Parameters\n- command: name attribute. Name of the command to execute.\n- code: quoted string of code to execute.\n- creates: a file this command creates - if the file exists, the command will not be run.\n- cwd: current working directory to run the command from.\n- flags: command line flags to pass to the interpreter when invoking.\n- user: A user name or user ID that we should change to before running this command.\n- group: A group name or group ID that we should change to before running this command.\n\n#### Examples\n```ruby\nwindows_batch 'unzip_and_move_ruby' do\n code <<-EOH\n 7z.exe x #{Chef::Config[:file_cache_path]}/ruby-1.8.7-p352-i386-mingw32.7z -oC:\\\\source -r -y\n xcopy C:\\\\source\\\\ruby-1.8.7-p352-i386-mingw32 C:\\\\ruby /e /y\n EOH\nend\n```\n\n```ruby\nwindows_batch 'echo some env vars' do\n code <<-EOH\n echo %TEMP%\n echo %SYSTEMDRIVE%\n echo %PATH%\n echo %WINDIR%\n EOH\nend\n```\n\n### windows_feature\nWindows Roles and Features can be thought of as built-in operating system packages that ship with the OS. A server role is a set of software programs that, when they are installed and properly configured, lets a computer perform a specific function for multiple users or other computers within a network. A Role can have multiple Role Services that provide functionality to the Role. Role services are software programs that provide the functionality of a role. Features are software programs that, although they are not directly parts of roles, can support or augment the functionality of one or more roles, or improve the functionality of the server, regardless of which roles are installed. Collectively we refer to all of these attributes as 'features'.\n\nThis resource allows you to manage these 'features' in an unattended, idempotent way.\n\nThere are two providers for the `windows_features` which map into Microsoft's two major tools for managing roles/features: [Deployment Image Servicing and Management (DISM)](http://msdn.microsoft.com/en-us/library/dd371719%28v=vs.85%29.aspx) and [Servermanagercmd](http://technet.microsoft.com/en-us/library/ee344834%28WS.10%29.aspx) (The CLI for Server Manager). As Servermanagercmd is deprecated, Chef will set the default provider to `Chef::Provider::WindowsFeature::DISM` if DISM is present on the system being configured. The default provider will fall back to `Chef::Provider::WindowsFeature::ServerManagerCmd`.\n\nFor more information on Roles, Role Services and Features see the [Microsoft TechNet article on the topic](http://technet.microsoft.com/en-us/library/cc754923.aspx). For a complete list of all features that are available on a node type either of the following commands at a command prompt:\n\n```text\ndism /online /Get-Features\nservermanagercmd -query\n```\n\n#### Actions\n- :install: install a Windows role/feature\n- :remove: remove a Windows role/feature\n\n#### Attribute Parameters\n- feature_name: name of the feature/role to install. The same feature may have different names depending on the provider used (ie DHCPServer vs DHCP; DNS-Server-Full-Role vs DNS).\n- all: Boolean. Optional. Default: false. DISM provider only. Forces all dependencies to be installed.\n- source: String. Optional. DISM provider only. Uses local repository for feature install.\n\n#### Providers\n- **Chef::Provider::WindowsFeature::DISM**: Uses Deployment Image Servicing and Management (DISM) to manage roles/features.\n- **Chef::Provider::WindowsFeature::ServerManagerCmd**: Uses Server Manager to manage roles/features.\n- **Chef::Provider::WindowsFeaturePowershell**: Uses Powershell to manage roles/features. (see [COOK-3714](https://tickets.chef.io/browse/COOK-3714)\n\n#### Examples\nEnable the node as a DHCP Server\n\n```ruby\nwindows_feature 'DHCPServer' do\n action :install\nend\n```\n\nEnable TFTP\n\n```ruby\nwindows_feature 'TFTP' do\n action :install\nend\n```\n\nEnable .Net 3.5.1 on Server 2012 using repository files on DVD and\ninstall all dependencies\n\n```ruby\nwindows_feature \"NetFx3\" do\n action :install\n all true\n source \"d:\\sources\\sxs\"\nend\n```\n\nDisable Telnet client/server\n\n```ruby\n%w[TelnetServer TelnetClient].each do |feature|\n windows_feature feature do\n action :remove\n end\nend\n```\n\n### windows_font\nInstalls a font.\n\nFont files should be included in the cookbooks\n\n#### Actions\n- :install: install a font to the system fonts directory.\n\n#### Attribute Parameters\n- file: The name of the font file name to install. It should exist in the files/default directory of the cookbook you're calling windows_font from. Defaults to the resource name.\n\n#### Examples\n\n```ruby\nwindows_font 'Code New Roman.otf'\n```\n\n### windows_package\nManage Windows application packages in an unattended, idempotent way.\n\nThe following application installers are currently supported:\n\n* MSI packages\n* InstallShield\n* Wise InstallMaster\n* Inno Setup\n* Nullsoft Scriptable Install System\n\nIf the proper installer type is not passed into the resource's installer_type attribute, the provider will do it's best to identify the type by introspecting the installation package. If the installation type cannot be properly identified the `:custom` value can be passed into the installer_type attribute along with the proper flags for silent/quiet installation (using the `options` attribute..see example below).\n\n__PLEASE NOTE__ - For proper idempotence the resource's `package_name` should be the same as the 'DisplayName' registry value in the uninstallation data that is created during package installation. The easiest way to definitively find the proper 'DisplayName' value is to install the package on a machine and search for the uninstall information under the following registry keys:\n\n* `HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall`\n* `HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall`\n* `HKEY_LOCAL_MACHINE\\Software\\Wow6464Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall`\n\nFor maximum flexibility the `source` attribute supports both remote and local installation packages.\n\n#### Actions\n- :install: install a package\n- :remove: remove a package. The remove action is completely hit or miss as many application uninstallers do not support a full silent/quiet mode.\n\n#### Attribute Parameters\n- package_name: name attribute. The 'DisplayName' of the application installation package.\n- source: The source of the windows installer. This can either be a URI or a local path.\n- installer_type: They type of windows installation package. valid values are: :msi, :inno, :nsis, :wise, :installshield, :custom. If this value is not provided, the provider will do it's best to identify the installer type through introspection of the file.\n- checksum: useful if source is remote, the SHA-256 checksum of the file--if the local file matches the checksum, Chef will not download it\n- options: Additional options to pass the underlying installation command\n- timeout: set a timeout for the package download (default 600 seconds)\n- version: The version number of this package, as indicated by the 'DisplayVersion' value in one of the 'Uninstall' registry keys. If the given version number does equal the 'DisplayVersion' in the registry, the package will be installed.\n- success_codes: set an array of possible successful installation\n return codes. Previously this was hardcoded, but certain MSIs may\n have a different return code, e.g. 3010 for reboot required. Must be\n an array, and defaults to `[0, 42, 127]`.\n\n#### Examples\n\nInstall PuTTY (InnoSetup installer)\n```ruby\nwindows_package 'PuTTY version 0.60' do\n source 'http://the.earth.li/~sgtatham/putty/latest/x86/putty-0.60-installer.exe'\n installer_type :inno\n action :install\nend\n```\n\nInstall 7-Zip (MSI installer)\n```ruby\nwindows_package '7-Zip 9.20 (x64 edition)' do\n source 'http://downloads.sourceforge.net/sevenzip/7z920-x64.msi'\n action :install\nend\n```\n\nInstall Notepad++ (Y U No Emacs?) using a local installer\n```ruby\nwindows_package 'Notepad++' do\n source 'c:/installation_files/npp.5.9.2.Installer.exe'\n action :install\nend\n```\n\nInstall VLC for that Xvid (NSIS installer)\n```ruby\nwindows_package 'VLC media player 1.1.10' do\n source 'http://superb-sea2.dl.sourceforge.net/project/vlc/1.1.10/win32/vlc-1.1.10-win32.exe'\n action :install\nend\n```\n\nInstall Firefox as custom installer and manually set the silent install flags\n```ruby\nwindows_package 'Mozilla Firefox 5.0 (x86 en-US)' do\n source 'http://archive.mozilla.org/pub/mozilla.org/mozilla.org/firefox/releases/5.0/win32/en-US/Firefox%20Setup%205.0.exe'\n options '-ms'\n installer_type :custom\n action :install\nend\n```\n\nGoogle Chrome FTW (MSI installer)\n```ruby\nwindows_package 'Google Chrome' do\n source 'https://dl-ssl.google.com/tag/s/appguid%3D%7B8A69D345-D564-463C-AFF1-A69D9E530F96%7D%26iid%3D%7B806F36C0-CB54-4A84-A3F3-0CF8A86575E0%7D%26lang%3Den%26browser%3D3%26usagestats%3D0%26appname%3DGoogle%2520Chrome%26needsadmin%3Dfalse/edgedl/chrome/install/GoogleChromeStandaloneEnterprise.msi'\n action :install\nend\n```\n\nRemove Google Chrome\n```ruby\nwindows_package 'Google Chrome' do\n action :remove\nend\n```\n\nRemove 7-Zip\n```ruby\nwindows_package '7-Zip 9.20 (x64 edition)' do\n action :remove\nend\n```\n\n### windows_printer_port\n\nCreate and delete TCP/IPv4 printer ports.\n\n#### Actions\n- :create: Create a TCIP/IPv4 printer port. This is the default action.\n- :delete: Delete a TCIP/IPv4 printer port\n\n#### Attribute Parameters\n- :ipv4_address: Name attribute. Required. IPv4 address, e.g. '10.0.24.34'\n- :port_name: Port name. Optional. Defaults to 'IP_' + :ipv4_address\n- :port_number: Port number. Optional. Defaults to 9100.\n- :port_description: Port description. Optional.\n- :snmp_enabled: Boolean. Optional. Defaults to false.\n- :port_protocol: Port protocol, 1 (RAW), or 2 (LPR). Optional. Defaults to 1.\n\n#### Examples\n\nCreate a TCP/IP printer port named 'IP_10.4.64.37' with all defaults\n```ruby\nwindows_printer_port '10.4.64.37' do\nend\n```\n\nDelete a printer port\n```ruby\nwindows_printer_port '10.4.64.37' do\n action :delete\nend\n```\n\nDelete a port with a custom port_name\n```ruby\nwindows_printer_port '10.4.64.38' do\n port_name 'My awesome port'\n action :delete\nend\n```\n\nCreate a port with more options\n```ruby\nwindows_printer_port '10.4.64.39' do\n port_name 'My awesome port'\n snmp_enabled true\n port_protocol 2\nend\n```\n\n### windows_printer\n\nCreate Windows printer. Note that this doesn't currently install a printer\ndriver. You must already have the driver installed on the system.\n\nThe Windows Printer LWRP will automatically create a TCP/IP printer port for you using the `ipv4_address` property. If you want more granular control over the printer port, just create it using the `windows_printer_port` LWRP before creating the printer.\n\n#### Actions\n- :create: Create a new printer\n- :delete: Delete a new printer\n\n#### Attribute Parameters\n- :device_id: Name attribute. Required. Printer queue name, e.g. 'HP LJ 5200 in fifth floor copy room'\n- :comment: Optional string describing the printer queue.\n- :default: Boolean. Optional. Defaults to false. Note that Windows sets the first printer defined to the default printer regardless of this setting.\n- :driver_name: String. Required. Exact name of printer driver. Note that the printer driver must already be installed on the node.\n- :location: Printer location, e.g. 'Fifth floor copy room', or 'US/NYC/Floor42/Room4207'\n- :shared: Boolean. Defaults to false.\n- :share_name: Printer share name.\n- :ipv4_address: Printer IPv4 address, e.g. '10.4.64.23'. You don't have to be able to ping the IP addresss to set it. Required.\n\nAn error of \"Set-WmiInstance : Generic failure\" is most likely due to the printer driver name not matching or not being installed.\n\n#### Examples\n\nCreate a printer\n```ruby\nwindows_printer 'HP LaserJet 5th Floor' do\n driver_name 'HP LaserJet 4100 Series PCL6'\n ipv4_address '10.4.64.38'\nend\n```\n\nDelete a printer. Note: this doesn't delete the associated printer port. See `windows_printer_port` above for how to delete the port.\n```ruby\nwindows_printer 'HP LaserJet 5th Floor' do\n action :delete\nend\n```\n\n### windows_reboot\nSets required data in the node's run_state to notify `WindowsRebootHandler` a reboot is requested. If Chef run completes successfully a reboot will occur if the `WindowsRebootHandler` is properly registered as a report handler. As an action of `:request` will cause a node to reboot every Chef run, this resource is usually notified by other resources...ie restart node after a package is installed (see example below).\n\n#### Actions\n- :request: requests a reboot at completion of successful Cher run. requires `WindowsRebootHandler` to be registered as a report handler.\n- :cancel: remove reboot request from node.run_state. this will cancel *ALL* previously requested reboots as this is a binary state.\n\n#### Attribute Parameters\n- :timeout: Name attribute. timeout delay in seconds to wait before proceeding with the requested reboot. default is 60 seconds\n- :reason: comment on the reason for the reboot. default is 'Chef Software Chef initiated reboot'\n\n#### Examples\nIf the package installs, schedule a reboot at end of chef run\n```ruby\nwindows_reboot 60 do\n reason 'cause chef said so'\n action :nothing\nend\n\nwindows_package 'some_package' do\n action :install\n notifies :request, 'windows_reboot[60]'\nend\n```\n\nCancel the previously requested reboot\n```ruby\nwindows_reboot 60 do\n action :cancel\nend\n```\n\n### windows_registry\n(Chef 11.6.0 includes a built-in [registry_key](http://docs.chef.io/resource_registry_key.html) resource, so use that in preference to `windows_registry` if possible.)\n\nCreates and modifies Windows registry keys.\n\n*Change in v1.3.0: The Win32 classes use `::Win32` to avoid namespace conflict with `Chef::Win32` (introduced in Chef 0.10.10).*\n\n#### Actions\n- :create: create a new registry key with the provided values.\n- :modify: modify an existing registry key with the provided values.\n- :force_modify: modify an existing registry key with the provided values. ensures the value is actually set by checking multiple times. useful for fighting race conditions where two processes are trying to set the same registry key. This will be updated in the near future to use 'RegNotifyChangeKeyValue' which is exposed by the WinAPI and allows a process to register for notification on a registry key change.\n- :remove: removes a value from an existing registry key\n\n#### Attribute Parameters\n- key_name: name attribute. The registry key to create/modify.\n- values: hash of the values to set under the registry key. The individual hash items will become respective 'Value name' => 'Value data' items in the registry key.\n- type: Type of key to create, defaults to REG_SZ. Must be a symbol, see the overview below for valid values.\n\n#### Registry key types\n- :binary: REG_BINARY\n- :string: REG_SZ\n- :multi_string: REG_MULTI_SZ\n- :expand_string: REG_EXPAND_SZ\n- :dword: REG_DWORD\n- :dword_big_endian: REG_DWORD_BIG_ENDIAN\n- :qword: REG_QWORD\n\n#### Examples\n\nMake the local windows proxy match the one set for Chef\n```ruby\nproxy = URI.parse(Chef::Config[:http_proxy])\nwindows_registry 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings' do\n values 'ProxyEnable' => 1, 'ProxyServer' => \"#{proxy.host}:#{proxy.port}\", 'ProxyOverride' => ''\nend\n```\n\nEnable Remote Desktop and poke the firewall hole\n```ruby\nwindows_registry 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server' do\n values 'FdenyTSConnections' => 0\nend\n```\n\nDelete an item from the registry\n```ruby\nwindows_registry 'HKCU\\Software\\Test' do\n #Key is the name of the value that you want to delete the value is always empty\n values 'ValueToDelete' => ''\n action :remove\nend\n```\n\nAdd a REG_MULTI_SZ value to the registry\n```ruby\nwindows_registry 'HKCU\\Software\\Test' do\n values 'MultiString' => ['line 1', 'line 2', 'line 3']\n type :multi_string\nend\n```\n\n#### Library Methods\n\n```ruby\nRegistry.value_exists?('HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run','BGINFO')\nRegistry.key_exists?('HKLM\\SOFTWARE\\Microsoft')\nBgInfo = Registry.get_value('HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run','BGINFO')\n```\n\n### windows_path\n#### Actions\n- :add: Add an item to the system path\n- :remove: Remove an item from the system path\n\n#### Attribute Parameters\n- :path: Name attribute. The name of the value to add to the system path\n\n#### Examples\n\nAdd Sysinternals to the system path\n```ruby\nwindows_path 'C:\\Sysinternals' do\n action :add\nend\n```\n\nRemove 7-Zip from the system path\n```ruby\nwindows_path 'C:\\7-Zip' do\n action :remove\nend\n```\n\n### windows_task\nCreates, deletes or runs a Windows scheduled task. Requires Windows\nServer 2008 due to API usage.\n\n#### Actions\n- :create: creates a task\n- :delete: deletes a task\n- :run: runs a task\n- :change: changes the un/pw or command of a task\n- :enable: enable a task\n- :disable: disable a task\n\n#### Attribute Parameters\n- name: name attribute, The task name.\n- command: The command the task will run.\n- cwd: The directory the task will be run from.\n- user: The user to run the task as. (requires password)\n- password: The user's password. (requires user)\n- run_level: Run with limited or highest privileges.\n- frequency: Frequency with which to run the task. (hourly, daily, ect.)\n- frequency_modifier: Multiple for frequency. (15 minutes, 2 days)\n- start_day: Specifies the first date on which the task runs. Optional string (MM/DD/YYYY)\n- start_time: Specifies the start time to run the task. Optional string (HH:mm)\n\n#### Examples\n\nRun Chef every 15 minutes\n```ruby\nwindows_task 'Chef client' do\n user 'Administrator'\n password '$ecR3t'\n cwd 'C:\\chef\\bin'\n command 'chef-client -L C:\\tmp\\'\n run_level :highest\n frequency :minute\n frequency_modifier 15\nend\n```\n\nUpdate Chef Client task with new password and log location\n```ruby\nwindows_task 'Chef client' do\n user 'Administrator'\n password 'N3wPassW0Rd'\n cwd 'C:\\chef\\bin'\n command 'chef-client -L C:\\chef\\logs\\'\n action :change\nend\n```\n\nDelete a taks named 'old task'\n```ruby\nwindows_task 'old task' do\n action :delete\nend\n```\n\nEnable a task named 'Chef client'\n```ruby\nwindows_task 'Chef client' do\n action :enable\nend\n```\n\nDisable a task named 'Chef client'\n```ruby\nwindows_task 'Chef client' do\n action :disable\nend\n```\n\n### windows_zipfile\nMost version of Windows do not ship with native cli utility for managing compressed files. This resource provides a pure-ruby implementation for managing zip files. Be sure to use the `not_if` or `only_if` meta parameters to guard the resource for idempotence or action will be taken every Chef run.\n\n#### Actions\n- :unzip: unzip a compressed file\n- :zip: zip a directory (recursively)\n\n#### Attribute Parameters\n- path: name attribute. The path where files will be (un)zipped to.\n- source: source of the zip file (either a URI or local path) for :unzip, or directory to be zipped for :zip.\n- overwrite: force an overwrite of the files if they already exist.\n- checksum: for :unzip, useful if source is remote, if the local file matches the SHA-256 checksum, Chef will not download it.\n\n#### Examples\n\nUnzip a remote zip file locally\n```ruby\nwindows_zipfile 'c:/bin' do\n source 'http://download.sysinternals.com/Files/SysinternalsSuite.zip'\n action :unzip\n not_if {::File.exists?('c:/bin/PsExec.exe')}\nend\n```\n\nUnzip a local zipfile\n```ruby\nwindows_zipfile 'c:/the_codez' do\n source 'c:/foo/baz/the_codez.zip'\n action :unzip\nend\n```\n\nCreate a local zipfile\n```ruby\nwindows_zipfile 'c:/foo/baz/the_codez.zip' do\n source 'c:/the_codez'\n action :zip\nend\n```\n\nLibraries\n-------------------------\n### WindowsHelper\n\nHelper that allows you to use helpful functions in windows\n\n#### installed_packages\nReturns a hash of all DisplayNames installed\n```ruby\n# usage in a recipe\n::Chef::Recipe.send(:include, Windows::Helper)\nhash_of_installed_packages = installed_packages\n```\n\n#### is_package_installed?\n- `package_name`: The name of the package you want to query to see if it is installed\n- `returns`: true if the package is installed, false if it the package is not installed\n\nDownload a file if a package isn't installed\n```ruby\n# usage in a recipe to not download a file if package is already installed\n::Chef::Recipe.send(:include, Windows::Helper)\nis_win_sdk_installed = is_package_installed?('Windows Software Development Kit')\n\nremote_file 'C:\\windows\\temp\\windows_sdk.zip' do\n source 'http://url_to_download/windows_sdk.zip'\n action :create_if_missing\n not_if {is_win_sdk_installed}\nend\n```\nDo something if a package is installed\n```ruby\n# usage in a provider\ninclude Windows::Helper\nif is_package_installed?('Windows Software Development Kit')\n # do something if package is installed\nend\n```\n\nException/Report Handlers\n-------------------------\n### WindowsRebootHandler\nRequired reboots are a necessary evil of configuring and managing Windows nodes. This report handler (ie fires at the end of Chef runs) acts on requested (Chef initiated) or pending (as determined by the OS per configuration action we performed) reboots. The `allow_pending_reboots` initialization argument should be set to false if you do not want the handler to automatically reboot a node if it has been determined a reboot is pending. Reboots can still be requested explicitly via the `windows_reboot` LWRP.\n\n### Initialization Arguments\n- `allow_pending_reboots`: indicator on whether the handler should act on a the Window's 'pending reboot' state. default is true\n- `timeout`: timeout delay in seconds to wait before proceeding with the reboot. default is 60 seconds\n- `reason`: comment on the reason for the reboot. default is 'Chef Software Chef initiated reboot'\n\n\nWindows ChefSpec Matchers\n-------------------------\nThe Windows cookbook includes custom [ChefSpec](https://github.com/sethvargo/chefspec) matchers you can use to test your own cookbooks that consume Windows cookbook LWRPs.\n\n###Example Matcher Usage\n```ruby\nexpect(chef_run).to install_windows_package('Node.js').with(\n source: 'http://nodejs.org/dist/v0.10.26/x64/node-v0.10.26-x64.msi')\n```\n\n###Windows Cookbook Matchers\n* install_windows_package\n* remove_windows_package\n* install_windows_feature\n* remove_windows_feature\n* delete_windows_feature\n* create_windows_task\n* delete_windows_task\n* run_windows_task\n* change_windows_task\n* add_windows_path\n* remove_windows_path\n* run_windows_batch\n* set_windows_pagefile\n* unzip_windows_zipfile_to\n* zip_windows_zipfile_to\n* create_windows_shortcut\n* create_windows_auto_run\n* remove_windows_auto_run\n* create_windows_printer\n* delete_windows_printer\n* create_windows_printer_port\n* delete_windows_printer_port\n* request_windows_reboot\n* cancel_windows_reboot\n* create_windows_shortcut\n\n\nUsage\n-----\n\nPlace an explicit dependency on this cookbook (using depends in the cookbook's metadata.rb) from any cookbook where you would like to use the Windows-specific resources/providers that ship with this cookbook.\n\n```ruby\ndepends 'windows'\n```\n\n### default\nConvenience recipe that installs supporting gems for many of the resources/providers that ship with this cookbook.\n\n*Change in v1.3.0: Uses chef_gem instead of gem_package to ensure gem installation in Chef 0.10.10.*\n\n### reboot_handler\nLeverages the `chef_handler` LWRP to register the `WindowsRebootHandler` report handler that ships as part of this cookbook. By default this handler is set to automatically act on pending reboots. If you would like to change this behavior override `node['windows']['allow_pending_reboots']` and set the value to false. For example:\n\n```ruby\nname 'base'\ndescription 'base role'\noverride_attributes(\n 'windows' => {\n 'allow_pending_reboots' => false\n }\n)\n```\n\nThis will still allow a reboot to be explicitly requested via the `windows_reboot` LWRP.\n\nBy default, the handler will only be registered as a report handler, meaning that it will only fire at the end of successful Chef runs. If the run fails, pending or requested reboots will be ignored. This can lead to a situation where some package was installed and notified a reboot request via the `windows_reboot` LWRP, and then the run fails for some unrelated reason, and the reboot request gets dropped because the resource that notified the reboot request will already be up-to-date at the next run and will not request a reboot again, and thus the requested reboot will never be performed. To change this behavior and register the handler as an exception handler that fires at the end of failed runs too, override `node['windows']['allow_reboot_on_failure']` and set the value to true.\n\n\nLicense & Authors\n-----------------\n- Author:: Seth Chisamore ()\n- Author:: Doug MacEachern ()\n- Author:: Paul Morton ()\n- Author:: Doug Ireton ()\n\n```text\nCopyright 2011-2013, Chef Software, Inc.\nCopyright 2010, VMware, Inc.\nCopyright 2011, Business Intelligence Associates, Inc\nCopyright 2012, Nordstrom, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "maintainer": "Chef Software, Inc.", + "maintainer_email": "cookbooks@chef.io", + "license": "Apache 2.0", + "platforms": { + "windows": ">= 0.0.0" + }, + "dependencies": { + "chef_handler": ">= 0.0.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + } +} \ No newline at end of file diff --git a/cookbooks/windows/providers/auto_run.rb b/cookbooks/windows/providers/auto_run.rb new file mode 100644 index 0000000..e9c9cad --- /dev/null +++ b/cookbooks/windows/providers/auto_run.rb @@ -0,0 +1,33 @@ +# +# Author:: Paul Morton () +# Cookbook Name:: windows +# Provider:: auto_run +# +# Copyright:: 2011, Business Intelligence Associates, Inc +# +# 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. +# +use_inline_resources if defined?(use_inline_resources) + +action :create do + windows_registry 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' do + values new_resource.name => "\"#{new_resource.program}\" #{new_resource.args}" + end +end + +action :remove do + windows_registry 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' do + values new_resource.name => '' + action :remove + end +end diff --git a/cookbooks/windows/providers/batch.rb b/cookbooks/windows/providers/batch.rb new file mode 100644 index 0000000..47e5b60 --- /dev/null +++ b/cookbooks/windows/providers/batch.rb @@ -0,0 +1,63 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: windws +# Provider:: batch +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# +use_inline_resources if defined?(use_inline_resources) + +require 'tempfile' +require 'chef/resource/execute' + +action :run do + begin + script_file.puts(@new_resource.code) + script_file.close + set_owner_and_group + + # cwd hax...shell_out on windows needs to support proper 'cwd' + # follow CHEF-2357 for more + cwd = @new_resource.cwd ? "cd \"#{@new_resource.cwd}\" & " : "" + + r = Chef::Resource::Execute.new(@new_resource.name, run_context) + r.user(@new_resource.user) + r.group(@new_resource.group) + r.command("#{cwd}call \"#{script_file.path}\" #{@new_resource.flags}") + r.creates(@new_resource.creates) + r.returns(@new_resource.returns) + r.run_action(:run) + + @new_resource.updated_by_last_action(r.updated_by_last_action?) + ensure + unlink_script_file + end +end + +private +def set_owner_and_group + # FileUtils itself implements a no-op if +user+ or +group+ are nil + # You can prove this by running FileUtils.chown(nil,nil,'/tmp/file') + # as an unprivileged user. + FileUtils.chown(@new_resource.user, @new_resource.group, script_file.path) +end + +def script_file + @script_file ||= Tempfile.open(['chef-script', '.bat']) +end + +def unlink_script_file + @script_file && @script_file.close! +end diff --git a/cookbooks/windows/providers/feature_dism.rb b/cookbooks/windows/providers/feature_dism.rb new file mode 100644 index 0000000..84fdbcf --- /dev/null +++ b/cookbooks/windows/providers/feature_dism.rb @@ -0,0 +1,64 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Provider:: feature_dism +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +include Chef::Provider::WindowsFeature::Base +include Chef::Mixin::ShellOut +include Windows::Helper + +def install_feature(name) + addsource = @new_resource.source ? "/LimitAccess /Source:\"#{@new_resource.source}\"" : "" + addall = @new_resource.all ? "/All" : "" + shell_out!("#{dism} /online /enable-feature /featurename:#{@new_resource.feature_name} /norestart #{addsource} #{addall}", {:returns => [0,42,127,3010]}) +end + +def remove_feature(name) + shell_out!("#{dism} /online /disable-feature /featurename:#{@new_resource.feature_name} /norestart", {:returns => [0,42,127,3010]}) +end + +def delete_feature(name) + if win_version.major_version >= 6 and win_version.minor_version >=2 + shell_out!("#{dism} /online /disable-feature /featurename:#{@new_resource.feature_name} /Remove /norestart", {:returns => [0,42,127,3010]}) + else + raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} :delete action not support on #{win_version.sku}" + end +end + +def installed? + @installed ||= begin + cmd = shell_out("#{dism} /online /Get-Features", {:returns => [0,42,127]}) + cmd.stderr.empty? && (cmd.stdout =~ /^Feature Name : #{@new_resource.feature_name}.?$\n^State : Enabled.?$/i) + end +end + +def available? + @available ||= begin + cmd = shell_out("#{dism} /online /Get-Features", {:returns => [0,42,127]}) + cmd.stderr.empty? && (cmd.stdout !~ /^Feature Name : #{@new_resource.feature_name}.?$\n^State : .* with payload removed.?$/i) + end +end + +private +# account for File System Redirector +# http://msdn.microsoft.com/en-us/library/aa384187(v=vs.85).aspx +def dism + @dism ||= begin + locate_sysnative_cmd("dism.exe") + end +end diff --git a/cookbooks/windows/providers/feature_powershell.rb b/cookbooks/windows/providers/feature_powershell.rb new file mode 100644 index 0000000..44cd96c --- /dev/null +++ b/cookbooks/windows/providers/feature_powershell.rb @@ -0,0 +1,38 @@ +# +# Author:: Greg Zapp () +# Cookbook Name:: windows +# Provider:: feature_powershell +# + +include Chef::Provider::WindowsFeature::Base +include Chef::Mixin::PowershellOut +include Windows::Helper + +def install_feature(name) + cmd = powershell_out("Install-WindowsFeature #{@new_resource.feature_name}") + Chef::Log.info(cmd.stdout) +end + +def remove_feature(name) + cmd = powershell_out("Uninstall-WindowsFeature #{@new_resource.feature_name}") + Chef::Log.info(cmd.stdout) +end + +def delete_feature(name) + cmd = powershell_out("Uninstall-WindowsFeature #{@new_resource.feature_name} -Remove") + Chef::Log.info(cmd.stdout) +end + +def installed? + @installed ||= begin + cmd = powershell_out("Get-WindowsFeature #{@new_resource.feature_name} | Select Installed | % { Write-Host $_.Installed }") + cmd.stderr.empty? && cmd.stdout =~ /True/i + end +end + +def available? + @available ||= begin + cmd = powershell_out("Get-WindowsFeature #{@new_resource.feature_name}") + cmd.stderr.empty? && cmd.stdout !~ /Removed/i + end +end diff --git a/cookbooks/windows/providers/feature_servermanagercmd.rb b/cookbooks/windows/providers/feature_servermanagercmd.rb new file mode 100644 index 0000000..59cc3a7 --- /dev/null +++ b/cookbooks/windows/providers/feature_servermanagercmd.rb @@ -0,0 +1,61 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Provider:: feature_servermanagercmd +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +include Chef::Provider::WindowsFeature::Base +include Chef::Mixin::ShellOut +include Windows::Helper + +# Exit codes are listed at http://technet.microsoft.com/en-us/library/cc749128(v=ws.10).aspx + +def check_reboot(result, feature) + if result.exitstatus == 3010 # successful, but needs reboot + node.run_state[:reboot_requested] = true + Chef::Log.warn("Require reboot to install #{feature}") + elsif result.exitstatus == 1001 # failure, but needs reboot before we can do anything else + node.run_state[:reboot_requested] = true + Chef::Log.warn("Failed installing #{feature} and need to reboot") + end + result.error! # throw for any other bad results. The above results will also get raised, and should cause a reboot via the handler. +end + +def install_feature(name) + check_reboot(shell_out("#{servermanagercmd} -install #{@new_resource.feature_name}", {:returns => [0,42,127,1003,3010]}), @new_resource.feature_name) +end + +def remove_feature(name) + check_reboot(shell_out("#{servermanagercmd} -remove #{@new_resource.feature_name}", {:returns => [0,42,127,1003,3010]}), @new_resource.feature_name) +end + +def installed? + @installed ||= begin + cmd = shell_out("#{servermanagercmd} -query", {:returns => [0,42,127,1003]}) + cmd.stderr.empty? && (cmd.stdout =~ /^\s*?\[X\]\s.+?\s\[#{@new_resource.feature_name}\]\s*$/i) + end +end + +private + +# account for File System Redirector +# http://msdn.microsoft.com/en-us/library/aa384187(v=vs.85).aspx +def servermanagercmd + @servermanagercmd ||= begin + locate_sysnative_cmd("servermanagercmd.exe") + end +end diff --git a/cookbooks/windows/providers/font.rb b/cookbooks/windows/providers/font.rb new file mode 100644 index 0000000..1a1e473 --- /dev/null +++ b/cookbooks/windows/providers/font.rb @@ -0,0 +1,69 @@ +# +# Author:: Sander Botman +# Cookbook Name:: windows +# Provider:: font +# +# Copyright:: 2014, Schuberg Philis BV. +# +# 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. +# +include Windows::Helper + +def load_current_resource + require 'win32ole' + fonts_dir = WIN32OLE.new("WScript.Shell").SpecialFolders("Fonts") + @current_resource = Chef::Resource::WindowsFont.new(@new_resource.name) + @current_resource.file(win_friendly_path(::File.join(fonts_dir, @new_resource.file))) + @current_resource +end + +# Check to see if the font is installed +# +# === Returns +# :: If the font is installed +# :: If the font is not instaled +def font_exists? + ::File.exists?(@current_resource.file) +end + +def get_cookbook_font + r = Chef::Resource::CookbookFile.new(@new_resource.file, run_context) + r.path(win_friendly_path(::File.join(ENV['TEMP'], @new_resource.file))) + r.cookbook(cookbook_name.to_s) + r.run_action(:create) +end + +def del_cookbook_font + r = Chef::Resource::File.new(::File.join(ENV['TEMP'], @new_resource.file), run_context) + r.run_action(:delete) +end + +def install_font + require 'win32ole' + fonts_dir = WIN32OLE.new("WScript.Shell").SpecialFolders("Fonts") + folder = WIN32OLE.new("Shell.Application").Namespace(fonts_dir) + folder.CopyHere(win_friendly_path(::File.join(ENV['TEMP'], @new_resource.file))) + Chef::Log.debug("Installing font: #{@new_resource.file}") +end + +def action_install + unless font_exists? + get_cookbook_font + install_font + del_cookbook_font + new_resource.updated_by_last_action(true) + else + Chef::Log.debug("Not installing font: #{@new_resource.file}, font already installed.") + new_resource.updated_by_last_action(false) + end +end diff --git a/cookbooks/windows/providers/pagefile.rb b/cookbooks/windows/providers/pagefile.rb new file mode 100644 index 0000000..2b41af4 --- /dev/null +++ b/cookbooks/windows/providers/pagefile.rb @@ -0,0 +1,153 @@ +# +# Author:: Kevin Moser () +# Cookbook Name:: windows +# Provider:: pagefile +# +# Copyright:: 2012, Nordstrom, Inc. +# +# 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. +# + +include Chef::Mixin::ShellOut +include Windows::Helper + +action :set do + pagefile = @new_resource.name + initial_size = @new_resource.initial_size + maximum_size = @new_resource.maximum_size + system_managed = @new_resource.system_managed + automatic_managed = @new_resource.automatic_managed + updated = false + + if automatic_managed + unless automatic_managed? + set_automatic_managed + updated = true + end + else + if automatic_managed? + unset_automatic_managed + updated = true + end + + # Check that the resource is not just trying to unset automatic managed, if it is do nothing more + if (initial_size && maximum_size) || system_managed + unless exists?(pagefile) + create(pagefile) + end + + if system_managed + unless max_and_min_set?(pagefile, 0, 0) + set_system_managed(pagefile) + updated = true + end + else + unless max_and_min_set?(pagefile, initial_size, maximum_size) + set_custom_size(pagefile, initial_size, maximum_size) + updated = true + end + end + end + end + + new_resource.updated_by_last_action(updated) +end + +action :delete do + pagefile = @new_resource.name + updated = false + + if exists?(pagefile) + delete(pagefile) + updated = true + end + + new_resource.updated_by_last_action(updated) +end + + +private +def exists?(pagefile) + @exists ||= begin + cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list", {:returns => [0]}) + cmd.stderr.empty? && (cmd.stdout =~ /SettingID=#{get_setting_id(pagefile)}/i) + end +end + +def max_and_min_set?(pagefile, min, max) + @max_and_min_set ||= begin + cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list", {:returns => [0]}) + cmd.stderr.empty? && (cmd.stdout =~ /InitialSize=#{min}/i) && (cmd.stdout =~ /MaximumSize=#{max}/i) + end +end + +def create(pagefile) + Chef::Log.debug("Creating pagefile #{pagefile}") + cmd = shell_out("#{wmic} pagefileset create name=\"#{win_friendly_path(pagefile)}\"") + check_for_errors(cmd.stderr) +end + +def delete(pagefile) + Chef::Log.debug("Removing pagefile #{pagefile}") + cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" delete") + check_for_errors(cmd.stderr) +end + +def automatic_managed? + @automatic_managed ||= begin + cmd = shell_out("#{wmic} computersystem where name=\"%computername%\" get AutomaticManagedPagefile /format:list") + cmd.stderr.empty? && (cmd.stdout =~ /AutomaticManagedPagefile=TRUE/i) + end +end + +def set_automatic_managed + Chef::Log.debug("Setting pagefile to Automatic Managed") + cmd = shell_out("#{wmic} computersystem where name=\"%computername%\" set AutomaticManagedPagefile=True") + check_for_errors(cmd.stderr) +end + +def unset_automatic_managed + Chef::Log.debug("Setting pagefile to User Managed") + cmd = shell_out("#{wmic} computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False") + check_for_errors(cmd.stderr) +end + +def set_custom_size(pagefile, min, max) + Chef::Log.debug("Setting #{pagefile} to InitialSize=#{min} & MaximumSize=#{max}") + cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=#{min},MaximumSize=#{max}", {:returns => [0]}) + check_for_errors(cmd.stderr) +end + +def set_system_managed(pagefile) + Chef::Log.debug("Setting #{pagefile} to System Managed") + cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=0,MaximumSize=0", {:returns => [0]}) + check_for_errors(cmd.stderr) +end + +def get_setting_id(pagefile) + pagefile = win_friendly_path(pagefile) + pagefile = pagefile.split("\\") + "#{pagefile[1]} @ #{pagefile[0]}" +end + +def check_for_errors(stderr) + unless stderr.empty? + Chef::Log.fatal(stderr) + end +end + +def wmic + @wmic ||= begin + locate_sysnative_cmd("wmic.exe") + end +end diff --git a/cookbooks/windows/providers/path.rb b/cookbooks/windows/providers/path.rb new file mode 100644 index 0000000..8003c7f --- /dev/null +++ b/cookbooks/windows/providers/path.rb @@ -0,0 +1,52 @@ +# +# Author:: Paul Morton () +# Cookbook Name:: windows +# Provider:: path +# +# Copyright:: 2011, Business Intelligence Associates, Inc +# +# 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. +# +use_inline_resources if defined?(use_inline_resources) + +include Windows::Helper + +action :add do + env "path" do + action :modify + delim ::File::PATH_SEPARATOR + value new_resource.path + notifies :run, "ruby_block[fix ruby ENV['PATH']]", :immediately + end + + # The windows Env provider does not correctly expand variables in + # the PATH environment variable. Ruby expects these to be expanded. + # This is a temporary fix for that. + # + # Follow at https://github.com/chef/chef/pull/1876 + # + ruby_block "fix ruby ENV['PATH']" do + block do + ENV['PATH'] = expand_env_vars(ENV['PATH']) + end + action :nothing + end +end + +action :remove do + env "path" do + action :delete + delim ::File::PATH_SEPARATOR + value new_resource.path + end +end diff --git a/cookbooks/windows/providers/printer.rb b/cookbooks/windows/providers/printer.rb new file mode 100644 index 0000000..3dd976c --- /dev/null +++ b/cookbooks/windows/providers/printer.rb @@ -0,0 +1,101 @@ +# +# Author:: Doug Ireton () +# Cookbook Name:: windows +# Provider:: printer +# +# Copyright:: 2012, Nordstrom, Inc. +# +# 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. +# +use_inline_resources if defined?(use_inline_resources) + +# Support whyrun +def whyrun_supported? + true +end + +action :create do + if @current_resource.exists + Chef::Log.info "#{ @new_resource } already exists - nothing to do." + else + converge_by("Create #{ @new_resource }") do + create_printer + end + end +end + +action :delete do + if @current_resource.exists + converge_by("Delete #{ @new_resource }") do + delete_printer + end + else + Chef::Log.info "#{ @current_resource } doesn't exist - can't delete." + end +end + +def load_current_resource + @current_resource = Chef::Resource::WindowsPrinter.new(@new_resource.name) + @current_resource.name(@new_resource.name) + + if printer_exists?(@current_resource.name) + # TODO: Set @current_resource printer properties from registry + @current_resource.exists = true + end +end + + +private + +PRINTERS_REG_KEY = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\\'.freeze unless defined?(PRINTERS_REG_KEY) + +def printer_exists?(name) + printer_reg_key = PRINTERS_REG_KEY + name + Chef::Log.debug "Checking to see if this reg key exists: '#{ printer_reg_key }'" + Registry.key_exists?(printer_reg_key) +end + +def create_printer + + # Create the printer port first + windows_printer_port new_resource.ipv4_address do + end + + port_name = "IP_#{ new_resource.ipv4_address }" + + powershell_script "Creating printer: #{ new_resource.name }" do + code <<-EOH + + Set-WmiInstance -class Win32_Printer ` + -EnableAllPrivileges ` + -Argument @{ DeviceID = "#{ new_resource.device_id }"; + Comment = "#{ new_resource.comment }"; + Default = "$#{ new_resource.default }"; + DriverName = "#{ new_resource.driver_name }"; + Location = "#{ new_resource.location }"; + PortName = "#{ port_name }"; + Shared = "$#{ new_resource.shared }"; + ShareName = "#{ new_resource.share_name }"; + } + EOH + end +end + +def delete_printer + powershell_script "Deleting printer: #{ new_resource.name }" do + code <<-EOH + $printer = Get-WMIObject -class Win32_Printer -EnableAllPrivileges -Filter "name = '#{ new_resource.name }'" + $printer.Delete() + EOH + end +end diff --git a/cookbooks/windows/providers/printer_port.rb b/cookbooks/windows/providers/printer_port.rb new file mode 100644 index 0000000..d2666ec --- /dev/null +++ b/cookbooks/windows/providers/printer_port.rb @@ -0,0 +1,103 @@ +# +# Author:: Doug Ireton () +# Cookbook Name:: windows +# Provider:: printer_port +# +# Copyright:: 2012, Nordstrom, Inc. +# +# 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. +# +use_inline_resources if defined?(use_inline_resources) + +# Support whyrun +def whyrun_supported? + true +end + +action :create do + if @current_resource.exists + Chef::Log.info "#{ @new_resource } already exists - nothing to do." + else + converge_by("Create #{ @new_resource }") do + create_printer_port + end + end +end + +action :delete do + if @current_resource.exists + converge_by("Delete #{ @new_resource }") do + delete_printer_port + end + else + Chef::Log.info "#{ @current_resource } doesn't exist - can't delete." + end +end + +def load_current_resource + @current_resource = Chef::Resource::WindowsPrinterPort.new(@new_resource.name) + @current_resource.name(@new_resource.name) + @current_resource.ipv4_address(@new_resource.ipv4_address) + @current_resource.port_name(@new_resource.port_name || "IP_#{ @new_resource.ipv4_address }") + + if port_exists?(@current_resource.port_name) + # TODO: Set @current_resource port properties from registry + @current_resource.exists = true + end +end + + +private + +PORTS_REG_KEY = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\\'.freeze unless defined?(PORTS_REG_KEY) + +def port_exists?(name) + port_reg_key = PORTS_REG_KEY + name + + Chef::Log.debug "Checking to see if this reg key exists: '#{ port_reg_key }'" + Registry.key_exists?(port_reg_key) +end + + +def create_printer_port + + port_name = new_resource.port_name || "IP_#{ new_resource.ipv4_address }" + + # create the printer port using PowerShell + powershell_script "Creating printer port #{ new_resource.port_name }" do + code <<-EOH + + Set-WmiInstance -class Win32_TCPIPPrinterPort ` + -EnableAllPrivileges ` + -Argument @{ HostAddress = "#{ new_resource.ipv4_address }"; + Name = "#{ port_name }"; + Description = "#{ new_resource.port_description }"; + PortNumber = "#{ new_resource.port_number }"; + Protocol = "#{ new_resource.port_protocol }"; + SNMPEnabled = "$#{ new_resource.snmp_enabled }"; + } + EOH + end +end + +def delete_printer_port + + port_name = new_resource.port_name || "IP_#{ new_resource.ipv4_address }" + + powershell_script "Deleting printer port: #{ new_resource.port_name }" do + code <<-EOH + $port = Get-WMIObject -class Win32_TCPIPPrinterPort -EnableAllPrivileges -Filter "name = '#{ port_name }'" + $port.Delete() + EOH + end +end diff --git a/cookbooks/windows/providers/reboot.rb b/cookbooks/windows/providers/reboot.rb new file mode 100644 index 0000000..c5cf68c6 --- /dev/null +++ b/cookbooks/windows/providers/reboot.rb @@ -0,0 +1,33 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Provider:: reboot +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +action :request do + node.run_state[:reboot_requested] = true + node.run_state[:reboot_timeout] = @new_resource.timeout + node.run_state[:reboot_reason] = @new_resource.reason + new_resource.updated_by_last_action(true) +end + +action :cancel do + node.run_state.delete(:reboot_requested) + node.run_state.delete(:reboot_timeout) + node.run_state.delete(:reboot_reason) + new_resource.updated_by_last_action(true) +end diff --git a/cookbooks/windows/providers/registry.rb b/cookbooks/windows/providers/registry.rb new file mode 100644 index 0000000..6ffd498 --- /dev/null +++ b/cookbooks/windows/providers/registry.rb @@ -0,0 +1,75 @@ +# +# Author:: Doug MacEachern () +# Author:: Seth Chisamore () +# Author:: Paul Morton () +# Cookbook Name:: windows +# Provider:: registry +# +# Copyright:: 2010, VMware, Inc. +# Copyright:: 2011, Chef Software, Inc. +# Copyright:: 2011, Business Intelligence Associates, Inc +# +# 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. +# + +include Windows::RegistryHelper + +action :create do + updated = registry_update(:create) + new_resource.updated_by_last_action(updated) +end + +action :modify do + updated = registry_update(:open) + new_resource.updated_by_last_action(updated) +end + +action :force_modify do + require 'timeout' + Timeout.timeout(120) do + @new_resource.values.each do |value_name, value_data| + i = 1 + until i > 5 do + desired_value_data = value_data + current_value_data = get_value(@new_resource.key_name.dup, value_name.dup) + if current_value_data.to_s == desired_value_data.to_s + Chef::Log.debug("#{@new_resource} value [#{value_name}] desired [#{desired_value_data}] data already set. Check #{i}/5.") + i+=1 + else + Chef::Log.debug("#{@new_resource} value [#{value_name}] current [#{current_value_data}] data not equal to desired [#{desired_value_data}] data. Setting value and restarting check loop.") + begin + updated = registry_update(:open) + new_resource.updated_by_last_action(updated) + rescue Exception + updated = registry_update(:create) + new_resource.updated_by_last_action(updated) + end + i=0 # start count loop over + end + end + end + break + end +end + +action :remove do + delete_value(@new_resource.key_name,@new_resource.values) + new_resource.updated_by_last_action(true) +end + +private +def registry_update(mode) + + Chef::Log.debug("Registry Mode (#{mode})") + updated = set_value(mode,@new_resource.key_name,@new_resource.values,@new_resource.type) +end diff --git a/cookbooks/windows/providers/shortcut.rb b/cookbooks/windows/providers/shortcut.rb new file mode 100644 index 0000000..9fd9a88 --- /dev/null +++ b/cookbooks/windows/providers/shortcut.rb @@ -0,0 +1,56 @@ +# +# Author:: Doug MacEachern +# Cookbook Name:: windows +# Provider:: shortcut +# +# Copyright:: 2010, VMware, Inc. +# +# 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. +# + +def load_current_resource + require 'win32ole' + + @link = WIN32OLE.new("WScript.Shell").CreateShortcut(@new_resource.name) + + @current_resource = Chef::Resource::WindowsShortcut.new(@new_resource.name) + @current_resource.name(@new_resource.name) + @current_resource.target(@link.TargetPath) + @current_resource.arguments(@link.Arguments) + @current_resource.description(@link.Description) + @current_resource.cwd(@link.WorkingDirectory) +end + +# Check to see if the shorcut needs any changes +# +# === Returns +# :: If a change is required +# :: If the shorcuts are identical +def compare_shortcut + [:target, :arguments, :description, :cwd].any? do |attr| + !@new_resource.send(attr).nil? && @current_resource.send(attr) != @new_resource.send(attr) + end +end + +def action_create + if compare_shortcut + @link.TargetPath = @new_resource.target if @new_resource.target != nil + @link.Arguments = @new_resource.arguments if @new_resource.arguments != nil + @link.Description = @new_resource.description if @new_resource.description != nil + @link.WorkingDirectory = @new_resource.cwd if @new_resource.cwd != nil + #ignoring: WindowStyle, Hotkey, IconLocation + @link.Save + Chef::Log.info("Added #{@new_resource} shortcut") + new_resource.updated_by_last_action(true) + end +end diff --git a/cookbooks/windows/providers/task.rb b/cookbooks/windows/providers/task.rb new file mode 100644 index 0000000..24a35c0 --- /dev/null +++ b/cookbooks/windows/providers/task.rb @@ -0,0 +1,167 @@ +# +# Author:: Paul Mooring () +# Cookbook Name:: windows +# Provider:: task +# +# Copyright:: 2012, Chef Software, Inc. +# +# 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. +# + +require 'chef/mixin/shell_out' +include Chef::Mixin::ShellOut + +action :create do + if @current_resource.exists + Chef::Log.info "#{@new_resource} task already exists - nothing to do" + else + if @new_resource.user and @new_resource.password.nil? then Chef::Log.debug "#{@new_resource} did not specify a password, creating task without a password" end + use_force = @new_resource.force ? '/F' : '' + cmd = "schtasks /Create #{use_force} /TN \"#{@new_resource.name}\" " + schedule = @new_resource.frequency == :on_logon ? "ONLOGON" : @new_resource.frequency + cmd += "/SC #{schedule} " + cmd += "/MO #{@new_resource.frequency_modifier} " if [:minute, :hourly, :daily, :weekly, :monthly].include?(@new_resource.frequency) + cmd += "/SD \"#{@new_resource.start_day}\" " unless @new_resource.start_day.nil? + cmd += "/ST \"#{@new_resource.start_time}\" " unless @new_resource.start_time.nil? + cmd += "/TR \"#{@new_resource.command}\" " + cmd += "/RU \"#{@new_resource.user}\" " if @new_resource.user + cmd += "/RP \"#{@new_resource.password}\" " if @new_resource.user and @new_resource.password + cmd += "/RL HIGHEST " if @new_resource.run_level == :highest + shell_out!(cmd, {:returns => [0]}) + new_resource.updated_by_last_action true + Chef::Log.info "#{@new_resource} task created" + end +end + +action :run do + if @current_resource.exists + if @current_resource.status == :running + Chef::Log.info "#{@new_resource} task is currently running, skipping run" + else + cmd = "schtasks /Run /TN \"#{@current_resource.name}\"" + shell_out!(cmd, {:returns => [0]}) + new_resource.updated_by_last_action true + Chef::Log.info "#{@new_resource} task ran" + end + else + Chef::Log.debug "#{@new_resource} task doesn't exists - nothing to do" + end +end + +action :change do + if @current_resource.exists + cmd = "schtasks /Change /TN \"#{@current_resource.name}\" " + cmd += "/TR \"#{@new_resource.command}\" " if @new_resource.command + if @new_resource.user && @new_resource.password + cmd += "/RU \"#{@new_resource.user}\" /RP \"#{@new_resource.password}\" " + elsif (@new_resource.user and !@new_resource.password) || (@new_resource.password and !@new_resource.user) + Chef::Log.fatal "#{@new_resource.name}: Can't specify user or password without both!" + end + shell_out!(cmd, {:returns => [0]}) + new_resource.updated_by_last_action true + Chef::Log.info "Change #{@new_resource} task ran" + else + Chef::Log.debug "#{@new_resource} task doesn't exists - nothing to do" + end +end + +action :delete do + if @current_resource.exists + use_force = @new_resource.force ? '/F' : '' + cmd = "schtasks /Delete #{use_force} /TN \"#{@current_resource.name}\"" + shell_out!(cmd, {:returns => [0]}) + new_resource.updated_by_last_action true + Chef::Log.info "#{@new_resource} task deleted" + else + Chef::Log.debug "#{@new_resource} task doesn't exists - nothing to do" + end +end + +action :enable do + if @current_resource.exists + if @current_resource.enabled + Chef::Log.debug "#{@new_resource} already enabled - nothing to do" + else + cmd = "schtasks /Change /TN \"#{@current_resource.name}\" " + cmd += "/ENABLE" + shell_out!(cmd, {:returns => [0]}) + @new_resource.updated_by_last_action true + Chef::Log.info "#{@new_resource} task enabled" + end + else + Chef::Log.fatal "#{@new_resource} task doesn't exist - nothing to do" + raise Errno::ENOENT, "#{@new_resource}: task does not exist, cannot enable" + end +end + +action :disable do + if @current_resource.exists + if @current_resource.enabled + cmd = "schtasks /Change /TN \"#{@current_resource.name}\" " + cmd += "/DISABLE" + shell_out!(cmd, {:returns => [0]}) + @new_resource.updated_by_last_action true + Chef::Log.info "#{@new_resource} task disabled" + else + Chef::Log.debug "#{@new_resource} already disabled - nothing to do" + end + else + Chef::Log.debug "#{@new_resource} task doesn't exist - nothing to do" + end +end + + +def load_current_resource + @current_resource = Chef::Resource::WindowsTask.new(@new_resource.name) + @current_resource.name(@new_resource.name) + + task_hash = load_task_hash(@current_resource.name) + if task_hash[:TaskName] == '\\' + @new_resource.name + @current_resource.exists = true + if task_hash[:Status] == "Running" + @current_resource.status = :running + end + if task_hash[:ScheduledTaskState] == "Enabled" + @current_resource.enabled = true + end + @current_resource.cwd(task_hash[:Folder]) + @current_resource.command(task_hash[:TaskToRun]) + @current_resource.user(task_hash[:RunAsUser]) + end if task_hash.respond_to? :[] +end + +private + +def load_task_hash(task_name) + Chef::Log.debug "looking for existing tasks" + + # we use shell_out here instead of shell_out! because a failure implies that the task does not exist + output = shell_out("schtasks /Query /FO LIST /V /TN \"#{task_name}\"").stdout + if output.empty? + task = false + else + task = Hash.new + + output.split("\n").map! do |line| + line.split(":", 2).map! do |field| + field.strip + end + end.each do |field| + if field.kind_of? Array and field[0].respond_to? :to_sym + task[field[0].gsub(/\s+/,"").to_sym] = field[1] + end + end + end + + task +end diff --git a/cookbooks/windows/providers/zipfile.rb b/cookbooks/windows/providers/zipfile.rb new file mode 100644 index 0000000..caa9e9c --- /dev/null +++ b/cookbooks/windows/providers/zipfile.rb @@ -0,0 +1,93 @@ +# +# Author:: Doug MacEachern () +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Provider:: zipfile +# +# Copyright:: 2010, VMware, Inc. +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +include Windows::Helper + +require 'find' + +action :unzip do + ensure_rubyzip_gem_installed + Chef::Log.debug("unzip #{@new_resource.source} => #{@new_resource.path} (overwrite=#{@new_resource.overwrite})") + + Zip::File.open(cached_file(@new_resource.source, @new_resource.checksum)) do |zip| + zip.each do |entry| + path = ::File.join(@new_resource.path, entry.name) + FileUtils.mkdir_p(::File.dirname(path)) + if @new_resource.overwrite && ::File.exists?(path) && !::File.directory?(path) + FileUtils.rm(path) + end + zip.extract(entry, path) + end + end + new_resource.updated_by_last_action(true) +end + +action :zip do + ensure_rubyzip_gem_installed + # sanitize paths for windows. + @new_resource.source.downcase.gsub!(::File::SEPARATOR, ::File::ALT_SEPARATOR) + @new_resource.path.downcase.gsub!(::File::SEPARATOR, ::File::ALT_SEPARATOR) + Chef::Log.debug("zip #{@new_resource.source} => #{@new_resource.path} (overwrite=#{@new_resource.overwrite})") + + if @new_resource.overwrite == false && ::File.exists?(@new_resource.path) + Chef::Log.info("file #{@new_resource.path} already exists and overwrite is set to false, exiting") + else + # delete the archive if it already exists, because we are recreating it. + if ::File.exists?(@new_resource.path) + ::File.unlink(@new_resource.path) + end + # only supporting compression of a single directory (recursively). + if ::File.directory?(@new_resource.source) + z = Zip::File.new(@new_resource.path, true) + unless @new_resource.source =~ /::File::ALT_SEPARATOR$/ + @new_resource.source << ::File::ALT_SEPARATOR + end + Find.find(@new_resource.source) do |f| + f.downcase.gsub!(::File::SEPARATOR, ::File::ALT_SEPARATOR) + # don't add root directory to the zipfile. + next if f == @new_resource.source + # strip the root directory from the filename before adding it to the zipfile. + zip_fname = f.sub(@new_resource.source, '') + Chef::Log.debug("adding #{zip_fname} to archive, sourcefile is: #{f}") + z.add(zip_fname, f) + end + z.close + new_resource.updated_by_last_action(true) + else + Chef::Log.info("Single directory must be specified for compression, and #{@new_resource.source} does not meet that criteria.") + end + end +end + +private +def ensure_rubyzip_gem_installed + begin + require 'zip' + rescue LoadError + Chef::Log.info("Missing gem 'rubyzip'...installing now.") + chef_gem "rubyzip" do + version node['windows']['rubyzipversion'] + action :install + end + require 'zip' + end +end diff --git a/cookbooks/windows/recipes/default.rb b/cookbooks/windows/recipes/default.rb new file mode 100644 index 0000000..87e5096 --- /dev/null +++ b/cookbooks/windows/recipes/default.rb @@ -0,0 +1,34 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Recipe:: default +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +# gems with precompiled binaries +%w{ win32-api win32-service }.each do |win_gem| + chef_gem win_gem do + options '--platform=mswin32' + action :install + end +end + +# the rest +%w{ windows-api windows-pr win32-dir win32-event win32-mutex }.each do |win_gem| + chef_gem win_gem do + action :install + end +end diff --git a/cookbooks/windows/recipes/reboot_handler.rb b/cookbooks/windows/recipes/reboot_handler.rb new file mode 100644 index 0000000..ee5de51 --- /dev/null +++ b/cookbooks/windows/recipes/reboot_handler.rb @@ -0,0 +1,32 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Recipe:: restart_handler +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +remote_directory node['chef_handler']['handler_path'] do + source 'handlers' + recursive true + action :create +end + +chef_handler 'WindowsRebootHandler' do + source "#{node['chef_handler']['handler_path']}/windows_reboot_handler.rb" + arguments node['windows']['allow_pending_reboots'] + supports :report => true, :exception => node['windows']['allow_reboot_on_failure'] + action :enable +end diff --git a/cookbooks/windows/resources/auto_run.rb b/cookbooks/windows/resources/auto_run.rb new file mode 100644 index 0000000..78a7702 --- /dev/null +++ b/cookbooks/windows/resources/auto_run.rb @@ -0,0 +1,30 @@ +# +# Author:: Paul Morton () +# Cookbook Name:: windows +# Resource:: auto_run +# +# Copyright:: 2011, Business Intelligence Associates, Inc +# +# 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. +# + +def initialize(name,run_context=nil) + super + @action = :create +end + +actions :create, :remove + +attribute :program, :kind_of => String +attribute :name, :kind_of => String, :name_attribute => true +attribute :args, :kind_of => String, :default => '' diff --git a/cookbooks/windows/resources/batch.rb b/cookbooks/windows/resources/batch.rb new file mode 100644 index 0000000..4b1e6be --- /dev/null +++ b/cookbooks/windows/resources/batch.rb @@ -0,0 +1,36 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Resource:: batch +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +actions :run + +attribute :command, :kind_of => String, :name_attribute => true +attribute :cwd, :kind_of => String, :default => nil +attribute :code, :kind_of => String, :default => nil +attribute :user, :kind_of => [ String, Integer ], :default => nil +attribute :group, :kind_of => [ String, Integer ], :default => nil +attribute :creates, :kind_of => [ String ], :default => nil +attribute :flags, :kind_of => [ String ], :default => nil +attribute :returns, :kind_of => [Integer, Array], :default => 0 + +def initialize(name, run_context=nil) + super + @action = :run + @command = name +end diff --git a/cookbooks/windows/resources/feature.rb b/cookbooks/windows/resources/feature.rb new file mode 100644 index 0000000..4adf758 --- /dev/null +++ b/cookbooks/windows/resources/feature.rb @@ -0,0 +1,44 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Resource:: feature +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +include Windows::Helper + +actions :install, :remove, :delete + +attribute :feature_name, :kind_of => String, :name_attribute => true +attribute :source, :kind_of => String +attribute :all, :kind_of => [ TrueClass, FalseClass ], :default => false + +def initialize(name, run_context=nil) + super + @action = :install + @provider = lookup_provider_constant(locate_default_provider) +end + +private +def locate_default_provider + if node['windows'].attribute?(:feature_provider) + "windows_feature_#{node['windows']['feature_provider']}" + elsif ::File.exists?(locate_sysnative_cmd('dism.exe')) + :windows_feature_dism + elsif ::File.exists?(locate_sysnative_cmd('servermanagercmd.exe')) + :windows_feature_servermanagercmd + end +end diff --git a/cookbooks/windows/resources/font.rb b/cookbooks/windows/resources/font.rb new file mode 100644 index 0000000..57c73f0 --- /dev/null +++ b/cookbooks/windows/resources/font.rb @@ -0,0 +1,25 @@ +# +# Author:: Sander Botman +# Cookbook Name:: windows +# Resource:: font +# +# Copyright:: 2014, Schuberg Philis BV. +# +# 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. +# + +actions :install + +default_action :install + +attribute :file, :kind_of => String, :name_attribute => true diff --git a/cookbooks/windows/resources/pagefile.rb b/cookbooks/windows/resources/pagefile.rb new file mode 100644 index 0000000..4f488dc --- /dev/null +++ b/cookbooks/windows/resources/pagefile.rb @@ -0,0 +1,29 @@ +# +# Author:: Kevin Moser () +# Cookbook Name:: windows +# Resource:: pagefile +# +# Copyright:: 2012, Nordstrom, Inc. +# +# 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. +# + +actions :set, :delete + +attribute :name, :kind_of => String, :name_attribute => true +attribute :system_managed, :kind_of => [TrueClass, FalseClass] +attribute :automatic_managed, :kind_of => [TrueClass, FalseClass], :default => false +attribute :initial_size, :kind_of => Integer +attribute :maximum_size, :kind_of => Integer + +default_action :set diff --git a/cookbooks/windows/resources/path.rb b/cookbooks/windows/resources/path.rb new file mode 100644 index 0000000..84f5523 --- /dev/null +++ b/cookbooks/windows/resources/path.rb @@ -0,0 +1,28 @@ +# +# Author:: Paul Morton () +# Cookbook Name:: windows +# Resource:: path +# +# Copyright:: 2011, Business Intelligence Associates, Inc +# +# 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. +# + +def initialize(name,run_context=nil) + super + @action = :add +end + +actions :add, :remove + +attribute :path, :kind_of => String, :name_attribute => true diff --git a/cookbooks/windows/resources/printer.rb b/cookbooks/windows/resources/printer.rb new file mode 100644 index 0000000..5effa33 --- /dev/null +++ b/cookbooks/windows/resources/printer.rb @@ -0,0 +1,41 @@ +# +# Author:: Doug Ireton () +# Cookbook Name:: windows +# Resource:: printer +# +# Copyright:: 2012, Nordstrom, Inc. +# +# 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. +# +# See here for more info: +# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394492(v=vs.85).aspx + +require 'resolv' + +actions :create, :delete + +default_action :create + +attribute :device_id, :kind_of => String, :name_attribute => true, + :required => true +attribute :comment, :kind_of => String + +attribute :default, :kind_of => [ TrueClass, FalseClass ], :default => false +attribute :driver_name, :kind_of => String, :required => true +attribute :location, :kind_of => String +attribute :shared, :kind_of => [ TrueClass, FalseClass ], :default => false +attribute :share_name, :kind_of => String + +attribute :ipv4_address, :kind_of => String, :regex => Resolv::IPv4::Regex + +attr_accessor :exists diff --git a/cookbooks/windows/resources/printer_port.rb b/cookbooks/windows/resources/printer_port.rb new file mode 100644 index 0000000..b79a6fc --- /dev/null +++ b/cookbooks/windows/resources/printer_port.rb @@ -0,0 +1,40 @@ +# +# Author:: Doug Ireton () +# Cookbook Name:: windows +# Resource:: printer_port +# +# Copyright:: 2012, Nordstrom, Inc. +# +# 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. +# +# See here for more info: +# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394492(v=vs.85).aspx + +require 'resolv' + +actions :create, :delete + +default_action :create + +attribute :ipv4_address, :name_attribute => true, :kind_of => String, + :required => true, :regex => Resolv::IPv4::Regex + +attribute :port_name , :kind_of => String +attribute :port_number , :kind_of => Fixnum, :default => 9100 +attribute :port_description, :kind_of => String +attribute :snmp_enabled , :kind_of => [ TrueClass, FalseClass ], + :default => false + +attribute :port_protocol, :kind_of => Fixnum, :default => 1, :equal_to => [1, 2] + +attr_accessor :exists diff --git a/cookbooks/windows/resources/reboot.rb b/cookbooks/windows/resources/reboot.rb new file mode 100644 index 0000000..014e201 --- /dev/null +++ b/cookbooks/windows/resources/reboot.rb @@ -0,0 +1,29 @@ +# +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Resource:: reboot +# +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +actions :request, :cancel + +attribute :timeout, :kind_of => Integer, :name_attribute => true +attribute :reason, :kind_of => String, :default => '' + +def initialize(name,run_context=nil) + super + @action = :request +end diff --git a/cookbooks/windows/resources/registry.rb b/cookbooks/windows/resources/registry.rb new file mode 100644 index 0000000..ffe6cf2 --- /dev/null +++ b/cookbooks/windows/resources/registry.rb @@ -0,0 +1,34 @@ +# +# Author:: Doug MacEachern () +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Resource:: registry +# +# Copyright:: 2010, VMware, Inc. +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +actions :create, :modify, :force_modify, :remove + +attribute :key_name, :kind_of => String, :name_attribute => true +attribute :values, :kind_of => Hash +attribute :type, :kind_of => Symbol, :default => nil, :equal_to => [:binary, :string, :multi_string, :expand_string, :dword, :dword_big_endian, :qword] + +def initialize(name, run_context=nil) + super + @action = :modify + @key_name = name + Chef::Log.warn("Please use the registry_key resource in Chef Client 11. The windows_registry LWRP is still supported for Chef Client 10, but is deprecated in future versions.") +end diff --git a/cookbooks/windows/resources/shortcut.rb b/cookbooks/windows/resources/shortcut.rb new file mode 100644 index 0000000..eb6268b --- /dev/null +++ b/cookbooks/windows/resources/shortcut.rb @@ -0,0 +1,35 @@ +# +# Author:: Doug MacEachern +# Cookbook Name:: windows +# Resource:: shortcut +# +# Copyright:: 2010, VMware, Inc. +# +# 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. +# + +actions :create + +default_action :create + +attribute :name, :kind_of => String +attribute :target, :kind_of => String +attribute :arguments, :kind_of => String +attribute :description, :kind_of => String +attribute :cwd, :kind_of => String + +# Covers 0.10.8 and earlier +def initialize(*args) + super + @action = :create +end diff --git a/cookbooks/windows/resources/task.rb b/cookbooks/windows/resources/task.rb new file mode 100644 index 0000000..fa1fab4 --- /dev/null +++ b/cookbooks/windows/resources/task.rb @@ -0,0 +1,50 @@ +# +# Author:: Paul Mooring () +# Cookbook Name:: windows +# Resource:: task +# +# Copyright:: 2012, Chef Software, Inc. +# +# 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. +# + +# Passwords can't be loaded for existing tasks, making :modify both confusing +# and not very useful +actions :create, :delete, :run, :change, :enable, :disable + +attribute :name, :kind_of => String, :name_attribute => true, :regex => [ /\A[^\\\/\:\*\?\<\>\|]+\z/ ] +attribute :command, :kind_of => String +attribute :cwd, :kind_of => String +attribute :user, :kind_of => String, :default => nil +attribute :password, :kind_of => String, :default => nil +attribute :run_level, :equal_to => [:highest, :limited], :default => :limited +attribute :force, :kind_of => [ TrueClass, FalseClass ], :default => false +attribute :frequency_modifier, :kind_of => Integer, :default => 1 +attribute :frequency, :equal_to => [:minute, + :hourly, + :daily, + :weekly, + :monthly, + :once, + :on_logon, + :onstart, + :on_idle], :default => :hourly +attribute :start_day, :kind_of => String, :default => nil +attribute :start_time, :kind_of => String, :default => nil + +attr_accessor :exists, :status, :enabled + +def initialize(name, run_context=nil) + super + @action = :create +end diff --git a/cookbooks/windows/resources/zipfile.rb b/cookbooks/windows/resources/zipfile.rb new file mode 100644 index 0000000..3a802ae --- /dev/null +++ b/cookbooks/windows/resources/zipfile.rb @@ -0,0 +1,33 @@ +# +# Author:: Doug MacEachern () +# Author:: Seth Chisamore () +# Cookbook Name:: windows +# Resource:: zipfile +# +# Copyright:: 2010, VMware, Inc. +# Copyright:: 2011, Chef Software, Inc. +# +# 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. +# + +actions :unzip, :zip + +attribute :path, :kind_of => String, :name_attribute => true +attribute :source, :kind_of => String +attribute :overwrite, :kind_of => [ TrueClass, FalseClass ], :default => false +attribute :checksum, :kind_of => String + +def initialize(name, run_context=nil) + super + @action = :unzip +end diff --git a/cookbooks/xml/CHANGELOG.md b/cookbooks/xml/CHANGELOG.md new file mode 100644 index 0000000..2420032 --- /dev/null +++ b/cookbooks/xml/CHANGELOG.md @@ -0,0 +1,73 @@ +v1.2.13 (2014-02-18) +-------------------- +- Reverting compile_time work + +v1.2.12 (2014-02-18) +-------------------- +- Fixing last patch to play nicely with Chef Sugar + +v1.2.11 (2014-02-18) +-------------------- +- Fixing chef_gem for Chef below 12.1.0 + +v1.2.10 (2014-02-17) +-------------------- +- Being explicit about usage of the chef_gem's compile_time property. +- Eliminating future deprecation warnings in Chef 12.1.0. + +v1.2.9 (2014-12-10) +------------------- +- Re-release with stove 3.2.2 to get a metadata.rb + +v1.2.8 (2014-12-09) +------------------- +- [#11] Fix warning message from build-essential +- [#13] pin nokogiri to a working version + +v1.2.6 (2014-06-17) +------------------- +- [COOK-4468] Only set ENV variable when needed + + +v1.2.4 (2014-03-27) +------------------- +- [COOK-4474] - Bump apt and yum versions in Berksfile, Lock to build-essentials 1.4 +- [COOK-4468] - Set NOKOGIRI_USE_SYSTEM_LIBRARIES env variable + + +v1.2.2 (2014-02-27) +------------------- +[COOK-4382] - Fix xml cookbook spec test +[COOK-4304] - Set proper packages for SUSE 11 + + +v1.2.1 +------ +### Improvement +- [COOK-4304](https://tickets.chef.io/browse/COOK-4304) - Now sets proper packages for SUSE 11 + + +v1.2.0 +------ +### Improvement +- **[COOK-3462](https://tickets.chef.io/browse/COOK-3462)** - Allow installing packages during compile time + + +v1.1.2 +------ +- [COOK-2059] - missing dependency on build-essential + +v1.1.0 +------ +- [COOK-1826] - support nokogiri chef_gem +- [COOK-1902] - add support for archlinux + +v1.0.4 +------ +- [COOK-1232] - add xslt to xml cookbook + +v1.0.2 +------ +- [COOK-953] - Add FreeBSD support +- [COOK-775] - Add Amazon Linux support + diff --git a/cookbooks/xml/README.md b/cookbooks/xml/README.md new file mode 100644 index 0000000..76d7ba1 --- /dev/null +++ b/cookbooks/xml/README.md @@ -0,0 +1,52 @@ +XML Cookbook +============ +[![Build Status](https://secure.travis-ci.org/chef-cookbooks/xml.png?branch=master)](http://travis-ci.org/chef-cookbooks/xml) + +Installs development package for libxml. + + +Requirements +------------ +Debian, Ubuntu, CentOS, Red Hat, Scientific, Fedora, SUSE, ArchLinux + +Attributes +---------- +- `node['xml']['packages']` - Array of package names that should be installed +- `node['xml']['nokogiri']['use_system_libraries']` - Whether to use system libraries for nokogiri (defaults to `true`) + + +Recipes +------- +### default +Installs the development packages for libxml2 and libxslt. + +For installing the packages during compile time: + +```ruby +node.set['xml']['compiletime'] = true +include_recipe 'xml::default' +``` + +### ruby +Installs the nokogiri gem into Chef's Ruby environment so it can be used in recipes. + + +License & Authors +----------------- +- Author:: Joshua Timberman () + +```text +Copyright 2009-2013, Chef Software, Inc. + +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/cookbooks/xml/attributes/default.rb b/cookbooks/xml/attributes/default.rb new file mode 100644 index 0000000..e9ffd0c --- /dev/null +++ b/cookbooks/xml/attributes/default.rb @@ -0,0 +1,35 @@ +# +# Cookbook Name:: xml +# Recipe:: default +# +# Copyright 2010-2013, Chef Software, Inc. +# +# 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. +# + +default['xml']['compiletime'] = false + +case node['platform_family'] +when 'rhel', 'fedora', 'suse' + default['xml']['packages'] = %w(libxml2-devel libxslt-devel) +when 'ubuntu', 'debian' + default['xml']['packages'] = %w(libxml2-dev libxslt-dev) +when 'freebsd', 'arch' + default['xml']['packages'] = %w(libxml2 libxslt) +end + +default['xml']['nokogiri']['use_system_libraries'] = false + +# Newest versions will not compile with system libraries +# https://github.com/sparklemotion/nokogiri/issues/1099 +default['xml']['nokogiri']['version'] = '1.6.2.1' diff --git a/cookbooks/xml/metadata.json b/cookbooks/xml/metadata.json new file mode 100644 index 0000000..7e7dd17 --- /dev/null +++ b/cookbooks/xml/metadata.json @@ -0,0 +1,42 @@ +{ + "name": "xml", + "version": "1.2.13", + "description": "Installs xml", + "long_description": "XML Cookbook\n============\n[![Build Status](https://secure.travis-ci.org/chef-cookbooks/xml.png?branch=master)](http://travis-ci.org/chef-cookbooks/xml)\n\nInstalls development package for libxml.\n\n\nRequirements\n------------\nDebian, Ubuntu, CentOS, Red Hat, Scientific, Fedora, SUSE, ArchLinux\n\nAttributes\n----------\n- `node['xml']['packages']` - Array of package names that should be installed\n- `node['xml']['nokogiri']['use_system_libraries']` - Whether to use system libraries for nokogiri (defaults to `true`)\n\n\nRecipes\n-------\n### default\nInstalls the development packages for libxml2 and libxslt.\n\nFor installing the packages during compile time:\n\n```ruby\nnode.set['xml']['compiletime'] = true\ninclude_recipe 'xml::default'\n```\n\n### ruby\nInstalls the nokogiri gem into Chef's Ruby environment so it can be used in recipes.\n\n\nLicense & Authors\n-----------------\n- Author:: Joshua Timberman ()\n\n```text\nCopyright 2009-2013, Chef Software, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n", + "maintainer": "Chef Software, Inc.", + "maintainer_email": "cookbooks@chef.io", + "license": "Apache 2.0", + "platforms": { + "amazon": ">= 0.0.0", + "arch": ">= 0.0.0", + "centos": ">= 0.0.0", + "debian": ">= 0.0.0", + "fedora": ">= 0.0.0", + "freebsd": ">= 0.0.0", + "redhat": ">= 0.0.0", + "scientific": ">= 0.0.0", + "suse": ">= 0.0.0", + "ubuntu": ">= 0.0.0" + }, + "dependencies": { + "build-essential": ">= 0.0.0", + "chef-sugar": ">= 0.0.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + "xml": "Installs libxml development packages" + } +} \ No newline at end of file diff --git a/cookbooks/xml/recipes/default.rb b/cookbooks/xml/recipes/default.rb new file mode 100644 index 0000000..53ddeb8 --- /dev/null +++ b/cookbooks/xml/recipes/default.rb @@ -0,0 +1,25 @@ +# +# Cookbook Name:: xml +# Recipe:: default +# +# Copyright 2010-2013, Chef Software, Inc. +# +# 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. +# + +node['xml']['packages'].each do |pkg| + r = package pkg do + action(node['xml']['compiletime'] ? :nothing : :install) + end + r.run_action(:install) if node['xml']['compiletime'] +end diff --git a/cookbooks/xml/recipes/ruby.rb b/cookbooks/xml/recipes/ruby.rb new file mode 100644 index 0000000..2e114dd --- /dev/null +++ b/cookbooks/xml/recipes/ruby.rb @@ -0,0 +1,46 @@ +# +# Cookbook Name:: xml +# Recipe:: ruby +# +# Author:: Joseph Holsten () +# +# Copyright 2008-2013, Chef Software, Inc. +# +# 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. +# + +include_recipe 'chef-sugar' + +execute 'apt-get update' do + ignore_failure true + action :nothing +end.run_action(:run) if 'debian' == node['platform_family'] + +node.default['build-essential']['compile_time'] = true +node.default['xml']['compiletime'] = true +include_recipe 'build-essential::default' +include_recipe 'xml::default' + +if node['xml']['nokogiri']['use_system_libraries'] + if node['xml']['nokogiri']['version'].nil? || + version(node['xml']['nokogiri']['version']).satisfies?('> 1.6.1') + Chef::Application.fatal!("You must specify a version less than or equal to 1.6.1 of nokogiri to use system libraries. You set: #{node['xml']['nokogiri']['version']}.") + else + ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] = node['xml']['nokogiri']['use_system_libraries'].to_s + end +end + +chef_gem 'nokogiri' do + version node['xml']['nokogiri']['version'] + action :install +end diff --git a/cookbooks/yum-epel/CHANGELOG.md b/cookbooks/yum-epel/CHANGELOG.md new file mode 100644 index 0000000..14e5662 --- /dev/null +++ b/cookbooks/yum-epel/CHANGELOG.md @@ -0,0 +1,63 @@ +yum-epel Cookbook CHANGELOG +====================== + +v0.6.0 (2015-01-03) +------------------- +- Adding EL7 support + +v0.5.3 (2014-10-28) +------------------- +- Revert Use HTTPS for GPG keys and mirror lists + +v0.5.2 (2014-10-28) +------------------- +- Use HTTPS for GPG keys and mirror lists +- Use local key on Amazon Linux + +v0.5.0 (2014-09-02) +------------------- +- Add all attribute available to LWRP to allow for tuning. + +v0.4.0 (2014-07-27) +------------------- +- [#9] Allowing list of repositories to reference configurable. + + +v0.3.6 (2014-04-09) +------------------- +- [COOK-4509] add RHEL7 support to yum-epel cookbook + + +v0.3.4 (2014-02-19) +------------------- +COOK-4353 - Fixing typo in readme + + +v0.3.2 (2014-02-13) +------------------- +Updating README to explain the 'managed' parameter + + +v0.3.0 (2014-02-12) +------------------- +[COOK-4292] - Do not manage secondary repos by default + + +v0.2.0 +------ +Adding Amazon Linux support + + +v0.1.6 +------ +Fixing up attribute values for EL6 + + +v0.1.4 +------ +Adding CHANGELOG.md + + +v0.1.0 +------ +initial release diff --git a/cookbooks/yum-epel/README.md b/cookbooks/yum-epel/README.md new file mode 100644 index 0000000..54eb287 --- /dev/null +++ b/cookbooks/yum-epel/README.md @@ -0,0 +1,162 @@ +yum-epel Cookbook +============ + +The yum-epel cookbook takes over management of the default +repositoryids shipped with epel-release. It allows attribute +manipulation of `epel`, `epel-debuginfo`, `epel-source`, `epel-testing`, +`epel-testing-debuginfo`, and `epel-testing-source`. + +Requirements +------------ +* Chef 11 or higher +* yum cookbook version 3.0.0 or higher + +Attributes +---------- +The following attributes are set by default + +``` ruby +default['yum-epel']['repositories'] = %w{epel epel-debuginfo epel-source epel-testing epel-testing-debuginfo epel-testing-source} +``` + +``` ruby +default['yum']['epel']['repositoryid'] = 'epel' +default['yum']['epel']['description'] = 'Extra Packages for Enterprise Linux 6 - $basearch' +default['yum']['epel']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-5&arch=$basearch' +default['yum']['epel']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' +default['yum']['epel']['failovermethod'] = 'priority' +default['yum']['epel']['gpgcheck'] = true +default['yum']['epel']['enabled'] = true +default['yum']['epel']['managed'] = true +``` + +``` ruby +default['yum']['epel-debuginfo']['repositoryid'] = 'epel-debuginfo' +default['yum']['epel-debuginfo']['description'] = 'Extra Packages for Enterprise Linux 6 - $basearch - Debug' +default['yum']['epel-debuginfo']['mirrorlist'] = 'https://mirrors.fedoraproject.org/metalink?repo=epel-debug-6&arch=$basearch' +default['yum']['epel-debuginfo']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' +default['yum']['epel-debuginfo']['failovermethod'] = 'priority' +default['yum']['epel-debuginfo']['gpgcheck'] = true +default['yum']['epel-debuginfo']['enabled'] = false +default['yum']['epel-debuginfo']['managed'] = false +``` + +``` ruby +default['yum']['epel-source']['repositoryid'] = 'epel-source' +default['yum']['epel-source']['description'] = 'Extra Packages for Enterprise Linux 6 - $basearch - Source' +default['yum']['epel-source']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-source-6&arch=$basearch' +default['yum']['epel-source']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' +default['yum']['epel-source']['failovermethod'] = 'priority' +default['yum']['epel-source']['gpgcheck'] = true +default['yum']['epel-source']['enabled'] = false +default['yum']['epel-source']['managed'] = false +``` + +``` ruby +default['yum']['epel-testing']['repositoryid'] = 'epel-testing' +default['yum']['epel-testing']['description'] = 'Extra Packages for Enterprise Linux 6 - Testing - $basearch' +default['yum']['epel-testing']['mirrorlist'] = 'https://mirrors.fedoraproject.org/metalink?repo=testing-epel6&arch=$basearch' +default['yum']['epel-testing']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6r' +default['yum']['epel-testing']['failovermethod'] = 'priority' +default['yum']['epel-testing']['gpgcheck'] = true +default['yum']['epel-testing']['enabled'] = false +default['yum']['epel-testing']['managed'] = false +``` + +``` ruby +default['yum']['epel-testing-debuginfo']['repositoryid'] = 'epel-testing-debuginfo' +default['yum']['epel-testing-debuginfo']['description'] = 'Extra Packages for Enterprise Linux 6 - Testing - $basearch Debug' +default['yum']['epel-testing-debuginfo']['mirrorlist'] = 'https://mirrors.fedoraproject.org/metalink?repo=testing-debug-epel6&arch=$basearch' +default['yum']['epel-testing-debuginfo']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' +default['yum']['epel-testing-debuginfo']['failovermethod'] = 'priority' +default['yum']['epel-testing-debuginfo']['gpgcheck'] = true +default['yum']['epel-testing-debuginfo']['enabled'] = false +default['yum']['epel-testing-debuginfo']['managed'] = false +``` + +``` ruby +default['yum']['epel-testing-source']['repositoryid'] = 'epel-testing-source' +default['yum']['epel-testing-source']['description'] = 'Extra Packages for Enterprise Linux 6 - Testing - $basearch Source' +default['yum']['epel-testing-source']['mirrorlist'] = 'https://mirrors.fedoraproject.org/metalink?repo=testing-source-epel6&arch=$basearch' +default['yum']['epel-testing-source']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' +default['yum']['epel-testing-source']['failovermethod'] = 'priority' +default['yum']['epel-testing-source']['gpgcheck'] = true +default['yum']['epel-testing-source']['enabled'] = false +default['yum']['epel-testing-source']['managed'] = false +``` + +Recipes +------- +* default - Walks through node attributes and feeds a yum_resource + parameters. The following is an example a resource generated by the + recipe during compilation. + +```ruby + yum_repository 'epel' do + mirrorlist 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-5&arch=$basearch' + description 'Extra Packages for Enterprise Linux 5 - $basearch' + enabled true + gpgcheck true + gpgkey 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL' + end +``` + +Usage Example +------------- +To disable the epel repository through a Role or Environment definition + +``` +default_attributes( + :yum => { + :epel => { + :enabled => { + false + } + } + } + ) +``` + +Uncommonly used repositoryids are not managed by default. This is +speeds up integration testing pipelines by avoiding yum-cache builds +that nobody cares about. To enable the epel-testing repository with a +wrapper cookbook, place the following in a recipe: + +``` +node.default['yum']['epel-testing']['enabled'] = true +node.default['yum']['epel-testing']['managed'] = true +include_recipe 'yum-epel' +``` + +More Examples +------------- +Point the epel repositories at an internally hosted server. + +``` +node.default['yum']['epel']['enabled'] = true +node.default['yum']['epel']['mirrorlist'] = nil +node.default['yum']['epel']['baseurl'] = 'https://internal.example.com/centos/6/os/x86_64' +node.default['yum']['epel']['sslverify'] = false + +include_recipe 'yum-epel' +``` + +License & Authors +----------------- +- Author:: Sean OMeara () + +```text +Copyright:: 2011-2013 Opscode, Inc. + +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/cookbooks/yum-epel/attributes/default.rb b/cookbooks/yum-epel/attributes/default.rb new file mode 100644 index 0000000..5b03a69 --- /dev/null +++ b/cookbooks/yum-epel/attributes/default.rb @@ -0,0 +1 @@ +default['yum-epel']['repositories'] = %w(epel epel-debuginfo epel-source epel-testing epel-testing-debuginfo epel-testing-source) diff --git a/cookbooks/yum-epel/attributes/epel-debuginfo.rb b/cookbooks/yum-epel/attributes/epel-debuginfo.rb new file mode 100644 index 0000000..0e72757 --- /dev/null +++ b/cookbooks/yum-epel/attributes/epel-debuginfo.rb @@ -0,0 +1,28 @@ +default['yum']['epel-debuginfo']['repositoryid'] = 'epel-debuginfo' + +case node['platform'] +when 'amazon' + default['yum']['epel-debuginfo']['description'] = 'Extra Packages for Enterprise Linux 6 - $basearch' + default['yum']['epel-debuginfo']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-6&arch=$basearch' + default['yum']['epel-debuginfo']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' +else + case node['platform_version'].to_i + when 5 + default['yum']['epel-debuginfo']['description'] = 'Extra Packages for Enterprise Linux 5 - $basearch - Debug' + default['yum']['epel-debuginfo']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-debug-5&arch=$basearch' + default['yum']['epel-debuginfo']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL' + when 6 + default['yum']['epel-debuginfo']['description'] = 'Extra Packages for Enterprise Linux 6 - $basearch - Debug' + default['yum']['epel-debuginfo']['mirrorlist'] = 'https://mirrors.fedoraproject.org/metalink?repo=epel-debug-6&arch=$basearch' + default['yum']['epel-debuginfo']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' + when 7 + default['yum']['epel-debuginfo']['description'] = 'Extra Packages for Enterprise Linux 7 - $basearch - Debug' + default['yum']['epel-debuginfo']['mirrorlist'] = 'https://mirrors.fedoraproject.org/metalink?repo=epel-debug-7&arch=$basearch' + default['yum']['epel-debuginfo']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7' + end +end + +default['yum']['epel-debuginfo']['failovermethod'] = 'priority' +default['yum']['epel-debuginfo']['gpgcheck'] = true +default['yum']['epel-debuginfo']['enabled'] = false +default['yum']['epel-debuginfo']['managed'] = false diff --git a/cookbooks/yum-epel/attributes/epel-source.rb b/cookbooks/yum-epel/attributes/epel-source.rb new file mode 100644 index 0000000..1433eed --- /dev/null +++ b/cookbooks/yum-epel/attributes/epel-source.rb @@ -0,0 +1,28 @@ +default['yum']['epel-source']['repositoryid'] = 'epel-source' + +case node['platform'] +when 'amazon' + default['yum']['epel-source']['description'] = 'Extra Packages for Enterprise Linux 6 - $basearch' + default['yum']['epel-source']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-6&arch=$basearch' + default['yum']['epel-source']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' +else + case node['platform_version'].to_i + when 5 + default['yum']['epel-source']['description'] = 'Extra Packages for Enterprise Linux 5 - $basearch - Source' + default['yum']['epel-source']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-source-5&arch=$basearch' + default['yum']['epel-source']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL' + when 6 + default['yum']['epel-source']['description'] = 'Extra Packages for Enterprise Linux 6 - $basearch - Source' + default['yum']['epel-source']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-source-6&arch=$basearch' + default['yum']['epel-source']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' + when 7 + default['yum']['epel-source']['description'] = 'Extra Packages for Enterprise Linux 7 - $basearch - Source' + default['yum']['epel-source']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-source-7&arch=$basearch' + default['yum']['epel-source']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7' + end +end + +default['yum']['epel-source']['failovermethod'] = 'priority' +default['yum']['epel-source']['gpgcheck'] = true +default['yum']['epel-source']['enabled'] = false +default['yum']['epel-source']['managed'] = false diff --git a/cookbooks/yum-epel/attributes/epel-testing-debuginfo.rb b/cookbooks/yum-epel/attributes/epel-testing-debuginfo.rb new file mode 100644 index 0000000..ef5f6f3 --- /dev/null +++ b/cookbooks/yum-epel/attributes/epel-testing-debuginfo.rb @@ -0,0 +1,28 @@ +default['yum']['epel-testing-debuginfo']['repositoryid'] = 'epel-testing-debuginfo' + +case node['platform'] +when 'amazon' + default['yum']['epel-testing-debuginfo']['description'] = 'Extra Packages for Enterprise Linux 6 - $basearch' + default['yum']['epel-testing-debuginfo']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-6&arch=$basearch' + default['yum']['epel-testing-debuginfo']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' +else + case node['platform_version'].to_i + when 5 + default['yum']['epel-testing-debuginfo']['description'] = 'Extra Packages for Enterprise Linux 5 - Testing - $basearch Debug' + default['yum']['epel-testing-debuginfo']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=testing-debug-epel5&arch=$basearch' + default['yum']['epel-testing-debuginfo']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL' + when 6 + default['yum']['epel-testing-debuginfo']['description'] = 'Extra Packages for Enterprise Linux 6 - Testing - $basearch Debug' + default['yum']['epel-testing-debuginfo']['mirrorlist'] = 'https://mirrors.fedoraproject.org/metalink?repo=testing-debug-epel6&arch=$basearch' + default['yum']['epel-testing-debuginfo']['gpgkey'] = 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' + when 7 + default['yum']['epel-testing-debuginfo']['description'] = 'Extra Packages for Enterprise Linux 7 - Testing - $basearch Debug' + default['yum']['epel-testing-debuginfo']['mirrorlist'] = 'https://mirrors.fedoraproject.org/metalink?repo=testing-debug-epel7&arch=$basearch' + default['yum']['epel-testing-debuginfo']['gpgkey'] = 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7' + end +end + +default['yum']['epel-testing-debuginfo']['failovermethod'] = 'priority' +default['yum']['epel-testing-debuginfo']['gpgcheck'] = true +default['yum']['epel-testing-debuginfo']['enabled'] = false +default['yum']['epel-testing-debuginfo']['managed'] = false diff --git a/cookbooks/yum-epel/attributes/epel-testing-source.rb b/cookbooks/yum-epel/attributes/epel-testing-source.rb new file mode 100644 index 0000000..93aa882 --- /dev/null +++ b/cookbooks/yum-epel/attributes/epel-testing-source.rb @@ -0,0 +1,28 @@ +default['yum']['epel-testing-source']['repositoryid'] = 'epel-testing-source' + +case node['platform'] +when 'amazon' + default['yum']['epel-testing-source']['description'] = 'Extra Packages for Enterprise Linux 6 - $basearch' + default['yum']['epel-testing-source']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-6&arch=$basearch' + default['yum']['epel-testing-source']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' +else + case node['platform_version'].to_i + when 5 + default['yum']['epel-testing-source']['description'] = 'Extra Packages for Enterprise Linux 5 - Testing - $basearch Source' + default['yum']['epel-testing-source']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=testing-source-epel5&arch=$basearch' + default['yum']['epel-testing-source']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL' + when 6 + default['yum']['epel-testing-source']['description'] = 'Extra Packages for Enterprise Linux 6 - Testing - $basearch Source' + default['yum']['epel-testing-source']['mirrorlist'] = 'https://mirrors.fedoraproject.org/metalink?repo=testing-source-epel6&arch=$basearch' + default['yum']['epel-testing-source']['gpgkey'] = 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' + when 7 + default['yum']['epel-testing-source']['description'] = 'Extra Packages for Enterprise Linux 7 - Testing - $basearch Source' + default['yum']['epel-testing-source']['mirrorlist'] = 'https://mirrors.fedoraproject.org/metalink?repo=testing-source-epel7&arch=$basearch' + default['yum']['epel-testing-source']['gpgkey'] = 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7' + end +end + +default['yum']['epel-testing-source']['failovermethod'] = 'priority' +default['yum']['epel-testing-source']['gpgcheck'] = true +default['yum']['epel-testing-source']['enabled'] = false +default['yum']['epel-testing-source']['managed'] = false diff --git a/cookbooks/yum-epel/attributes/epel-testing.rb b/cookbooks/yum-epel/attributes/epel-testing.rb new file mode 100644 index 0000000..63df142 --- /dev/null +++ b/cookbooks/yum-epel/attributes/epel-testing.rb @@ -0,0 +1,28 @@ +default['yum']['epel-testing']['repositoryid'] = 'epel-testing' + +case node['platform'] +when 'amazon' + default['yum']['epel-testing']['description'] = 'Extra Packages for Enterprise Linux 6 - $basearch' + default['yum']['epel-testing']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-6&arch=$basearch' + default['yum']['epel-testing']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' +else + case node['platform_version'].to_i + when 5 + default['yum']['epel-testing']['description'] = 'Extra Packages for Enterprise Linux 5 - Testing - $basearch' + default['yum']['epel-testing']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=testing-epel5&arch=$basearch' + default['yum']['epel-testing']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL' + when 6 + default['yum']['epel-testing']['description'] = 'Extra Packages for Enterprise Linux 6 - Testing - $basearch' + default['yum']['epel-testing']['mirrorlist'] = 'https://mirrors.fedoraproject.org/metalink?repo=testing-epel6&arch=$basearch' + default['yum']['epel-testing']['gpgkey'] = 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' + when 7 + default['yum']['epel-testing']['description'] = 'Extra Packages for Enterprise Linux 7 - Testing - $basearch' + default['yum']['epel-testing']['mirrorlist'] = 'https://mirrors.fedoraproject.org/metalink?repo=testing-epel7&arch=$basearch' + default['yum']['epel-testing']['gpgkey'] = 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7' + end +end + +default['yum']['epel-testing']['failovermethod'] = 'priority' +default['yum']['epel-testing']['gpgcheck'] = true +default['yum']['epel-testing']['enabled'] = false +default['yum']['epel-testing']['managed'] = false diff --git a/cookbooks/yum-epel/attributes/epel.rb b/cookbooks/yum-epel/attributes/epel.rb new file mode 100644 index 0000000..07dceb6 --- /dev/null +++ b/cookbooks/yum-epel/attributes/epel.rb @@ -0,0 +1,28 @@ +default['yum']['epel']['repositoryid'] = 'epel' + +case node['platform'] +when 'amazon' + default['yum']['epel']['description'] = 'Extra Packages for Enterprise Linux 6 - $basearch' + default['yum']['epel']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-6&arch=$basearch' + default['yum']['epel']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' +else + case node['platform_version'].to_i + when 5 + default['yum']['epel']['description'] = 'Extra Packages for Enterprise Linux 5 - $basearch' + default['yum']['epel']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-5&arch=$basearch' + default['yum']['epel']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL' + when 6 + default['yum']['epel']['description'] = 'Extra Packages for Enterprise Linux 6 - $basearch' + default['yum']['epel']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-6&arch=$basearch' + default['yum']['epel']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' + when 7 + default['yum']['epel']['description'] = 'Extra Packages for Enterprise Linux 7 - $basearch' + default['yum']['epel']['mirrorlist'] = 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-7&arch=$basearch' + default['yum']['epel']['gpgkey'] = 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7' + end +end + +default['yum']['epel']['failovermethod'] = 'priority' +default['yum']['epel']['gpgcheck'] = true +default['yum']['epel']['enabled'] = true +default['yum']['epel']['managed'] = true diff --git a/cookbooks/yum-epel/metadata.json b/cookbooks/yum-epel/metadata.json new file mode 100644 index 0000000..f60c0d8 --- /dev/null +++ b/cookbooks/yum-epel/metadata.json @@ -0,0 +1,34 @@ +{ + "name": "yum-epel", + "version": "0.6.0", + "description": "Installs/Configures yum-epel", + "long_description": "", + "maintainer": "Chef", + "maintainer_email": "Sean OMeara ", + "license": "Apache 2.0", + "platforms": { + "redhat": ">= 0.0.0", + "centos": ">= 0.0.0", + "scientific": ">= 0.0.0", + "amazon": ">= 0.0.0" + }, + "dependencies": { + "yum": "~> 3.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + } +} \ No newline at end of file diff --git a/cookbooks/yum-epel/recipes/default.rb b/cookbooks/yum-epel/recipes/default.rb new file mode 100644 index 0000000..8ed695e --- /dev/null +++ b/cookbooks/yum-epel/recipes/default.rb @@ -0,0 +1,61 @@ +# +# Author:: Sean OMeara () +# Recipe:: yum-epel::default +# +# Copyright 2013, Chef +# +# 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. + +node['yum-epel']['repositories'].each do |repo| + + if node['yum'][repo]['managed'] + yum_repository repo do + baseurl node['yum'][repo]['baseurl'] + cost node['yum'][repo]['cost'] + description node['yum'][repo]['description'] + enabled node['yum'][repo]['enabled'] + enablegroups node['yum'][repo]['enablegroups'] + exclude node['yum'][repo]['exclude'] + failovermethod node['yum'][repo]['failovermethod'] + fastestmirror_enabled node['yum'][repo]['fastestmirror_enabled'] + gpgcheck node['yum'][repo]['gpgcheck'] + gpgkey node['yum'][repo]['gpgkey'] + http_caching node['yum'][repo]['http_caching'] + include_config node['yum'][repo]['include_config'] + includepkgs node['yum'][repo]['includepkgs'] + keepalive node['yum'][repo]['keepalive'] + max_retries node['yum'][repo]['max_retries'] + metadata_expire node['yum'][repo]['metadata_expire'] + mirror_expire node['yum'][repo]['mirror_expire'] + mirrorlist node['yum'][repo]['mirrorlist'] + mirrorlist_expire node['yum'][repo]['mirrorlist_expire'] + password node['yum'][repo]['password'] + priority node['yum'][repo]['priority'] + proxy node['yum'][repo]['proxy'] + proxy_username node['yum'][repo]['proxy_username'] + proxy_password node['yum'][repo]['proxy_password'] + report_instanceid node['yum'][repo]['report_instanceid'] + repositoryid node['yum'][repo]['repositoryid'] + skip_if_unavailable node['yum'][repo]['skip_if_unavailable'] + source node['yum'][repo]['source'] + sslcacert node['yum'][repo]['sslcacert'] + sslclientcert node['yum'][repo]['sslclientcert'] + sslclientkey node['yum'][repo]['sslclientkey'] + sslverify node['yum'][repo]['sslverify'] + timeout node['yum'][repo]['timeout'] + username node['yum'][repo]['username'] + + action :create + end + end +end diff --git a/cookbooks/yum-mysql-community/CHANGELOG.md b/cookbooks/yum-mysql-community/CHANGELOG.md new file mode 100644 index 0000000..11fa69f --- /dev/null +++ b/cookbooks/yum-mysql-community/CHANGELOG.md @@ -0,0 +1,98 @@ +yum-mysql-community Cookbook CHANGELOG +====================== +This file is used to list changes made in each version of the yum-mysql-community cookbook. + +v0.1.17 (2015-04-06) +-------------------- +- Updating pubkey link from someara to chef-client github orgs + +v0.1.16 (2015-03-25) +-------------------- +- Adding support Amazon Linux 2015.03 to all channels + +v0.1.15 (2015-03-25) +-------------------- +- Added support for amazon linux 2015.03 + +v0.1.14 (2015-03-12) +-------------------- +- The content of 0.1.13 is questionable: didn't have changelog entry, may have had merged attribute change, but let's be clear and say at least this version 0.1.14 is the right thing. + +v0.1.13 (2015-03-12) +-------------------- +- #3 corrected typo in public key attribute + +v0.1.12 (2015-01-20) +------------------- +- Minor style updates + +v0.1.11 (2014-07-21) +------------------- +- Adding RHEL-7 support + +v0.1.10 (2014-07-21) +------------------- +- Adding mysql-5.7 and centos 7 support + +v0.1.8 (2014-06-18) +------------------- +- Updating to support real RHEL + +v0.1.6 (2014-06-16) +------------------- +Fixing typo in mysql55-community attributes + + +v0.1.4 (2014-06-13) +------------------- +- updating url to keys in cookbook attributes + + +v0.1.2 (2014-06-11) +------------------- +#1 - Move files/mysql_pubkey.asc to files/default/mysql_pubkey.asc + + +v0.1.0 (2014-04-30) +------------------- +Initial release + + +v0.3.6 (2014-04-09) +------------------- +- [COOK-4509] add RHEL7 support to yum-mysql-community cookbook + + +v0.3.4 (2014-02-19) +------------------- +COOK-4353 - Fixing typo in readme + + +v0.3.2 (2014-02-13) +------------------- +Updating README to explain the 'managed' parameter + + +v0.3.0 (2014-02-12) +------------------- +[COOK-4292] - Do not manage secondary repos by default + + +v0.2.0 +------ +Adding Amazon Linux support + + +v0.1.6 +------ +Fixing up attribute values for EL6 + + +v0.1.4 +------ +Adding CHANGELOG.md + + +v0.1.0 +------ +initial release diff --git a/cookbooks/yum-mysql-community/README.md b/cookbooks/yum-mysql-community/README.md new file mode 100644 index 0000000..c642ab1 --- /dev/null +++ b/cookbooks/yum-mysql-community/README.md @@ -0,0 +1,137 @@ +yum-mysql-community Cookbook +============ + +The yum-mysql-community cookbook takes over management of the default +repositoryids shipped with epel-release. It allows attribute +manipulation of `mysql-connectors-community`, `mysql56-community`, and +`mysql57-community-dmr`. + +Requirements +------------ +* Chef 11 or higher +* yum cookbook version 3.0.0 or higher + +Attributes +---------- +The following attributes are set by default + +``` ruby +default['yum']['mysql-connectors-community']['repositoryid'] = 'mysql-connectors-community' +default['yum']['mysql-connectors-community']['description'] = 'MySQL Connectors Community' +default['yum']['mysql-connectors-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-connectors-community/el/$releasever/$basearch/' +default['yum']['mysql-connectors-community']['gpgkey'] = 'https://raw.githubusercontent.com/rs-services/equinix-public/master/cookbooks/db_mysql/files/centos/mysql_pubkey.asc' +default['yum']['mysql-connectors-community']['failovermethod'] = 'priority' +default['yum']['mysql-connectors-community']['gpgcheck'] = true +default['yum']['mysql-connectors-community']['enabled'] = true +``` + +``` ruby +default['yum']['mysql56-community']['repositoryid'] = 'mysql56-community' +default['yum']['mysql56-community']['description'] = 'MySQL 5.6 Community Server' +default['yum']['mysql56-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql56-community/el/$releasever/$basearch/' +default['yum']['mysql56-community']['gpgkey'] = 'https://raw.githubusercontent.com/rs-services/equinix-public/master/cookbooks/db_mysql/files/centos/mysql_pubkey.asc' +default['yum']['mysql56-community']['failovermethod'] = 'priority' +default['yum']['mysql56-community']['gpgcheck'] = true +default['yum']['mysql56-community']['enabled'] = true +``` + +``` ruby +default['yum']['mysql57-community-dmr']['repositoryid'] = 'mysql57-community-dmr' +default['yum']['mysql57-community-dmr']['description'] = 'MySQL 5.7 Community Server Development Milestone Release' +default['yum']['mysql57-community-dmr']['baseurl'] = 'http://repo.mysql.com/yum/mysql56-community/el/$releasever/$basearch/' +default['yum']['mysql57-community-dmr']['gpgkey'] = 'https://raw.githubusercontent.com/rs-services/equinix-public/master/cookbooks/db_mysql/files/centos/mysql_pubkey.asc' +default['yum']['mysql57-community-dmr']['failovermethod'] = 'priority' +default['yum']['mysql57-community-dmr']['gpgcheck'] = true +default['yum']['mysql57-community-dmr']['enabled'] = true +``` + +Recipes +------- +* mysql55 - Sets up the mysql56-community repository on supported + platforms + +```ruby + yum_repository 'mysql55-community' do + mirrorlist 'http://repo.mysql.com/yum/mysql55-community/el/$releasever/$basearch/' + description '' + enabled true + gpgcheck true + end +``` + +* mysql56 - Sets up the mysql56-community repository on supported + platforms + +```ruby + yum_repository 'mysql56-community' do + mirrorlist 'http://repo.mysql.com/yum/mysql56-community/el/$releasever/$basearch/' + description '' + enabled true + gpgcheck true + end +``` + + +* connectors - Sets up the mysql-connectors-community repository on supported + platforms + + +Usage Example +------------- +To disable the epel repository through a Role or Environment definition + +``` +default_attributes( + :yum => { + :mysql57-community-dmr => { + :enabled => { + false + } + } + } + ) +``` + +Uncommonly used repositoryids are not managed by default. This is +speeds up integration testing pipelines by avoiding yum-cache builds +that nobody cares about. To enable the epel-testing repository with a +wrapper cookbook, place the following in a recipe: + +``` +node.default['yum']['mysql57-community-dmr']['enabled'] = true +node.default['yum']['mysql57-community-dmr']['managed'] = true +include_recipe 'mysql57-community-dmr' +``` + +More Examples +------------- +Point the mysql56-community repositories at an internally hosted server. + +``` +node.default['yum']['mysql56-community']['enabled'] = true +node.default['yum']['mysql56-community']['mirrorlist'] = nil +node.default['yum']['mysql56-community']['baseurl'] = 'https://internal.example.com/mysql/mysql56-community/' +node.default['yum']['mysql56-community']['sslverify'] = false + +include_recipe 'mysql56-community' +``` + +License & Authors +----------------- +- Author:: Sean OMeara () + +```text +Copyright:: 2011-2015, Chef Software, Inc. + +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/cookbooks/yum-mysql-community/attributes/mysql-connectors-community.rb b/cookbooks/yum-mysql-community/attributes/mysql-connectors-community.rb new file mode 100644 index 0000000..2ffd3d6 --- /dev/null +++ b/cookbooks/yum-mysql-community/attributes/mysql-connectors-community.rb @@ -0,0 +1,35 @@ +default['yum']['mysql-connectors-community']['repositoryid'] = 'mysql-connectors-community' +default['yum']['mysql-connectors-community']['gpgkey'] = 'https://raw.githubusercontent.com/chef-cookbooks/yum-mysql-community/master/files/default/mysql_pubkey.asc' +default['yum']['mysql-connectors-community']['description'] = 'MySQL Connectors Community' +default['yum']['mysql-connectors-community']['failovermethod'] = 'priority' +default['yum']['mysql-connectors-community']['gpgcheck'] = true +default['yum']['mysql-connectors-community']['enabled'] = true + +case node['platform_family'] +when 'rhel' + case node['platform'] + when 'amazon' + case node['platform_version'].to_i + when 2013 + default['yum']['mysql-connectors-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-connectors-community/el/6/$basearch/' + when 2014 + default['yum']['mysql-connectors-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-connectors-community/el/6/$basearch/' + when 2015 + default['yum']['mysql-connectors-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-connectors-community/el/6/$basearch/' + end + when 'redhat' + case node['platform_version'].to_i + when 5 + # Real Redhat identifies $releasever as 5Server and 6Server + default['yum']['mysql-connectors-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-connectors-community/el/5/$basearch/' + when 6 + default['yum']['mysql-connectors-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-connectors-community/el/6/$basearch/' + when 7 + default['yum']['mysql-connectors-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-connectors-community/el/7/$basearch/' + end + else # other rhel + default['yum']['mysql-connectors-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-connectors-community/el/$releasever/$basearch/' + end +when 'fedora' + default['yum']['mysql-connectors-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-connectors-community/fc/$releasever/$basearch/' +end diff --git a/cookbooks/yum-mysql-community/attributes/mysql55-community.rb b/cookbooks/yum-mysql-community/attributes/mysql55-community.rb new file mode 100644 index 0000000..73e2efd --- /dev/null +++ b/cookbooks/yum-mysql-community/attributes/mysql55-community.rb @@ -0,0 +1,33 @@ +default['yum']['mysql55-community']['repositoryid'] = 'mysql55-community' +default['yum']['mysql55-community']['gpgkey'] = 'https://raw.githubusercontent.com/chef-cookbooks/yum-mysql-community/master/files/default/mysql_pubkey.asc' +default['yum']['mysql55-community']['description'] = 'MySQL 5.5 Community Server' +default['yum']['mysql55-community']['failovermethod'] = 'priority' +default['yum']['mysql55-community']['gpgcheck'] = true +default['yum']['mysql55-community']['enabled'] = true + +case node['platform_family'] +when 'rhel' + case node['platform'] + when 'amazon' + case node['platform_version'].to_i + when 2013 + default['yum']['mysql55-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.5-community/el/6/$basearch/' + when 2014 + default['yum']['mysql55-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.5-community/el/6/$basearch/' + when 2015 + default['yum']['mysql55-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.5-community/el/6/$basearch/' + end + when 'redhat' + case node['platform_version'].to_i + when 5 + # Real Redhat identifies $releasever as 5Server and 6Server + default['yum']['mysql55-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.5-community/el/5/$basearch/' + when 6 + default['yum']['mysql55-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.5-community/el/6/$basearch/' + when 7 + default['yum']['mysql55-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.5-community/el/7/$basearch/' + end + else # other rhel. only 6 and 7 for now + default['yum']['mysql55-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.5-community/el/$releasever/$basearch/' + end +end diff --git a/cookbooks/yum-mysql-community/attributes/mysql56-community.rb b/cookbooks/yum-mysql-community/attributes/mysql56-community.rb new file mode 100644 index 0000000..c301a57 --- /dev/null +++ b/cookbooks/yum-mysql-community/attributes/mysql56-community.rb @@ -0,0 +1,35 @@ +default['yum']['mysql56-community']['repositoryid'] = 'mysql56-community' +default['yum']['mysql56-community']['gpgkey'] = 'https://raw.githubusercontent.com/chef-cookbooks/yum-mysql-community/master/files/default/mysql_pubkey.asc' +default['yum']['mysql56-community']['description'] = 'MySQL 5.6 Community Server' +default['yum']['mysql56-community']['failovermethod'] = 'priority' +default['yum']['mysql56-community']['gpgcheck'] = true +default['yum']['mysql56-community']['enabled'] = true + +case node['platform_family'] +when 'rhel' + case node['platform'] + when 'amazon' + case node['platform_version'].to_i + when 2013 + default['yum']['mysql56-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.6-community/el/6/$basearch/' + when 2014 + default['yum']['mysql56-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.6-community/el/6/$basearch/' + when 2015 + default['yum']['mysql56-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.6-community/el/6/$basearch/' + end + when 'redhat' + case node['platform_version'].to_i + when 5 + # Real Redhat identifies $releasever as 5Server and 6Server + default['yum']['mysql56-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.6-community/el/5/$basearch/' + when 6 + default['yum']['mysql56-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.6-community/el/6/$basearch/' + when 7 + default['yum']['mysql56-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.6-community/el/7/$basearch/' + end + else # other rhel + default['yum']['mysql56-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.6-community/el/$releasever/$basearch/' + end +when 'fedora' + default['yum']['mysql56-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.6-community/fc/$releasever/$basearch/' +end diff --git a/cookbooks/yum-mysql-community/attributes/mysql57-community.rb b/cookbooks/yum-mysql-community/attributes/mysql57-community.rb new file mode 100644 index 0000000..ad8e55c --- /dev/null +++ b/cookbooks/yum-mysql-community/attributes/mysql57-community.rb @@ -0,0 +1,35 @@ +default['yum']['mysql57-community']['repositoryid'] = 'mysql57-community' +default['yum']['mysql57-community']['gpgkey'] = 'https://raw.githubusercontent.com/chef-cookbooks/yum-mysql-community/master/files/default/mysql_pubkey.asc' +default['yum']['mysql57-community']['description'] = 'MySQL 5.7 Community Server' +default['yum']['mysql57-community']['failovermethod'] = 'priority' +default['yum']['mysql57-community']['gpgcheck'] = true +default['yum']['mysql57-community']['enabled'] = true + +case node['platform_family'] +when 'rhel' + case node['platform'] + when 'amazon' + case node['platform_version'].to_i + when 2013 + default['yum']['mysql57-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.7-community/el/6/$basearch/' + when 2014 + default['yum']['mysql57-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.7-community/el/6/$basearch/' + when 2015 + default['yum']['mysql57-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.7-community/el/6/$basearch/' + end + when 'redhat' + case node['platform_version'].to_i + when 5 + # Real Redhat identifies $releasever as 5Server and 6Server + default['yum']['mysql57-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.7-community/el/5/$basearch/' + when 6 + default['yum']['mysql57-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.7-community/el/6/$basearch/' + when 7 + default['yum']['mysql57-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.7-community/el/7/$basearch/' + end + else # other rhel + default['yum']['mysql57-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.7-community/el/$releasever/$basearch/' + end +when 'fedora' + default['yum']['mysql57-community']['baseurl'] = 'http://repo.mysql.com/yum/mysql-5.7-community/fc/$releasever/$basearch/' +end diff --git a/cookbooks/yum-mysql-community/files/default/mysql_pubkey.asc b/cookbooks/yum-mysql-community/files/default/mysql_pubkey.asc new file mode 100644 index 0000000..8009b88 --- /dev/null +++ b/cookbooks/yum-mysql-community/files/default/mysql_pubkey.asc @@ -0,0 +1,33 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.5 (GNU/Linux) + +mQGiBD4+owwRBAC14GIfUfCyEDSIePvEW3SAFUdJBtoQHH/nJKZyQT7h9bPlUWC3 +RODjQReyCITRrdwyrKUGku2FmeVGwn2u2WmDMNABLnpprWPkBdCk96+OmSLN9brZ +fw2vOUgCmYv2hW0hyDHuvYlQA/BThQoADgj8AW6/0Lo7V1W9/8VuHP0gQwCgvzV3 +BqOxRznNCRCRxAuAuVztHRcEAJooQK1+iSiunZMYD1WufeXfshc57S/+yeJkegNW +hxwR9pRWVArNYJdDRT+rf2RUe3vpquKNQU/hnEIUHJRQqYHo8gTxvxXNQc7fJYLV +K2HtkrPbP72vwsEKMYhhr0eKCbtLGfls9krjJ6sBgACyP/Vb7hiPwxh6rDZ7ITnE +kYpXBACmWpP8NJTkamEnPCia2ZoOHODANwpUkP43I7jsDmgtobZX9qnrAXw+uNDI +QJEXM6FSbi0LLtZciNlYsafwAPEOMDKpMqAK6IyisNtPvaLd8lH0bPAnWqcyefep +rv0sxxqUEMcM3o7wwgfN83POkDasDbs3pjwPhxvhz6//62zQJ7Q7TXlTUUwgUGFj +a2FnZSBzaWduaW5nIGtleSAod3d3Lm15c3FsLmNvbSkgPGJ1aWxkQG15c3FsLmNv +bT6IXQQTEQIAHQULBwoDBAMVAwIDFgIBAheABQJLcC5lBQkQ8/JZAAoJEIxxjTtQ +cuH1oD4AoIcOQ4EoGsZvy06D0Ei5vcsWEy8dAJ4g46i3WEcdSWxMhcBSsPz65sh5 +lohMBBMRAgAMBQI+PqPRBYMJZgC7AAoJEElQ4SqycpHyJOEAn1mxHijft00bKXvu +cSo/pECUmppiAJ41M9MRVj5VcdH/KN/KjRtW6tHFPYhMBBMRAgAMBQI+QoIDBYMJ +YiKJAAoJELb1zU3GuiQ/lpEAoIhpp6BozKI8p6eaabzF5MlJH58pAKCu/ROofK8J +Eg2aLos+5zEYrB/LsrkCDQQ+PqMdEAgA7+GJfxbMdY4wslPnjH9rF4N2qfWsEN/l +xaZoJYc3a6M02WCnHl6ahT2/tBK2w1QI4YFteR47gCvtgb6O1JHffOo2HfLmRDRi +Rjd1DTCHqeyX7CHhcghj/dNRlW2Z0l5QFEcmV9U0Vhp3aFfWC4Ujfs3LU+hkAWzE +7zaD5cH9J7yv/6xuZVw411x0h4UqsTcWMu0iM1BzELqX1DY7LwoPEb/O9Rkbf4fm +Le11EzIaCa4PqARXQZc4dhSinMt6K3X4BrRsKTfozBu74F47D8Ilbf5vSYHbuE5p +/1oIDznkg/p8kW+3FxuWrycciqFTcNz215yyX39LXFnlLzKUb/F5GwADBQf+Lwqq +a8CGrRfsOAJxim63CHfty5mUc5rUSnTslGYEIOCR1BeQauyPZbPDsDD9MZ1ZaSaf +anFvwFG6Llx9xkU7tzq+vKLoWkm4u5xf3vn55VjnSd1aQ9eQnUcXiL4cnBGoTbOW +I39EcyzgslzBdC++MPjcQTcA7p6JUVsP6oAB3FQWg54tuUo0Ec8bsM8b3Ev42Lmu +QT5NdKHGwHsXTPtl0klk4bQk4OajHsiy1BMahpT27jWjJlMiJc+IWJ0mghkKHt92 +6s/ymfdf5HkdQ1cyvsz5tryVI3Fx78XeSYfQvuuwqp2H139pXGEkg0n6KdUOetdZ +Whe70YGNPw1yjWJT1IhMBBgRAgAMBQI+PqMdBQkJZgGAAAoJEIxxjTtQcuH17p4A +n3r1QpVC9yhnW2cSAjq+kr72GX0eAJ4295kl6NxYEuFApmr1+0uUq/SlsQ== +=Mski +-----END PGP PUBLIC KEY BLOCK----- diff --git a/cookbooks/yum-mysql-community/metadata.json b/cookbooks/yum-mysql-community/metadata.json new file mode 100644 index 0000000..3bae524 --- /dev/null +++ b/cookbooks/yum-mysql-community/metadata.json @@ -0,0 +1,30 @@ +{ + "name": "yum-mysql-community", + "version": "0.1.17", + "description": "Installs/Configures yum-mysql-community", + "long_description": "", + "maintainer": "Chef Software, Inc", + "maintainer_email": "Sean OMeara ", + "license": "Apache 2.0", + "platforms": { + }, + "dependencies": { + "yum": ">= 3.0" + }, + "recommendations": { + }, + "suggestions": { + }, + "conflicting": { + }, + "providing": { + }, + "replacing": { + }, + "attributes": { + }, + "groupings": { + }, + "recipes": { + } +} \ No newline at end of file diff --git a/cookbooks/yum-mysql-community/recipes/connectors.rb b/cookbooks/yum-mysql-community/recipes/connectors.rb new file mode 100644 index 0000000..6bc02bf --- /dev/null +++ b/cookbooks/yum-mysql-community/recipes/connectors.rb @@ -0,0 +1,48 @@ +# +# Author:: Sean OMeara () +# Recipe:: yum-mysql-community::connectors +# +# Copyright 2014, Chef Software, Inc. +# +# 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. + +yum_repository 'mysql-connectors-community' do + description node['yum']['mysql-connectors-community']['description'] + baseurl node['yum']['mysql-connectors-community']['baseurl'] + mirrorlist node['yum']['mysql-connectors-community']['mirrorlist'] + gpgcheck node['yum']['mysql-connectors-community']['gpgcheck'] + gpgkey node['yum']['mysql-connectors-community']['gpgkey'] + enabled node['yum']['mysql-connectors-community']['enabled'] + cost node['yum']['mysql-connectors-community']['cost'] + exclude node['yum']['mysql-connectors-community']['exclude'] + enablegroups node['yum']['mysql-connectors-community']['enablegroups'] + failovermethod node['yum']['mysql-connectors-community']['failovermethod'] + http_caching node['yum']['mysql-connectors-community']['http_caching'] + include_config node['yum']['mysql-connectors-community']['include_config'] + includepkgs node['yum']['mysql-connectors-community']['includepkgs'] + keepalive node['yum']['mysql-connectors-community']['keepalive'] + max_retries node['yum']['mysql-connectors-community']['max_retries'] + metadata_expire node['yum']['mysql-connectors-community']['metadata_expire'] + mirror_expire node['yum']['mysql-connectors-community']['mirror_expire'] + priority node['yum']['mysql-connectors-community']['priority'] + proxy node['yum']['mysql-connectors-community']['proxy'] + proxy_username node['yum']['mysql-connectors-community']['proxy_username'] + proxy_password node['yum']['mysql-connectors-community']['proxy_password'] + repositoryid node['yum']['mysql-connectors-community']['repositoryid'] + sslcacert node['yum']['mysql-connectors-community']['sslcacert'] + sslclientcert node['yum']['mysql-connectors-community']['sslclientcert'] + sslclientkey node['yum']['mysql-connectors-community']['sslclientkey'] + sslverify node['yum']['mysql-connectors-community']['sslverify'] + timeout node['yum']['mysql-connectors-community']['timeout'] + action :create +end diff --git a/cookbooks/yum-mysql-community/recipes/mysql55.rb b/cookbooks/yum-mysql-community/recipes/mysql55.rb new file mode 100644 index 0000000..e675ce3 --- /dev/null +++ b/cookbooks/yum-mysql-community/recipes/mysql55.rb @@ -0,0 +1,48 @@ +# +# Author:: Sean OMeara () +# Recipe:: yum-mysql-community::mysql55 +# +# Copyright 2014, Chef Software, Inc. +# +# 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. + +yum_repository 'mysql55-community' do + description node['yum']['mysql55-community']['description'] + baseurl node['yum']['mysql55-community']['baseurl'] + mirrorlist node['yum']['mysql55-community']['mirrorlist'] + gpgcheck node['yum']['mysql55-community']['gpgcheck'] + gpgkey node['yum']['mysql55-community']['gpgkey'] + enabled node['yum']['mysql55-community']['enabled'] + cost node['yum']['mysql55-community']['cost'] + exclude node['yum']['mysql55-community']['exclude'] + enablegroups node['yum']['mysql55-community']['enablegroups'] + failovermethod node['yum']['mysql55-community']['failovermethod'] + http_caching node['yum']['mysql55-community']['http_caching'] + include_config node['yum']['mysql55-community']['include_config'] + includepkgs node['yum']['mysql55-community']['includepkgs'] + keepalive node['yum']['mysql55-community']['keepalive'] + max_retries node['yum']['mysql55-community']['max_retries'] + metadata_expire node['yum']['mysql55-community']['metadata_expire'] + mirror_expire node['yum']['mysql55-community']['mirror_expire'] + priority node['yum']['mysql55-community']['priority'] + proxy node['yum']['mysql55-community']['proxy'] + proxy_username node['yum']['mysql55-community']['proxy_username'] + proxy_password node['yum']['mysql55-community']['proxy_password'] + repositoryid node['yum']['mysql55-community']['repositoryid'] + sslcacert node['yum']['mysql55-community']['sslcacert'] + sslclientcert node['yum']['mysql55-community']['sslclientcert'] + sslclientkey node['yum']['mysql55-community']['sslclientkey'] + sslverify node['yum']['mysql55-community']['sslverify'] + timeout node['yum']['mysql55-community']['timeout'] + action :create +end diff --git a/cookbooks/yum-mysql-community/recipes/mysql56.rb b/cookbooks/yum-mysql-community/recipes/mysql56.rb new file mode 100644 index 0000000..6b6bb33 --- /dev/null +++ b/cookbooks/yum-mysql-community/recipes/mysql56.rb @@ -0,0 +1,48 @@ +# +# Author:: Sean OMeara () +# Recipe:: yum-mysql-community::mysql56-community +# +# Copyright 2014, Chef Software, Inc. +# +# 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. + +yum_repository 'mysql56-community' do + description node['yum']['mysql56-community']['description'] + baseurl node['yum']['mysql56-community']['baseurl'] + mirrorlist node['yum']['mysql56-community']['mirrorlist'] + gpgcheck node['yum']['mysql56-community']['gpgcheck'] + gpgkey node['yum']['mysql56-community']['gpgkey'] + enabled node['yum']['mysql56-community']['enabled'] + cost node['yum']['mysql56-community']['cost'] + exclude node['yum']['mysql56-community']['exclude'] + enablegroups node['yum']['mysql56-community']['enablegroups'] + failovermethod node['yum']['mysql56-community']['failovermethod'] + http_caching node['yum']['mysql56-community']['http_caching'] + include_config node['yum']['mysql56-community']['include_config'] + includepkgs node['yum']['mysql56-community']['includepkgs'] + keepalive node['yum']['mysql56-community']['keepalive'] + max_retries node['yum']['mysql56-community']['max_retries'] + metadata_expire node['yum']['mysql56-community']['metadata_expire'] + mirror_expire node['yum']['mysql56-community']['mirror_expire'] + priority node['yum']['mysql56-community']['priority'] + proxy node['yum']['mysql56-community']['proxy'] + proxy_username node['yum']['mysql56-community']['proxy_username'] + proxy_password node['yum']['mysql56-community']['proxy_password'] + repositoryid node['yum']['mysql56-community']['repositoryid'] + sslcacert node['yum']['mysql56-community']['sslcacert'] + sslclientcert node['yum']['mysql56-community']['sslclientcert'] + sslclientkey node['yum']['mysql56-community']['sslclientkey'] + sslverify node['yum']['mysql56-community']['sslverify'] + timeout node['yum']['mysql56-community']['timeout'] + action :create +end diff --git a/cookbooks/yum-mysql-community/recipes/mysql57.rb b/cookbooks/yum-mysql-community/recipes/mysql57.rb new file mode 100644 index 0000000..dafe194 --- /dev/null +++ b/cookbooks/yum-mysql-community/recipes/mysql57.rb @@ -0,0 +1,48 @@ +# +# Author:: Sean OMeara () +# Recipe:: yum-mysql-community::mysql57-community +# +# Copyright 2014, Chef Software, Inc. +# +# 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. + +yum_repository 'mysql57-community' do + description node['yum']['mysql57-community']['description'] + baseurl node['yum']['mysql57-community']['baseurl'] + mirrorlist node['yum']['mysql57-community']['mirrorlist'] + gpgcheck node['yum']['mysql57-community']['gpgcheck'] + gpgkey node['yum']['mysql57-community']['gpgkey'] + enabled node['yum']['mysql57-community']['enabled'] + cost node['yum']['mysql57-community']['cost'] + exclude node['yum']['mysql57-community']['exclude'] + enablegroups node['yum']['mysql57-community']['enablegroups'] + failovermethod node['yum']['mysql57-community']['failovermethod'] + http_caching node['yum']['mysql57-community']['http_caching'] + include_config node['yum']['mysql57-community']['include_config'] + includepkgs node['yum']['mysql57-community']['includepkgs'] + keepalive node['yum']['mysql57-community']['keepalive'] + max_retries node['yum']['mysql57-community']['max_retries'] + metadata_expire node['yum']['mysql57-community']['metadata_expire'] + mirror_expire node['yum']['mysql57-community']['mirror_expire'] + priority node['yum']['mysql57-community']['priority'] + proxy node['yum']['mysql57-community']['proxy'] + proxy_username node['yum']['mysql57-community']['proxy_username'] + proxy_password node['yum']['mysql57-community']['proxy_password'] + repositoryid node['yum']['mysql57-community']['repositoryid'] + sslcacert node['yum']['mysql57-community']['sslcacert'] + sslclientcert node['yum']['mysql57-community']['sslclientcert'] + sslclientkey node['yum']['mysql57-community']['sslclientkey'] + sslverify node['yum']['mysql57-community']['sslverify'] + timeout node['yum']['mysql57-community']['timeout'] + action :create +end diff --git a/cookbooks/yum/CHANGELOG.md b/cookbooks/yum/CHANGELOG.md new file mode 100644 index 0000000..f23bc47 --- /dev/null +++ b/cookbooks/yum/CHANGELOG.md @@ -0,0 +1,276 @@ +yum Cookbook CHANGELOG +====================== +This file is used to list changes made in each version of the yum cookbook. + +v3.6.1 (2015-06-04) +------------------- +- Executing yum clean before makecache +- Adding repo_gpgcheck + +v3.6.0 (2015-04-23) +------------------- +- Adding "yum clean" before "yum makecache" in yum_repository :create +- Adding why_run support to yum_globalconfig + +v3.5.4 (2015-04-07) +------------------- +- Changing tolerant config line to stringified integer + +v3.5.3 (2015-01-16) +------------------- +- Adding reposdir to globalconfig template + +v3.5.2 (2014-12-24) +------------------- +- Fixing redhat-release detection for Redhat 7 + +v3.5.1 (2014-11-24) +------------------- +- Reverting management of ca-certificates because EL5 was broken + +v3.5.0 (2014-11-24) +------------------- +- Adding management of ca-certificates package to yum_repository provider + +v3.4.1 (2014-10-29) +------------------- +- Run yum-makecache only_if new_resource.enabled +- Allow setting of reposdir in global yum config and man page +- Change default 'obsoletes' behavior to match yum defaults + +v3.4.0 (2014-10-15) +------------------- +- Dynamically generate the new_resource attributes + +v3.3.2 (2014-09-11) +------------------- +- Fix globalconfig resource param for http_caching + +v3.3.1 (2014-09-04) +------------------- +- Fix issue with sslverify if set to false +- Add fancy badges + +v3.3.0 (2014-09-03) +------------------- +- Adding tuning attributes for all supported resource parameters +- Adding options hash parameter +- Adding (real) rhel-6.5 and centos-7.0 to test-kitchen coverage +- Updating regex for mirror_expire and mirrorlist_expire to include /^\d+[mhd]$/ +- Updating README so keepcache reflects reality (defaults to false) +- Changing 'obsoletes' behavior in globalconfig resource to match + default behavior. (now defaults to nil, yum defaults to false) +- Adding makecache action to repository resource +- Adding mode parameter to repository resource. Defaults to '0644'. + +v3.2.4 (2014-08-20) +------------------- +#82 - Adding a makecache parameter + +v3.2.2 (2014-06-11) +------------------- +#77 - Parameter default to be Trueclass instead of "1" +#78 - add releasever parameter + + +v3.2.0 (2014-04-09) +------------------- +- [COOK-4510] - Adding username and password parameters to node attributes +- [COOK-4518] - Fix Scientific Linux distroverpkg + + +v3.1.6 (2014-03-27) +------------------- +- [COOK-4463] - support multiple GPG keys +- [COOK-4364] - yum_repository delete action fails + + +v3.1.4 (2014-03-12) +------------------- +- [COOK-4417] Expand test harness to encompass 32-bit boxes + + +v3.1.2 (2014-02-23) +------------------- +Fixing bugs around :delete action and cache clean +Fixing specs to cover :remove and :delete aliasing properly +Adding Travis-ci build matrix bits + + +v3.1.0 (2014-02-13) +------------------- +- Updating testing harness for integration testing on Travis-ci +- Adding TESTING.md and Guardfile +- PR #67 - Add skip_if_unvailable repository option +- PR #64 - Fix validation of 'metadata_expire' option to match documentation +- [COOK-3591] - removing node.name from repo template rendering +- [COOK-4275] - Enhancements to yum cookbook +- Adding full spec coverage +- Adding support for custom source template to yum_repository + + +v3.0.8 (2014-01-27) +------------------- +Fixing typo in default.rb. yum_globalconfig now passes proxy attribute correctly. + + +v3.0.6 (2014-01-27) +------------------- +Updating default.rb to consume node['yum']['main']['proxy'] + + +v3.0.4 (2013-12-29) +------------------- +### Bug +- **[COOK-4156](https://tickets.chef.io/browse/COOK-4156)** - yum cookbook creates a yum.conf with "cachefir" directive + + +v3.0.2 +------ +Updating globalconfig provider for Chef 10 compatability + + +v3.0.0 +------ +3.0.0 +Major rewrite with breaking changes. +Recipes broken out into individual cookbooks +yum_key resource has been removed +yum_repository resource now takes gpgkey as a URL directly +yum_repository actions have been reduced to :create and :delete +'name' has been changed to repositoryid to avoid ambiguity +chefspec test coverage +gpgcheck is set to 'true' by default and must be explicitly disabled + + +v2.4.4 +------ +Reverting to Ruby 1.8 hash syntax. + + +v2.4.2 +------ +[COOK-3275] LWRP repository.rb :add method fails to create yum repo in +some cases which causes :update to fail Amazon rhel + + +v2.4.0 +------ +### Improvement +- [COOK-3025] - Allow per-repo proxy definitions + + +v2.3.4 +------ +### Improvement +- **[COOK-3689](https://tickets.chef.io/browse/COOK-3689)** - Fix warnings about resource cloning +- **[COOK-3574](https://tickets.chef.io/browse/COOK-3574)** - Add missing "description" field in metadata + + +v2.3.2 +------ +### Bug +- **[COOK-3145](https://tickets.chef.io/browse/COOK-3145)** - Use correct download URL for epel `key_url` + +v2.3.0 +------ +### New Feature +- [COOK-2924]: Yum should allow type setting in repo file + +v2.2.4 +------ +### Bug +- [COOK-2360]: last commit to `yum_repository` changes previous behaviour +- [COOK-3015]: Yum cookbook test minitest to fail + +v2.2.2 +------ +### Improvement +- [COOK-2741]: yum::elrepo +- [COOK-2946]: update tests, test kitchen support in yum cookbook + +### Bug +- [COOK-2639]: Yum cookbook - epel - always assumes url is a mirror list +- [COOK-2663]: Yum should allow metadata_expire setting in repo file +- [COOK-2751]: Update yum.ius_release version to 1.0-11 + +v2.2.0 +------ +- [COOK-2189] - yum::ius failed on install (caused from rpm dependency) +- [COOK-2196] - Make includepkgs and exclude configurable for each repos +- [COOK-2244] - Allow configuring caching using attributes +- [COOK-2399] - yum cookbook LWRPs fail FoodCritic +- [COOK-2519] - Add priority option to Yum repo files +- [COOK-2593] - allow integer or string for yum priority +- [COOK-2643] - don't use conditional attribute for `yum_key` `remote_file` + +v2.1.0 +------ +- [COOK-2045] - add remi repository recipe +- [COOK-2121] - add `:create` action to `yum_repository` + +v2.0.6 +------ +- [COOK-2037] - minor style fixes +- [COOK-2038] - updated README + +v2.0.4 +------ +- [COOK-1908] - unable to install repoforge on CentOS 6 32 bit + +v2.0.2 +------ +- [COOK-1758] - Add default action for repository resource + +v2.0.0 +------ +This version changes the behavior of the EPEL recipe (most commonly used in other Chef cookbooks) on Amazon, and removes an attribute, `node['yum']['epel_release']`. See the README for details. + +- [COOK-1772] - Simplify management of EPEL with LWRP + +v1.0.0 +------ +`mirrorlist` in the `yum_repository` LWRP must be set to the mirror list URI to use rather than setting it to true. See README.md. + +- [COOK-1088] - use dl.fedoraproject.org for EPEL to prevent redirects +- [COOK-1653] - fix mirrorlist +- [COOK-1710] - support http proxy +- [COOK-1722] - update IUS version + +v0.8.2 +------ +- [COOK-1521] - add :update action to `yum_repository` + +v0.8.0 +------ +- [COOK-1204] - Make 'add' default action for yum_repository +- [COOK-1351] - option to not make the yum cache (via attribute) +- [COOK-1353] - x86_64 centos path fixes +- [COOK-1414] - recipe for repoforge + +v0.6.2 +------ +- Updated README to remove git diff artifacts. + +v0.6.0 +------ +- Default action for the yum_repository LWRP is now add. +- [COOK-1227] - clear Chefs internal cache after adding new yum repo +- [COOK-1262] - yum::epel should enable existing repo on Amazon Linux +- [COOK-1272], [COOK-1302] - update RPM file for CentOS / RHEL 6 +- [COOK-1330] - update cookbook documentation on excludes for yum +- [COOK-1346] - retry remote_file for EPEL in case we get an FTP mirror + + +v0.5.2 +------ +- [COOK-825] - epel and ius `remote_file` should notify the `rpm_package` to install + +v0.5.0 +------ +- [COOK-675] - add recipe for handling EPEL repository +- [COOK-722] - add recipe for handling IUS repository + +v.0.1.2 +------ +- Remove yum update in default recipe, that doesn't update caches, it updates packages installed. diff --git a/cookbooks/yum/README.md b/cookbooks/yum/README.md new file mode 100644 index 0000000..6aa9da0 --- /dev/null +++ b/cookbooks/yum/README.md @@ -0,0 +1,280 @@ +yum Cookbook +============ + +[![Join the chat at https://gitter.im/chef-cookbooks/yum](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/chef-cookbooks/yum?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +[![Cookbook Version](https://img.shields.io/cookbook/v/yum.svg)](https://supermarket.chef.io/cookbooks/yum) +[![Travis status](http://img.shields.io/travis/chef-cookbooks/yum.svg)](https://travis-ci.org/chef-cookbooks/yum) + +The Yum cookbook exposes the `yum_globalconfig` and `yum_repository` +resources that allows a user to both control global behavior and make +individual Yum repositories available for use. These resources aim to +allow the user to configure all options listed in the `yum.conf` man +page, found at http://linux.die.net/man/5/yum.conf + +NOTES +----- +WARNING: Yum cookbook version 3.0.0 and above contain non-backwards +compatible breaking changes and will not work with cookbooks written +against the 2.x and 1.x series. Changes have been made to the +yum_repository resource, and the yum_key resource has been eliminated +entirely. Recipes have been eliminated and moved into their own +cookbooks. Please lock yum to the 2.x series in your Chef environments +until all dependent cookbooks have been ported. + +Requirements +------------ +* Chef 11 or higher +* Ruby 1.9 (preferably from the Chef full-stack installer) +* RHEL5, RHEL6, or other platforms within the family + +Resources/Providers +------------------- +### yum_repository +This resource manages a yum repository configuration file at +/etc/yum.repos.d/`repositoryid`.repo. When the file needs to be +repaired, it calls yum-makecache so packages in the repo become +available to the next resource. + +#### Example +``` ruby +# add the Zenoss repository +yum_repository 'zenoss' do + description "Zenoss Stable repo" + baseurl "http://dev.zenoss.com/yum/stable/" + gpgkey 'http://dev.zenoss.com/yum/RPM-GPG-KEY-zenoss' + action :create +end + +# add the EPEL repo +yum_repository 'epel' do + description 'Extra Packages for Enterprise Linux' + mirrorlist 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-6&arch=$basearch' + gpgkey 'http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6' + action :create +end +``` + +``` ruby +# delete CentOS-Media repo +yum_repository 'CentOS-Media' do + action :delete +end +``` + +#### Actions +- `:create` - creates a repository file and builds the repository listing +- `:delete` - deletes the repository file +- `:makecache` - update yum cache + +#### Parameters +* `baseurl` - Must be a URL to the directory where the yum repository's + 'repodata' directory lives. Can be an http://, ftp:// or file:// + URL. You can specify multiple URLs in one baseurl statement. +* `cost` - relative cost of accessing this repository. Useful for + weighing one repo's packages as greater/less than any other. + defaults to 1000 +* `description` - Maps to the 'name' parameter in a repository .conf. + Descriptive name for the repository channel. This directive must be + specified. +* `enabled` - Either `true` or `false`. This tells yum whether or not use this repository. +* `enablegroups` - Either `true` or `false`. Determines whether yum + will allow the use of package groups for this repository. Default is + `true` (package groups are allowed). +* `exclude` - List of packages to exclude from updates or installs. This + should be a space separated list in a single string. Shell globs using wildcards (eg. * + and ?) are allowed. +* `failovermethod` - Either 'roundrobin' or 'priority'. +* `fastestmirror_enabled` - Either `true` or `false` +* `gpgcheck` - Either `true` or `false`. This tells yum whether or not + it should perform a GPG signature check on packages. When this is + set in the [main] section it sets the default for all repositories. + The default is `true`. +* `gpgkey` - A URL pointing to the ASCII-armored GPG key file for the + repository. This option is used if yum needs a public key to verify + a package and the required key hasn't been imported into the RPM + database. If this option is set, yum will automatically import the + key from the specified URL. +* `http_caching` - Either 'all', 'packages', or 'none'. Determines how + upstream HTTP caches are instructed to handle any HTTP downloads + that Yum does. Defaults to 'all' +* `includepkgs` - Inverse of exclude. This is a list of packages you + want to use from a repository. If this option lists only one package + then that is all yum will ever see from the repository. Defaults to + an empty list. +* `keepalive` - Either `true` or `false`. This tells yum whether or not + HTTP/1.1 keepalive should be used with this repository. +* `make_cache` - Optional, Default is `true`, if `false` then `yum -q makecache` will not + be ran +* `max_retries` - Set the number of times any attempt to retrieve a file + should retry before returning an error. Setting this to '0' makes + yum try forever. Default is '10'. +* `metadata_expire` - Time (in seconds) after which the metadata will + expire. So that if the current metadata downloaded is less than this + many seconds old then yum will not update the metadata against the + repository. If you find that yum is not downloading information on + updates as often as you would like lower the value of this option. + You can also change from the default of using seconds to using days, + hours or minutes by appending a d, h or m respectively. The default + is 6 hours, to compliment yum-updatesd running once an hour. It's + also possible to use the word "never", meaning that the metadata + will never expire. Note that when using a metalink file the metalink + must always be newer than the metadata for the repository, due to + the validation, so this timeout also applies to the metalink file. +* `mirrorlist` - Specifies a URL to a file containing a list of + baseurls. This can be used instead of or with the baseurl option. + Substitution variables, described below, can be used with this + option. As a special hack is the mirrorlist URL contains the word + "metalink" then the value of mirrorlist is copied to metalink (if + metalink is not set) +* `mirror_expire` - Time (in seconds) after which the mirrorlist locally + cached will expire. If the current mirrorlist is less than this many + seconds old then yum will not download another copy of the + mirrorlist, it has the same extra format as metadata_expire. If you + find that yum is not downloading the mirrorlists as often as you + would like lower the value of this option. +* `mirrorlist_expire` - alias for mirror_expire +* `mode` - Permissions mode of .repo file on disk. Useful for + scenarios where secrets are in the repo file. If set to '600', + normal users will not be able to use yum search, yum info, etc. + Defaults to '0644' +* `priority` - When the yum-priorities plug-in is enabled, you set + priorities on repository entries, where N is an integer from 1 to 99. The + default priority for repositories is 99. +* `proxy` - URL to the proxy server that yum should use. +* `proxy_username` - username to use for proxy +* `proxy_password` - password for this proxy +* `report_instanceid` - Report instance ID when using Amazon Linux AMIs + and repositories +* `repositoryid` - Must be a unique name for each repository, one word. + Defaults to name attribute. +* `source` - Use a custom template source instead of the default one + in the yum cookbook +* `sslcacert` - Path to the directory containing the databases of the + certificate authorities yum should use to verify SSL certificates. + Defaults to none - uses system default +* `sslclientcert` - Path to the SSL client certificate yum should use to + connect to repos/remote sites Defaults to none. +* `sslclientkey` - Path to the SSL client key yum should use to connect + to repos/remote sites Defaults to none. +* `sslverify` - Either `true` or `false`. Determines if yum will verify SSL certificates/hosts. Defaults to `true` +* `timeout` - Number of seconds to wait for a connection before timing + out. Defaults to 30 seconds. This may be too short of a time for + extremely overloaded sites. + +### yum_globalconfig +This renders a template with global yum configuration parameters. The +default recipe uses it to render `/etc/yum.conf`. It is flexible +enough to be used in other scenarios, such as building RPMs in +isolation by modifying `installroot`. + +#### Example +``` ruby +yum_globalconfig '/my/chroot/etc/yum.conf' do + cachedir '/my/chroot/etc/yum.conf' + keepcache 'yes' + debuglevel '2' + installroot '/my/chroot' + action :create +end +``` + +#### Parameters +`yum_globalconfig` can take most of the same parameters as a +`yum_repository`, plus more, too numerous to describe here. Below are +a few of the more commonly used ones. For a complete list, please +consult the `yum.conf` man page, found here: +http://linux.die.net/man/5/yum.conf + +* `cachedir` - Directory where yum should store its cache and db + files. The default is '/var/cache/yum'. +* `keepcache` - Either `true` or `false`. Determines whether or not + yum keeps the cache of headers and packages after successful + installation. Default is `false` +* `debuglevel` - Debug message output level. Practical range is 0-10. + Default is '2'. +* `exclude` - List of packages to exclude from updates or installs. + This should be a space separated list. Shell globs using wildcards + (eg. * and ?) are allowed. +* `installonlypkgs` = List of package provides that should only ever + be installed, never updated. Kernels in particular fall into this + category. Defaults to kernel, kernel-bigmem, kernel-enterprise, + kernel-smp, kernel-debug, kernel-unsupported, kernel-source, + kernel-devel, kernel-PAE, kernel-PAE-debug. +* `logfile` - Full directory and file name for where yum should write + its log file. +* `exactarch` - Either `true` or `false`. Set to `true` to make 'yum update' only + update the architectures of packages that you have installed. ie: + with this enabled yum will not install an i686 package to update an + x86_64 package. Default is `true` +* `gpgcheck` - Either `true` or `false`. This tells yum whether or not + it should perform a GPG signature check on the packages gotten from + this repository. + +Recipes +------- +* `default` - Configures `yum_globalconfig[/etc/yum.conf]` with values + found in node attributes at `node['yum']['main']` + +Attributes +---------- +The following attributes are set by default + +``` ruby +default['yum']['main']['cachedir'] = '/var/cache/yum/$basearch/$releasever' +default['yum']['main']['keepcache'] = false +default['yum']['main']['debuglevel'] = nil +default['yum']['main']['exclude'] = nil +default['yum']['main']['logfile'] = '/var/log/yum.log' +default['yum']['main']['exactarch'] = nil +default['yum']['main']['obsoletes'] = nil +default['yum']['main']['installonly_limit'] = nil +default['yum']['main']['installonlypkgs'] = nil +default['yum']['main']['installroot'] = nil +``` + +Related Cookbooks +----------------- +Recipes from older versions of this cookbook have been moved +individual cookbooks. Recipes for managing platform yum configurations +and installing specific repositories can be found in one (or more!) of +the following cookbook. + +* yum-centos +* yum-fedora +* yum-amazon +* yum-epel +* yum-elrepo +* yum-repoforge +* yum-ius +* yum-percona +* yum-pgdg + +Usage +----- +Put `depends 'yum'` in your metadata.rb to gain access to the +yum_repository resource. + +License & Authors +----------------- +- Author:: Eric G. Wolfe +- Author:: Matt Ray () +- Author:: Joshua Timberman () +- Author:: Sean OMeara () + +```text +Copyright:: 2011 Eric G. Wolfe +Copyright:: 2013 Chef + +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/cookbooks/yum/attributes/main.rb b/cookbooks/yum/attributes/main.rb new file mode 100644 index 0000000..9c0012c --- /dev/null +++ b/cookbooks/yum/attributes/main.rb @@ -0,0 +1,99 @@ +# http://linux.die.net/man/5/yum.conf +case node['platform_version'].to_i +when 5 + default['yum']['main']['cachedir'] = '/var/cache/yum' +else + default['yum']['main']['cachedir'] = '/var/cache/yum/$basearch/$releasever' +end + +case node['platform'] +when 'amazon' + default['yum']['main']['distroverpkg'] = 'system-release' +when 'scientific' + default['yum']['main']['distroverpkg'] = 'sl-release' +when 'redhat' + default['yum']['main']['distroverpkg'] = nil +else + default['yum']['main']['distroverpkg'] = "#{node['platform']}-release" +end + +default['yum']['main']['alwaysprompt'] = nil # [TrueClass, FalseClass] +default['yum']['main']['assumeyes'] = nil # [TrueClass, FalseClass] +default['yum']['main']['bandwidth'] = nil # /^\d+$/ +default['yum']['main']['bugtracker_url'] = nil # /.*/ +default['yum']['main']['clean_requirements_on_remove'] = nil # [TrueClass, FalseClass] +default['yum']['main']['color'] = nil # %w{ always never } +default['yum']['main']['color_list_available_downgrade'] = nil # /.*/ +default['yum']['main']['color_list_available_install'] = nil # /.*/ +default['yum']['main']['color_list_available_reinstall'] = nil # /.*/ +default['yum']['main']['color_list_available_upgrade'] = nil # /.*/ +default['yum']['main']['color_list_installed_extra'] = nil # /.*/ +default['yum']['main']['color_list_installed_newer'] = nil # /.*/ +default['yum']['main']['color_list_installed_older'] = nil # /.*/ +default['yum']['main']['color_list_installed_reinstall'] = nil # /.*/ +default['yum']['main']['color_search_match'] = nil # /.*/ +default['yum']['main']['color_update_installed'] = nil # /.*/ +default['yum']['main']['color_update_local'] = nil # /.*/ +default['yum']['main']['color_update_remote'] = nil # /.*/ +default['yum']['main']['commands'] = nil # /.*/ +default['yum']['main']['debuglevel'] = nil # /^\d+$/ +default['yum']['main']['diskspacecheck'] = nil # [TrueClass, FalseClass] +default['yum']['main']['enable_group_conditionals'] = nil # [TrueClass, FalseClass] +default['yum']['main']['errorlevel'] = nil # /^\d+$/ +default['yum']['main']['exactarch'] = nil # [TrueClass, FalseClass] +default['yum']['main']['exclude'] = nil # /.*/ +default['yum']['main']['gpgcheck'] = true # [TrueClass, FalseClass] +default['yum']['main']['group_package_types'] = nil # /.*/ +default['yum']['main']['groupremove_leaf_only'] = nil # [TrueClass, FalseClass] +default['yum']['main']['history_list_view'] = nil # /.*/ +default['yum']['main']['history_record'] = nil # [TrueClass, FalseClass] +default['yum']['main']['history_record_packages'] = nil # /.*/ +default['yum']['main']['http_caching'] = nil # %w{ packages all none } +default['yum']['main']['installonly_limit'] = nil # /\d+/, /keep/ +default['yum']['main']['installonlypkgs'] = nil # /.*/ +default['yum']['main']['installroot'] = nil # /.*/ +default['yum']['main']['keepalive'] = nil # [TrueClass, FalseClass] +default['yum']['main']['keepcache'] = false # [TrueClass, FalseClass] +default['yum']['main']['kernelpkgnames'] = nil # /.*/ +default['yum']['main']['localpkg_gpgcheck'] = nil # [TrueClass,# FalseClass] +default['yum']['main']['logfile'] = '/var/log/yum.log' # /.*/ +default['yum']['main']['max_retries'] = nil # /^\d+$/ +default['yum']['main']['mdpolicy'] = nil # %w{ packages all none } +default['yum']['main']['metadata_expire'] = nil # /^\d+$/ +default['yum']['main']['mirrorlist_expire'] = nil # /^\d+$/ +default['yum']['main']['multilib_policy'] = nil # %w{ all best } +default['yum']['main']['obsoletes'] = nil # [TrueClass, FalseClass] +default['yum']['main']['overwrite_groups'] = nil # [TrueClass, FalseClass] +default['yum']['main']['password'] = nil # /.*/ +default['yum']['main']['path'] = '/etc/yum.conf' # /.*/ +default['yum']['main']['persistdir'] = nil # /.*/ +default['yum']['main']['pluginconfpath'] = nil # /.*/ +default['yum']['main']['pluginpath'] = nil # /.*/ +default['yum']['main']['plugins'] = nil # [TrueClass, FalseClass] +default['yum']['main']['protected_multilib'] = nil # /.*/ +default['yum']['main']['protected_packages'] = nil # /.*/ +default['yum']['main']['proxy'] = nil # /.*/ +default['yum']['main']['proxy_password'] = nil # /.*/ +default['yum']['main']['proxy_username'] = nil # /.*/ +default['yum']['main']['password'] = nil # /.*/ +default['yum']['main']['recent'] = nil # /^\d+$/ +default['yum']['main']['releasever'] = nil # /.*/ +default['yum']['main']['repo_gpgcheck'] = nil # [TrueClass, FalseClass] +default['yum']['main']['reposdir'] = nil # /.*/ +default['yum']['main']['reset_nice'] = nil # [TrueClass, FalseClass] +default['yum']['main']['rpmverbosity'] = nil # %w{ info critical# emergency error warn debug } +default['yum']['main']['showdupesfromrepos'] = nil # [TrueClass, FalseClass] +default['yum']['main']['skip_broken'] = nil # [TrueClass, FalseClass] +default['yum']['main']['ssl_check_cert_permissions'] = nil # [TrueClass, FalseClass] +default['yum']['main']['sslcacert'] = nil # /.*/ +default['yum']['main']['sslclientcert'] = nil # /.*/ +default['yum']['main']['sslclientkey'] = nil # /.*/ +default['yum']['main']['sslverify'] = nil # [TrueClass, FalseClass] +default['yum']['main']['syslog_device'] = nil # /.*/ +default['yum']['main']['syslog_facility'] = nil # /.*/ +default['yum']['main']['syslog_ident'] = nil # /.*/ +default['yum']['main']['throttle'] = nil # [/\d+k/, /\d+M/, /\d+G/] +default['yum']['main']['timeout'] = nil # /\d+/ +default['yum']['main']['tolerant'] = false +default['yum']['main']['tsflags'] = nil # /.*/ +default['yum']['main']['username'] = nil # /.*/ diff --git a/cookbooks/yum/libraries/matchers.rb b/cookbooks/yum/libraries/matchers.rb new file mode 100644 index 0000000..433347b --- /dev/null +++ b/cookbooks/yum/libraries/matchers.rb @@ -0,0 +1,27 @@ +# Matchers for chefspec 3 + +if defined?(ChefSpec) + def create_yum_repository(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:yum_repository, :create, resource_name) + end + + def add_yum_repository(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:yum_repository, :add, resource_name) + end + + def delete_yum_repository(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:yum_repository, :delete, resource_name) + end + + def remove_yum_repository(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:yum_repository, :remove, resource_name) + end + + def create_yum_globalconfig(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:yum_globalconfig, :create, resource_name) + end + + def delete_yum_globalconfig(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:yum_globalconfig, :delete, resource_name) + end +end diff --git a/cookbooks/yum/metadata.json b/cookbooks/yum/metadata.json new file mode 100644 index 0000000..f5b2c8e --- /dev/null +++ b/cookbooks/yum/metadata.json @@ -0,0 +1 @@ +{"name":"yum","version":"3.6.1","description":"Configures various yum components on Red Hat-like systems","long_description":"","maintainer":"Chef","maintainer_email":"cookbooks@chef.io","license":"Apache 2.0","platforms":{"redhat":">= 0.0.0","centos":">= 0.0.0","scientific":">= 0.0.0","amazon":">= 0.0.0","fedora":">= 0.0.0"},"dependencies":{},"recommendations":{},"suggestions":{},"conflicting":{},"providing":{},"replacing":{},"attributes":{},"groupings":{},"recipes":{}} \ No newline at end of file diff --git a/cookbooks/yum/providers/globalconfig.rb b/cookbooks/yum/providers/globalconfig.rb new file mode 100644 index 0000000..dfd9db5 --- /dev/null +++ b/cookbooks/yum/providers/globalconfig.rb @@ -0,0 +1,41 @@ +# +# Cookbook Name:: yum +# Provider:: repository +# +# Author:: Sean OMeara +# Copyright 2013, Chef +# +# 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. +# + +# Allow for Chef 10 support +use_inline_resources if defined?(use_inline_resources) + +def whyrun_supported? + true +end + +action :create do + template new_resource.path do + source 'main.erb' + cookbook 'yum' + mode '0644' + variables(:config => new_resource) + end +end + +action :delete do + file new_resource.path do + action :delete + end +end diff --git a/cookbooks/yum/providers/repository.rb b/cookbooks/yum/providers/repository.rb new file mode 100644 index 0000000..9999060 --- /dev/null +++ b/cookbooks/yum/providers/repository.rb @@ -0,0 +1,106 @@ +# +# Cookbook Name:: yum +# Provider:: repository +# +# Author:: Sean OMeara +# Copyright 2013, Chef +# +# 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. +# + +# In Chef 11 and above, calling the use_inline_resources method will +# make Chef create a new "run_context". When an action is called, any +# nested resources are compiled and converged in isolation from the +# recipe that calls it. + +# Allow for Chef 10 support +use_inline_resources if defined?(use_inline_resources) + +def whyrun_supported? + true +end + +action :create do + # Hack around the lack of "use_inline_resources" before Chef 11 by + # uniquely naming the execute[yum-makecache] resources. Set the + # notifies timing to :immediately for the same reasons. Remove both + # of these when dropping Chef 10 support. + + template "/etc/yum.repos.d/#{new_resource.repositoryid}.repo" do + if new_resource.source.nil? + source 'repo.erb' + cookbook 'yum' + else + source new_resource.source + end + mode new_resource.mode + variables(:config => new_resource) + if new_resource.make_cache + notifies :run, "execute[yum clean #{new_resource.repositoryid}]", :immediately + notifies :run, "execute[yum-makecache-#{new_resource.repositoryid}]", :immediately + notifies :create, "ruby_block[yum-cache-reload-#{new_resource.repositoryid}]", :immediately + end + end + + execute "yum clean #{new_resource.repositoryid}" do + command "yum clean all --disablerepo=* --enablerepo=#{new_resource.repositoryid}" + action :nothing + end + + # get the metadata for this repo only + execute "yum-makecache-#{new_resource.repositoryid}" do + command "yum -q makecache --disablerepo=* --enablerepo=#{new_resource.repositoryid}" + action :nothing + only_if { new_resource.enabled } + end + + # reload internal Chef yum cache + ruby_block "yum-cache-reload-#{new_resource.repositoryid}" do + block { Chef::Provider::Package::Yum::YumCache.instance.reload } + action :nothing + end +end + +action :delete do + file "/etc/yum.repos.d/#{new_resource.repositoryid}.repo" do + action :delete + notifies :run, "execute[yum clean #{new_resource.repositoryid}]", :immediately + notifies :create, "ruby_block[yum-cache-reload-#{new_resource.repositoryid}]", :immediately + end + + execute "yum clean #{new_resource.repositoryid}" do + command "yum clean all --disablerepo=* --enablerepo=#{new_resource.repositoryid}" + only_if "yum repolist | grep -P '^#{new_resource.repositoryid}([ \t]|$)'" + action :nothing + end + + ruby_block "yum-cache-reload-#{new_resource.repositoryid}" do + block { Chef::Provider::Package::Yum::YumCache.instance.reload } + action :nothing + end +end + +action :makecache do + execute "yum-makecache-#{new_resource.repositoryid}" do + command "yum -q makecache --disablerepo=* --enablerepo=#{new_resource.repositoryid}" + action :run + end + + ruby_block "yum-cache-reload-#{new_resource.repositoryid}" do + block { Chef::Provider::Package::Yum::YumCache.instance.reload } + action :run + end +end + +alias_method :action_add, :action_create +alias_method :action_remove, :action_delete diff --git a/cookbooks/yum/recipes/default.rb b/cookbooks/yum/recipes/default.rb new file mode 100644 index 0000000..2b41e4a --- /dev/null +++ b/cookbooks/yum/recipes/default.rb @@ -0,0 +1,26 @@ +# +# Author:: Sean OMeara () +# Author:: Joshua Timberman () +# Recipe:: yum::default +# +# Copyright 2013-2014, Chef Software, Inc () +# +# 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. + +yum_globalconfig '/etc/yum.conf' do + node['yum']['main'].each do |config, value| + send(config.to_sym, value) + end + + action :create +end diff --git a/cookbooks/yum/resources/globalconfig.rb b/cookbooks/yum/resources/globalconfig.rb new file mode 100644 index 0000000..3802428 --- /dev/null +++ b/cookbooks/yum/resources/globalconfig.rb @@ -0,0 +1,108 @@ +# +# Cookbook Name:: yum +# Resource:: repository +# +# Author:: Sean OMeara +# Copyright 2013, Chef +# +# 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. +# + +actions :create, :delete + +default_action :create + +# http://linux.die.net/man/5/yum.conf +attribute :alwaysprompt, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :assumeyes, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :bandwidth, :kind_of => String, :regex => /^\d+/, :default => nil +attribute :bugtracker_url, :kind_of => String, :regex => /.*/, :default => nil +attribute :clean_requirements_on_remove, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :cachedir, :kind_of => String, :regex => /.*/, :default => '/var/cache/yum/$basearch/$releasever' +attribute :color, :kind_of => String, :equal_to => %w(always never), :default => nil +attribute :color_list_available_downgrade, :kind_of => String, :regex => /.*/, :default => nil +attribute :color_list_available_install, :kind_of => String, :regex => /.*/, :default => nil +attribute :color_list_available_reinstall, :kind_of => String, :regex => /.*/, :default => nil +attribute :color_list_available_upgrade, :kind_of => String, :regex => /.*/, :default => nil +attribute :color_list_installed_extra, :kind_of => String, :regex => /.*/, :default => nil +attribute :color_list_installed_newer, :kind_of => String, :regex => /.*/, :default => nil +attribute :color_list_installed_older, :kind_of => String, :regex => /.*/, :default => nil +attribute :color_list_installed_reinstall, :kind_of => String, :regex => /.*/, :default => nil +attribute :color_search_match, :kind_of => String, :regex => /.*/, :default => nil +attribute :color_update_installed, :kind_of => String, :regex => /.*/, :default => nil +attribute :color_update_local, :kind_of => String, :regex => /.*/, :default => nil +attribute :color_update_remote, :kind_of => String, :regex => /.*/, :default => nil +attribute :commands, :kind_of => String, :regex => /.*/, :default => nil +attribute :debuglevel, :kind_of => String, :regex => /^\d+$/, :default => '2' +attribute :diskspacecheck, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :distroverpkg, :kind_of => String, :regex => /.*/, :default => nil +attribute :enable_group_conditionals, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :errorlevel, :kind_of => String, :regex => /^\d+$/, :default => nil +attribute :exactarch, :kind_of => [TrueClass, FalseClass], :default => true +attribute :exclude, :kind_of => String, :regex => /.*/, :default => nil +attribute :gpgcheck, :kind_of => [TrueClass, FalseClass], :default => true +attribute :group_package_types, :kind_of => String, :regex => /.*/, :default => nil +attribute :groupremove_leaf_only, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :history_list_view, :kind_of => String, :equal_to => %w(users commands single-user-commands), :default => nil +attribute :history_record, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :history_record_packages, :kind_of => String, :regex => /.*/, :default => nil +attribute :http_caching, :kind_of => String, :equal_to => %w(packages all none), :default => nil +attribute :installonly_limit, :kind_of => String, :regex => [/^\d+/, /keep/], :default => '3' +attribute :installonlypkgs, :kind_of => String, :regex => /.*/, :default => nil +attribute :installroot, :kind_of => String, :regex => /.*/, :default => nil +attribute :keepalive, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :keepcache, :kind_of => [TrueClass, FalseClass], :default => false +attribute :kernelpkgnames, :kind_of => String, :regex => /.*/, :default => nil +attribute :localpkg_gpgcheck, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :logfile, :kind_of => String, :regex => /.*/, :default => '/var/log/yum.log' +attribute :max_retries, :kind_of => String, :regex => /^\d+$/, :default => nil +attribute :mdpolicy, :kind_of => String, :equal_to => %w(instant group:primary group:small group:main group:all), :default => nil +attribute :metadata_expire, :kind_of => String, :regex => [/^\d+$/, /^\d+[mhd]$/, /never/], :default => nil +attribute :mirrorlist_expire, :kind_of => String, :regex => /^\d+$/, :default => nil +attribute :multilib_policy, :kind_of => String, :equal_to => %w(all best), :default => nil +attribute :obsoletes, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :overwrite_groups, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :password, :kind_of => String, :regex => /.*/, :default => nil +attribute :path, :kind_of => String, :regex => /.*/, :default => nil, :name_attribute => true +attribute :persistdir, :kind_of => String, :regex => /.*/, :default => nil +attribute :pluginconfpath, :kind_of => String, :regex => /.*/, :default => nil +attribute :pluginpath, :kind_of => String, :regex => /.*/, :default => nil +attribute :plugins, :kind_of => [TrueClass, FalseClass], :default => true +attribute :protected_multilib, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :protected_packages, :kind_of => String, :regex => /.*/, :default => nil +attribute :proxy, :kind_of => String, :regex => /.*/, :default => nil +attribute :proxy_password, :kind_of => String, :regex => /.*/, :default => nil +attribute :proxy_username, :kind_of => String, :regex => /.*/, :default => nil +attribute :recent, :kind_of => String, :regex => /^\d+$/, :default => nil +attribute :releasever, :kind_of => String, :regex => /.*/, :default => nil +attribute :repo_gpgcheck, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :reposdir, :kind_of => String, :regex => /.*/, :default => nil +attribute :reset_nice, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :rpmverbosity, :kind_of => String, :equal_to => %w(info critical emergency error warn debug), :default => nil +attribute :showdupesfromrepos, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :skip_broken, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :ssl_check_cert_permissions, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :sslcacert, :kind_of => String, :regex => /.*/, :default => nil +attribute :sslclientcert, :kind_of => String, :regex => /.*/, :default => nil +attribute :sslclientkey, :kind_of => String, :regex => /.*/, :default => nil +attribute :sslverify, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :syslog_device, :kind_of => String, :regex => /.*/, :default => nil +attribute :syslog_facility, :kind_of => String, :regex => /.*/, :default => nil +attribute :syslog_ident, :kind_of => String, :regex => /.*/, :default => nil +attribute :throttle, :kind_of => String, :regex => [/\d+k/, /\d+M/, /\d+G/], :default => nil +attribute :timeout, :kind_of => String, :regex => /^\d+$/, :default => nil +attribute :tolerant, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :tsflags, :kind_of => String, :regex => /.*/, :default => nil +attribute :username, :kind_of => String, :regex => /.*/, :default => nil + +attribute :options, :kind_of => Hash diff --git a/cookbooks/yum/resources/repository.rb b/cookbooks/yum/resources/repository.rb new file mode 100644 index 0000000..9e6043d --- /dev/null +++ b/cookbooks/yum/resources/repository.rb @@ -0,0 +1,68 @@ +# +# Cookbook Name:: yum +# Resource:: repository +# +# Author:: Sean OMeara +# Copyright 2013, Chef +# +# 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. +# + +actions :create, :delete, :add, :remove, :makecache + +default_action :create + +# http://linux.die.net/man/5/yum.conf +attribute :baseurl, :kind_of => String, :regex => /.*/, :default => nil +attribute :cost, :kind_of => String, :regex => /^\d+$/, :default => nil +attribute :description, :kind_of => String, :regex => /.*/, :default => 'Ye Ole Rpm Repo' +attribute :enabled, :kind_of => [TrueClass, FalseClass], :default => true +attribute :enablegroups, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :exclude, :kind_of => String, :regex => /.*/, :default => nil +attribute :failovermethod, :kind_of => String, :equal_to => %w(priority roundrobin), :default => nil +attribute :fastestmirror_enabled, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :gpgcheck, :kind_of => [TrueClass, FalseClass], :default => true +attribute :gpgkey, :kind_of => [String, Array], :regex => /.*/, :default => nil +attribute :http_caching, :kind_of => String, :equal_to => %w(packages all none), :default => nil +attribute :include_config, :kind_of => String, :regex => /.*/, :default => nil +attribute :includepkgs, :kind_of => String, :regex => /.*/, :default => nil +attribute :keepalive, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :make_cache, :kind_of => [TrueClass, FalseClass], :default => true +attribute :max_retries, :kind_of => String, :regex => /.*/, :default => nil +attribute :metadata_expire, :kind_of => String, :regex => [/^\d+$/, /^\d+[mhd]$/, /never/], :default => nil +attribute :mirrorexpire, :kind_of => String, :regex => /.*/, :default => nil +attribute :mirrorlist, :kind_of => String, :regex => /.*/, :default => nil +attribute :mirror_expire, :kind_of => String, :regex => [/^\d+$/, /^\d+[mhd]$/], :default => nil +attribute :mirrorlist_expire, :kind_of => String, :regex => [/^\d+$/, /^\d+[mhd]$/], :default => nil +attribute :mode, :default => '0644' +attribute :priority, :kind_of => String, :regex => /^(\d?[0-9]|[0-9][0-9])$/, :default => nil +attribute :proxy, :kind_of => String, :regex => /.*/, :default => nil +attribute :proxy_username, :kind_of => String, :regex => /.*/, :default => nil +attribute :proxy_password, :kind_of => String, :regex => /.*/, :default => nil +attribute :username, :kind_of => String, :regex => /.*/, :default => nil +attribute :password, :kind_of => String, :regex => /.*/, :default => nil +attribute :repo_gpgcheck, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :report_instanceid, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :repositoryid, :kind_of => String, :regex => /.*/, :name_attribute => true +attribute :skip_if_unavailable, :kind_of => [TrueClass, FalseClass], :default => nil +attribute :source, :kind_of => String, :regex => /.*/, :default => nil +attribute :sslcacert, :kind_of => String, :regex => /.*/, :default => nil +attribute :sslclientcert, :kind_of => String, :regex => /.*/, :default => nil +attribute :sslclientkey, :kind_of => String, :regex => /.*/, :default => nil +attribute :sslverify, :kind_of => [TrueClass, FalseClass], :default => true +attribute :timeout, :kind_of => String, :regex => /^\d+$/, :default => nil + +attribute :options, :kind_of => Hash + +alias_method :url, :baseurl +alias_method :keyurl, :gpgkey diff --git a/cookbooks/yum/templates/default/main.erb b/cookbooks/yum/templates/default/main.erb new file mode 100644 index 0000000..01bf7ce --- /dev/null +++ b/cookbooks/yum/templates/default/main.erb @@ -0,0 +1,269 @@ +# This file was generated by Chef +# Do NOT modify this file by hand. + +[main] +<% if @config.alwaysprompt %> +alwaysprompt=<%= @config.alwaysprompt %> +<% end %> +<% if @config.assumeyes %> +assumeyes=<%= @config.assumeyes %> +<% end %> +<% if @config.bandwidth %> +bandwidth=<%= @config.bandwidth %> +<% end %> +<% if @config.bugtracker_url %> +bugtracker_url=<%= @config.bugtracker_url %> +<% end %> +<% if @config.cachedir %> +cachedir=<%= @config.cachedir %> +<% end %> +<% if @config.clean_requirements_on_remove %> +clean_requirements_on_remove=<%= @config.clean_requirements_on_remove %> +<% end %> +<% if @config.color %> +color=<%= @config.color %> +<% end %> +<% if @config.color_list_available_downgrade %> +color_list_available_downgrade=<%= @config.color_list_available_downgrade %> +<% end %> +<% if @config.color_list_available_install %> +color_list_available_install=<%= @config.color_list_available_install %> +<% end %> +<% if @config.color_list_available_reinstall %> +color_list_available_reinstall=<%= @config.color_list_available_reinstall %> +<% end %> +<% if @config.color_list_available_upgrade %> +color_list_available_upgrade=<%= @config.color_list_available_upgrade %> +<% end %> +<% if @config.color_list_installed_extra %> +color_list_installed_extra=<%= @config.color_list_installed_extra %> +<% end %> +<% if @config.color_list_installed_newer %> +color_list_installed_newer=<%= @config.color_list_installed_newer %> +<% end %> +<% if @config.color_list_installed_older %> +color_list_installed_older=<%= @config.color_list_installed_older %> +<% end %> +<% if @config.color_list_installed_reinstall %> +color_list_installed_reinstall=<%= @config.color_list_installed_reinstall %> +<% end %> +<% if @config.color_search_match %> +color_search_match=<%= @config.color_search_match %> +<% end %> +<% if @config.color_update_installed %> +color_update_installed=<%= @config.color_update_installed %> +<% end %> +<% if @config.color_update_local %> +color_update_local=<%= @config.color_update_local %> +<% end %> +<% if @config.color_update_remote %> +color_update_remote=<%= @config.color_update_remote %> +<% end %> +<% if @config.commands %> +commands=<%= @config.commands %> +<% end %> +<% if @config.debuglevel %> +debuglevel=<%= @config.debuglevel %> +<% end %> +<% if @config.diskspacecheck %> +diskspacecheck=<%= @config.diskspacecheck %> +<% end %> +<% if @config.distroverpkg %> +distroverpkg=<%= @config.distroverpkg %> +<% end %> +<% if @config.enable_group_conditionals %> +enable_group_conditionals=1 +<% end %> +<% if @config.errorlevel %> +errorlevel=<%= @config.errorlevel %> +<% end %> +<% if @config.exactarch %> +exactarch=1 +<% else %> +exactarch=0 +<% end %> +<% if @config.exclude %> +exclude=<%= @config.exclude %> +<% end %> +<% if @config.gpgcheck %> +gpgcheck=1 +<% else %> +gpgcheck=0 +<% end %> +<% if @config.group_package_types %> +group_package_types=<%= @config.group_package_types %> +<% end %> +<% if @config.groupremove_leaf_only %> +groupremove_leaf_only=<%= @config.groupremove_leaf_only %> +<% end %> +<% if @config.history_list_view %> +history_list_view=<%= @config.history_list_view %> +<% end %> +<% if @config.history_record %> +history_record=<%= @config.history_record %> +<% end %> +<% if @config.history_record_packages %> +history_record_packages=<%= @config.history_record_packages %> +<% end %> +<% if @config.http_caching %> +http_caching=<%= @config.http_caching %> +<% end %> +<% if @config.installonly_limit %> +installonly_limit=<%= @config.installonly_limit %> +<% end %> +<% if @config.installonlypkgs %> +installonlypkgs=<%= @config.installonlypkgs %> +<% end %> +<% if @config.installroot %> +installroot=<%= @config.installroot %> +<% end %> +<% if @config.keepalive %> +keepalive=<%= @config.keepalive %> +<% end %> +<% if @config.keepcache %> +keepcache=1 +<% else %> +keepcache=0 +<% end %> +<% if @config.kernelpkgnames %> +kernelpkgnames=<%= @config.kernelpkgnames %> +<% end %> +<% if @config.localpkg_gpgcheck %> +localpkg_gpgcheck=<%= @config.localpkg_gpgcheck %> +<% end %> +<% if @config.logfile %> +logfile=<%= @config.logfile %> +<% end %> +<% if @config.max_retries %> +max_retries=<%= @config.max_retries %> +<% end %> +<% if @config.mdpolicy %> +mdpolicy=<%= @config.mdpolicy %> +<% end %> +<% if @config.metadata_expire %> +metadata_expire=<%= @config.metadata_expire %> +<% end %> +<% if @config.mirrorlist_expire %> +mirrorlist_expire=<%= @config.mirrorlist_expire %> +<% end %> +<% if @config.multilib_policy %> +multilib_policy=<%= @config.multilib_policy %> +<% end %> +<% if @config.obsoletes == false %> +obsoletes=0 +<% else %> +obsoletes=1 +<% end %> +<% if @config.overwrite_groups %> +overwrite_groups=<%= @config.overwrite_groups %> +<% end %> +<% if @config.password %> +password=<%= @config.password %> +<% end %> +<% if @config.persistdir %> +persistdir=<%= @config.persistdir %> +<% end %> +<% if @config.pluginconfpath %> +pluginconfpath=<%= @config.pluginconfpath %> +<% end %> +<% if @config.pluginpath %> +pluginpath=<%= @config.pluginpath %> +<% end %> +<% if @config.plugins %> +plugins=1 +<% else %> +plugins=0 +<% end %> +<% if @config.protected_multilib %> +protected_multilib=<%= @config.protected_multilib %> +<% end %> +<% if @config.protected_packages %> +protected_packages=<%= @config.protected_packages %> +<% end %> +<% if @config.proxy %> +proxy=<%= @config.proxy %> +<% end %> +<% if @config.proxy_password %> +proxy_password=<%= @config.proxy_password %> +<% end %> +<% if @config.proxy_username %> +proxy_username=<%= @config.proxy_username %> +<% end %> +<% if @config.recent %> +recent=<%= @config.recent %> +<% end %> +<% if @config.releasever %> +releasever=<%= @config.releasever %> +<% end %> +<% if @config.repo_gpgcheck %> +repo_gpgcheck=<%= @config.repo_gpgcheck %> +<% end %> +<% if @config.reposdir %> +reposdir=<%= @config.reposdir %> +<% end %> +<% if @config.reset_nice %> +reset_nice=<%= @config.reset_nice %> +<% end %> +<% if @config.rpmverbosity %> +rpmverbosity=<%= @config.rpmverbosity %> +<% end %> +<% if @config.showdupesfromrepos %> +showdupesfromrepos=<%= @config.showdupesfromrepos %> +<% end %> +<% if @config.skip_broken %> +skip_broken=<%= @config.skip_broken %> +<% end %> +<% if @config.ssl_check_cert_permissions %> +ssl_check_cert_permissions=<%= @config.ssl_check_cert_permissions %> +<% end %> +<% if @config.sslcacert %> +sslcacert=<%= @config.sslcacert %> +<% end %> +<% if @config.sslclientcert %> +sslclientcert=<%= @config.sslclientcert %> +<% end %> +<% if @config.sslclientkey %> +sslclientkey=<%= @config.sslclientkey %> +<% end %> +<% unless @config.sslverify.nil? %> +sslverify=<%= @config.sslverify %> +<% end %> +<% if @config.syslog_device %> +syslog_device=<%= @config.syslog_device %> +<% end %> +<% if @config.syslog_facility %> +syslog_facility=<%= @config.syslog_facility %> +<% end %> +<% if @config.syslog_ident %> +syslog_ident=<%= @config.syslog_ident %> +<% end %> +<% if @config.throttle %> +throttle=<%= @config.throttle %> +<% end %> +<% if @config.timeout %> +timeout=<%= @config.timeout %> +<% end %> +<% if @config.tolerant %> +tolerant=<%= ( @config.tolerant ) ? '1' : '0' %> +<% end %> +<% if @config.tsflags %> +tsflags=<%= @config.tsflags %> +<% end %> +<% if @config.username %> +username=<%= @config.username %> +<% end %> +<% if @config.options -%> +<% @config.options.each do |key, value| -%> +<%= key %>=<%= + case value + when Array + value.join("\n ") + when TrueClass + '1' + when FalseClass + '0' + else + value + end %> +<% end -%> +<% end -%> diff --git a/cookbooks/yum/templates/default/repo.erb b/cookbooks/yum/templates/default/repo.erb new file mode 100644 index 0000000..d462f00 --- /dev/null +++ b/cookbooks/yum/templates/default/repo.erb @@ -0,0 +1,125 @@ +# This file was generated by Chef +# Do NOT modify this file by hand. + +[<%= @config.repositoryid %>] +name=<%= @config.description %> +<% if @config.baseurl %> +baseurl=<%= @config.baseurl %> +<% end %> +<% if @config.cost %> +cost=<%= @config.cost %> +<% end %> +<% if @config.enabled %> +enabled=1 +<% else %> +enabled=0 +<% end %> +<% if @config.enablegroups %> +enablegroups=1 +<% end %> +<% if @config.exclude %> +exclude=<%= @config.exclude %> +<% end %> +<% if @config.failovermethod %> +failovermethod=<%= @config.failovermethod %> +<% end %> +<% if @config.fastestmirror_enabled %> +fastestmirror_enabled=<%= @config.fastestmirror_enabled %> +<% end %> +<% if @config.gpgcheck %> +gpgcheck=1 +<% else %> +gpgcheck=0 +<% end %> +<% if @config.gpgkey %> +gpgkey=<%= case @config.gpgkey + when Array + @config.gpgkey.join("\n ") + else + @config.gpgkey + end %> +<% end -%> +<% if @config.http_caching %> +http_caching=<%= @config.http_caching %> +<% end %> +<% if @config.include_config %> +include=<%= @config.include_config %> +<% end %> +<% if @config.includepkgs %> +includepkgs=<%= @config.includepkgs %> +<% end %> +<% if @config.keepalive %> +keepalive=1 +<% end %> +<% if @config.metadata_expire %> +metadata_expire=<%= @config.metadata_expire %> +<% end %> +<% if @config.mirrorlist %> +mirrorlist=<%= @config.mirrorlist %> +<% end %> +<% if @config.mirror_expire %> +mirror_expire=<%= @config.mirror_expire %> +<% end %> +<% if @config.mirrorlist_expire %> +mirrorlist_expire=<%= @config.mirrorlist_expire %> +<% end %> +<% if @config.priority %> +priority=<%= @config.priority %> +<% end %> +<% if @config.proxy %> +proxy=<%= @config.proxy %> +<% end %> +<% if @config.proxy_username %> +proxy_username=<%= @config.proxy_username %> +<% end %> +<% if @config.proxy_password %> +proxy_password=<%= @config.proxy_password %> +<% end %> +<% if @config.username %> +username=<%= @config.username %> +<% end %> +<% if @config.password %> +password=<%= @config.password %> +<% end %> +<% if @config.repo_gpgcheck %> +repo_gpgcheck=1 +<% end %> +<% if @config.max_retries %> +retries=<%= @config.max_retries %> +<% end %> +<% if @config.report_instanceid %> +report_instanceid=<%= @config.report_instanceid %> +<% end %> +<% if @config.skip_if_unavailable %> +skip_if_unavailable=1 +<% end %> +<% if @config.sslcacert %> +sslcacert=<%= @config.sslcacert %> +<% end %> +<% if @config.sslclientcert %> +sslclientcert=<%= @config.sslclientcert %> +<% end %> +<% if @config.sslclientkey %> +sslclientkey=<%= @config.sslclientkey %> +<% end %> +<% unless @config.sslverify.nil? %> +sslverify=<%= @config.sslverify %> +<% end %> +<% if @config.timeout %> +timeout=<%= @config.timeout %> +<% end %> +<% if @config.options -%> +<% @config.options.each do |key, value| -%> +<%= key %>=<%= + case value + when Array + value.join("\n ") + when TrueClass + '1' + when FalseClass + '0' + else + value + end %> +<% end -%> +<% end -%> diff --git a/data_bags/.gitkeep b/data_bags/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/data_bags/certificates/wildcard_kosmos_org.json b/data_bags/certificates/wildcard_kosmos_org.json new file mode 100644 index 0000000..c75e661 --- /dev/null +++ b/data_bags/certificates/wildcard_kosmos_org.json @@ -0,0 +1,15 @@ +{ + "id": "wildcard_kosmos_org", + "ssl_cert": { + "encrypted_data": "qmx5Z5ZKRwks8monIPphrlc9Sm6KXqISO3kEv9m23bVcU/T1KxbdEu7/+9uM\nnEphL88hzOys92FbjDRvP3lmHN0CjGqamAwcPCiFEIX3oZ0GL+Cws6VaVnVa\nnTPCeVHRjNZ4J2Ss8bGdLJc2m4wAEcxEd9i9zLGmClpVpIO+Fq9NuwhB8JnK\ndTMj6NTV6u2QDcSpV+4gpnXExiFDK6QMR62737UZdkQ1C34E7Rs/ja8jsvpv\nX1WNbhGMpZ9FAgJUwchVSCZOClqanbCVXL6agJXvTGGqb6P5ffsnzKC6uvSl\n1c7FvLpjdMwZ159DSuLNXlsEJNv2yI/RpejjDBRKrOJezGQW/QFaFu3ETItO\n9vEO/UnQ9QuBZfxtOAF/9X1w5EiRagkf9+U7snKvunhH+AIqfk+n1I16VoaZ\nVjB7MkXHjXohV1XNCz/sxKYCKcBl5AVe01tTQSpmqlZ23lN6rYkePaMo3Y4d\nuRwbMHBIW1cEIy88yBq7Nc1oPxqVwtqJQCjA1O95jeVAzP2Masr9G+376wQZ\nUAJR9Tvqh64cOPh8/Sgzsc0eGfF9ndiw20Lz0j5nHvHjpf3XFUDQufpL0rKP\n6cu9kjM8qwvVD4+O7o58/nkMlKFSyrDKVg7q8U2wjtWP/yJ/0SSBE3j/z9pX\nWoa0fDlxJAWUsVu1NcKCTmjsXTela46vBCBQvIra/zVEzXgwubPq+2Ni3G49\nZ6UgI5dHdxLg2FrrAsLxFgMf6tivtYtsJM1l9Mu4jz5Rn1/r/EroibtPi0Up\nYGfabdQxDsca//YxLAX+j3rWDWvLFkpTULve+yZTCnv8Wz+mQNQiQHzMAL5j\nQJAYmxtY15ZDwCohDbX38KJuhcWX+L2kOvLn4qcjHUDzm6U3VM1ywx0EFOgw\n/mXdAICeZnLAFKyuz2JVzkimqm5SNxI4GVLFiRbuB/KfGUnhfVtVLMqKSuz9\n7BJLyXmVBJIXSeyRmNbMV+NIyMBzliLp8j64w+f4hUQhFub/owphhPHYEZSc\n2zbxq0VTcaqLfr8XyD4ylzjAxlmyWJdX9Kop1wLo6h6LJqfORsYTSmKTuZRJ\ntWcemz/R2PM0HiDXr0GTZ7NWphMWVZDl+fF67CnclGfPgFPQD3gfplPiN4Ec\nJ6bMiF6C5ZtPAbPBYxa0pXzCh7GCrRnoPY4ErIg86EUchbbEnrm+wNd98DgO\nXPu6WkXUyMB/hArxGAExxm0j6VlgFBWo5uFYMZWTF4iw0YaiUg4tW3geN28w\nFTtmb0PVYzNNz3VCScKjnSyfVNS4B4pz3W/ZTAzHGR7FupzG2H0WuIFjMLb5\nNtPVXJnq048oRcc53OAvzFE7RgEg+l+mfsR3kJtwdMUSL3fG05X9t6TpFL9p\neA8BNnr3KNPlgGsJoskHJd0WvLCmbG7a7rSI5RPwzPEy/1VzGTzQj/JPtZcN\nvLtXFRylU/N0EIOiP+2VYScn+0XJhyjNbI9S8gYwqT3Q08ZaNI9HVrYGwfHV\ntWOGcdzZkTmc26DjGPvaDfZkjAsaAIy9kNVnr2u0WtOevLvG7zkMvHCbxB2c\nGxK/OfGkAeMas1OB1kwHpBYGLd61yBCDsgQtSnYNa9KOR+l+0pj5wtI8ZydG\n/40IZK4Okga+DBHPUcoN0xr++F8iKR5RiDQm3cHev8A0R3XEaqG7L2EC0XgJ\n1rjb0MfTkgUzifSmvijwgAJ5Ns6Iu543VWDHN53Sm3dqpZsOJ5n0/xobBdjM\nOI4WsgubuijomeaOes/nOeB0a+QneB8RT/zfqRrFVH0paCgPqB+JmdBDuQ2H\nAr3ym4lPI0AiFvG7oxdCBJuyFJ3fy2X13fY4tyDlaf8/58HzRU7Bh0ldo60W\nsRDLHTf8bAIEiBtA0hW9RhQ/k2Ya7zuBVM3gg7Ch7IZpgum/uKzab93gB6Z9\noUJGe+AXsgJ7AbBMSguOYGtEGjS1NeOHQFRxjfzKPBKUumF1fyn1u1rYmAm0\ndPb1H+qQH3r6OrOs3DuowbZNiZwqRW2ZZINTCfJ3alHeLuMC+a3z4jGJQ5Vh\nc3HGf/sIbUjzi4MJHr7EpGIdRDx9FkSTrMUYsoEuCq6oC7Vk/mjCM6FotrFO\nZ6FS5MIKdblVDazxJQ9ZV8CY788M4ZrFjqOEh4GNUgmgmEsUOEg2QuCgjHll\nALQD8h1+AGQ4ABksxC6EQ6o/7j0pnnNzLSCONLU38S0WAneD6lyuPveb5uHb\nhInsIY+8U7ZDt6scEnDhUwQR8kWI3GCMolt/r89eP82AHlX7j/VblYBeKt7e\nDb7NIDgBVOTYQAudu/CTuzl92QrpdaAHI0l7RIA+POLrhvOQWzDOIkp8x0mz\nuFChMkECoHfKqLIxVOTpR8wW5EoVe3bDvVEzbsmN4d6FSaEXFtN2Uq+aIj0I\nNeLWYMID+Tq9rtR0Cm+OTptcsRscij7YbChmLxYGsK0l0msTGTL+4LMWyWoD\n2YZfqprJmWzAs6pFiv3BUJLUC3+gyBt5TiUEF4OYgOEqjOqQgvp/z75q+XXq\n9RPw5G612ZuVJ9c/2w2T1Wr0Anum29SyFcEiGwIRL3jWHbrqWCc2rdsy3zJL\n+5nkC6bJQVz0x+VFr0n5rFNtiWLWy34v9Y21lFO140Hgh/n87ExiwoMtAVN5\n7WNY2QPs1CUR8277K0Gb5xUsc4uvN3Bt1zSJijUl/krGPV/U3iY/ALzpsZgV\nkSIGaCoWmlaMRMRtWeawFavPsjJKdlvFLWA4+6ijbiJqN+u90g8xAbYTm4ZJ\nWbM0emq3E8mgncWvpGhwqTzY0XGqyTB32DPA404jCn9LXLuSeJKuq8hbiKw1\n2hh2ypvrvvggu3iUCQUvYDcYo108/OpvbZdSjLNm5ZlRqPBshWOKzNnXju/u\nmKmp1P4FvG7yW1Bw3cC6By8vKcaDOX6UqVeRSZpA0835MCnqMIlOy9wjN/mm\nCSPkbdKphXbMUG28Gm79GGbU6/10PjNlmf2bSy0UidCsjQYzoXb0Xr1yk9Ia\nj6kdV5L9vRmcEQjO3G5onEoW+seOp2ZrUpKtoDynNKNQYAcIHkHvcsrwEg1h\nXpLF6qwhy1BmjEvw8SOhFuaIpeooVcZ+xkLaniYrvXZulDqTZwZiOwZ6cNXA\nHkJUhVxLoEPVRqF6P9ybohBHef8qVsItYJ/w5Vc8mC849e+BiN3RYtdoXaQa\npMeoNjzNkAA0+McdXTrMbcGmNS6FcocGowTtHX6KXEpFVbW36OKez/tRdZnn\nowqyj79w/yeU5VEBUPtK9oqor1Duhvw+gPRVSwOR81KaNSvFHh9GU8OoIb+v\ngPnczqwNPDjDGDNIIBzfFLw9lDr+V5mu1JKYmQqB1V8mtiNUtAJWGkagOkBd\nL/rYuzPtUl+9YuqC/pDjyH3lwNKzw4TlMkbY/qsyMXfKqR3VW2i+LoeWbeqr\nYQGa20VaQ0rbWrnYC3rMIkKes7Euf3c+4C+Ulu6pa42UPLdU66IacgMq8tgt\nbFQJC3PJqQ/pWy/sG8XKKO0kjSGkLUX6rCa2JqXvuhQf/VLD5cakqrg0xFup\nO/Q7Ocb3wdZdDFWP3ifz4S1PwIbwxXVmLXUGE/ftpK8eQ7Trgu38mCxMT1ct\neeF8reK4LcBX339q5KfoKI2kxIEsVk9QFciRsIhyggpl5+G3vrcDjQKUXZrN\nG0vka6nVlHyfzhUvhSfO3gKlkkge0sKiemxiT3SG+FBvk01tq/kH32WDf8+B\ndK7cu0Z64VVq0IJlF7sMsZujvOZu0oCdGMrsUe7/R6CI+0iNOvm3UA/pmQvp\n62yMuPxVjQYln3zP+o2fd2I/7kvwp70xOJV3cC6QK5PrMJJOekD2ZxW+yTpv\nsu6JwDEczafL7x75qpPYgTIUoiYYypI1DXJSk7qSXPv7j4AhIZXkyuAaS5RN\noOkqWR38JvG2pykiHJx5bGG5aZJORoT6/VmiIXAvKJAX9b81otyzX/DSjUD3\nurBe11SoT/ww1Db6U4p8bbQ0DSPYpI335D6EgNW2BApR1zADakS0FS62Vasj\nVF4zj29WxX2MbHxo9BizouaJHffTFn+TY6XsWoEJUipTnbEgoRU1aVbt8N/M\nk1Qn2+S+d9ycbYDTLB1nTUh/E3NPK+4FzTBmkrfAGrfX1hMDy5nMMay1mCA7\n1tYkQbS7XjM3RX/jQnFHTfikBOVYtyaVhLjZ1hqT64y4OvBhAZHSGkrifGWi\nYfHKbxSJ9g0Cx0hXsaH0Q7bupRcBAT9PuLmpq6Hfa2y1z0OJ2VVvCnpXrifo\nsbVgfKI0c1x/aIcr9pjVaw3o/zbJFxNR2Q0JmpPVbjWGH3l/UKk4sacSzsBr\n90AP09ODWgm8Omh5zXA9I4e7LtasmnAno6Yglhqrd24iN6BjXF+PTkQ5D372\nsghSLqUge+7kiXW4iNjA0qjAuKbZ2NE5JbuC90RrJtn9onQ/PhhgT5JJFN3m\nGB9mKy7lzcUpmAHr01v48r+84XcVDVf29afhb5egjp9y3AkQs+QXsPBDlc6i\nuJhRvFiyjTpjktLaj+TbY+0Y/63EgsayAgIyDVUD+51VjZAy3DugfMMhnoVN\nNVj9fDrLYoXOfoA7MD7JjLw1ccZ6RcF9Wi/g3LN3SR7jRKUsaDLw7QpQ2Tgz\nTVyN5oy1wcb66ZRXRoI7mMgaxv4koGNHzgqC0eBLthuN0sn3hnlA1DlGlTxA\nIr4IiX0u/EwXyn0fh9b7TtretMY448gmb5uLqo5ABxQ7lZNupstJbRuVlLE/\nGhHjyKQoZQWNCBL56JKpYHpI/Y8Giw/Iwh0ta/2/gMQhAjPIkNHlM5aCTVO5\nt/3X8LW4XyHekPfEZbWUnx/mjd1OVR23tWmsJC7AT6zKbH+nUQKU6ilsCHPf\npfjWP6aKCemkJML0K4hfBOO5kAuZQOs9UZ0pUNMfqrt+X7TpV38aXyNc3ADf\nOVvJkKXP00jtihXxluE11eJPaNrV4JqZCfCYwuHPJt9D7HF3BbgeBw7PGVkD\nosXLxdFZeg4c3lzdZn15GtaH372GGRkaPVkwoACV8ZwuXofkI8Pf4Ei085C1\n+hrDO1x8wfmRR/G5pHvf8AMTGmZdUic+lpR0qLKUZk1JRDIsdyldcUjH+iBa\npE+nkpY2qSW7Q+5a/bVO6lSDenSUGTz/Cz1k4ITuBZ6sIlLIlpXyq15sXPLj\ngbR03kjqlLDCAiSfv/STHw4x6JFysg747FAVBWRcnOsFKhDx0WpacXDivLEZ\nYVxV17HxUnMfPOCv2JiUeMtgsTqn1gKHXonwlANI3Ur5FXnxD/5RRCs6wheQ\n6yW/G/ezY5Ev6z6wqwv2bHWkD0IIPG2oUa50uB9wpYmJWDIXopDX2DtOnSLG\n/8ElJj9ax7odh/9psK/+np3O6gQHWUknkekmBmof7LjpucxaANz6szCjAjxH\noALw/2NcQzTaVNiig50RylApQltZusn05XzfTcnHpBdZJ8NY0a8cppSahKe+\naMMWg8mvsnihw1lvA5yBVJj7ItTFr76/I+cDgB5Mq9djbAmWz1qSBb8HOaG+\n1oJ06elII1I65RUfp16nR7t2RZnTC5bWxMhyhJWRS5peZxDEFEuaeJuv3UJV\noppF43gq3OmWaM4Q+sYzEYI/p4yZYFXWSMvppDJir9sNFODywa7Pui6mVN9f\nMaosR8+ZQy6T7Waa27e+6tN8sWM/mfQRMzKxixAjQO8YJlSegMgykRSNpSqF\n+0+J5Si+RBVtXQmlhVDaa1LU2cEVZPkwX0ipDXrr26i2Lkr7zmPOUxpqkrad\n/YQNLphRErYgoVcoF0Qx4Bc2dC4+uSaoLoQG89pPI0EBSiZ9O8Y0pLj8dI3y\nkKVLcHfOqh8F5PQVnel4jsq/tHgCfeGiLFTSIK9wOptzavZPESmxBJtky6pY\nM1MfPx/T3Yhgy7iXEFJ6MYOkPiBIKXvfSofMaWvREpJNPOvqgOtQldEKWCeU\nkDP/DASOok4GG+fPBpF0cNUnI15VUgng3pK5lsp4XEkUH72vKhaE+3zQziR4\nfbI9h4Tm+RZSTYrZEimOSUIklQx97eByGsbOxr+eBhjkXyieU1ge9XyhNOTz\nK5k/jTEHbYTcm03XsaoU32GysNvvr6cQLgV771xPckNt9IcKlLoJGJCpzs8N\npSuJxskKmHzKatOYXEIIu+QjQlhYLuVjkTcphHxO9F9DXyLOP3DZsHif+tsl\nuEbN2l/PyK1sI3OyRzJTjwKv5MstEsQTKzaPMa/k5xdfiId7tzzENaOLscQd\notjkahplbNGW5CWxxp6mLKmg+wYAdybmxGh8Yd++h2jGvf5r2bokvEHUUiY+\nQ6kvyGZjHO38NHaPeTNfr4wfoRTVRUp010sWWLzYGYeuFnGal3Wtvp0zyIsG\n6K2qeU95MfTwba6UASRil2W97tsaQqQf8Dm2XTZBiCwRyFGLnleGN9J1MvEz\ntsHgL82PQn8r3PgR3TbcHGVVWr1oUDsslu1I5oM6fbqKKfPfZFeNc5uNFJso\nla/XjoWo5VAvc0hc8vDrTAOcmCYzmz5wcf9lODITrwl+QzNNHfWHXPWjF0i+\nPqS+BMDWLL0mcjFtBZV7dwqatxU+dXK42Goi35oigO71iyIw0HWQXhS+TYLv\nkXTJyI+Pxcvy+HN7Pcl+W8NKcHNcKq1ue+Ec9lA64hNNW/v2DB5cdwm+p8uN\nz30oSta9zjs4pjHU0/SM5s/BZ7pyOC4KFKCt2Lp5tJCX8HQr2m+/Q4TTorfb\nhGSKIxUwbuUVvon1MzIS50cSL1yhPPvXf0JUwRUThmXmTdnpiN03Fj55+EZm\n8kirxjCmyRJiS2SodTVYCP2sxPlRu8+ar9jAVqNBMZZaOSjpEC29p3snqiRb\nqSHZ4r7grfkoFB2xKQBikV9IMw7STj1DYZVZB9hrGxC8TG7B3/HWZffbrujH\nMuH4br/+TYepkeHgJQ9yn3GxRUWche1sm4/MgNptx5L3RUZ2geY783L0En6v\nxMtTJfoUYRDqNEHON0ibdzru1dYaTVEdES2JWwxEnefGzBUAjOQIRZpehSVi\nha16hdrrCLG1578ghKGnSYCIbZVkzuWn+bCi13G6wpuE+FuY1mI11cADigKe\nYzN8x0u+vD+4Y8Jybmk5s32XyQGmKg4Uxl7fe0G7m3e/YueZZ602F+KMa+iT\n/DuHluJzOOoDokrDpMfQAxMP7sDkRPLwODE7WC5APeTzwy+9NMXazoOxsPi/\nvdZ9vL4iJYi58MwKhYGGD68rLACKHX1ewNtmijL5T8Voy042pwIJU/Zt70k1\nzkwUdn7TjM/CiH2EJ1X2VRXnyB2F/W/id1K6KGEPdbstQ1MPu9YOP25+0HeT\nuifSij6EtZOjePzYFOqM2n0Y//VN7DlWnTu/tNGr/e1DwCQOFws7szrU0BTm\nEJg25xGHsNlT48X3i7k8FA4rjcnNC9ZL+1fKyyZCAqlB1O2aqWgGjBpzs5Q3\nShVGKhcd68JrtHuAjawmqDiNuHXQ7LZbq50emAJxExXVLViqU9FOuxJnp3rj\nE9QyciEiNYL85gPB4LvFscV9cJ3ni4Q7xxjYSdipMlOKb0E+5xZrTiZyBt/k\nafd3ktzsBHXDX55kF447svkT3mlyJ0PfFxI0ykTlTPEgujLdSouv3q7ZI9V3\nFYg2ZSPwWxrVKWr3ab3h2lb0KbkojJvZzd528v6tbLB/8WUWHc/B+ub/OIBT\nYFfQWlVx/2JnRDwWpv3UrDgLnCNAX9Wt5MA5rVEDV3eT7pjFzR9ZBw0bldha\nzV02V/qWwHPjWxTiUDEZk5hulPw+avZfQOyT0io+b8bV+pHaA4ip8WL3mq+z\nZP5G7JlvBenW2rxU9haxLNy3Jui9YJXzrMolemUXX6KsJjZcHDtqJpBlfRfJ\n0pvPm7RUuO5XfVLWbJydpdJT0Mmio3oq/UADd5AKp0Toc6/I7fMx2mrAObcH\nl4tC5/K4dPhEmVJmM0upvO/SK1tTfz9G31nIte/bhcoyaZUK1cNDUYHKY2Jk\nv+EFjes6wLa7Qj6/elZiHS/KZUbZeoPrX0+CgtMo19g6pdGONSnTjPvQZSKr\ndZiaesPIh2pMU1QBXaEiOgiZ24NzLEM7LYR6Wh4zDdcSmHsABj3Raz6G4HSu\nS4GhBNDSZowVyM+kg2uGZwvGSZvuomYw5IYOULLKGVTRyxeKijg5i9hGQmjR\nLkMxkGiXcDH4dtLz4Ka8stvZUZMLYJH+gSqx3gJGIIrnVKUsQo6NAyHXEIMh\nGmOsoS2v35PhFcr4PZEwTh9h3+/trUZEsPRq6Z0mT6C8hQNYs1RiJXJ1/WzP\n9ueV1/h8ZcTxV1HcEEOZzn1MMh6MR/b3kNA8Gyn+A7aaxk7RxdNXq+uhnf/W\nkxuGkJ/SgTIU1d0FYKA5wwQ9mT9yGS4p1xWimxLsuQkdnH2y58MpjcGCVgxJ\njsWyf43Z99mD0wgquTVM6itP5M1TQw6PO5jkmQe9u9qjwedXoug6e+2jIDmu\nBXumuqI/C2RoVBOpQ8QgO8gcpQeKLpnz57JLA/xqY8nSiGGkmZMy/Q2ndDHA\nWoAc1ur3AwBBch2Xs1CSpk7KvVCh8Gtp1X8kJxDs1OuTxvMXFEdKAlrwI+YH\n0ncUR5mD6j7QsBvby9y1IcJcBormlqgpJe4pjzgwNrdV4HZVL3KTXAWBJA7w\n7ObNhGIYURcEh4tkvLugtdknHNgU1KrH4Y0h4whRnhi7ObrlO/i9cgAYBIGt\nSInJ3SzuFktA7bP1kP+LlTpJ+5z229rm54LPfpmnY/fh3ORHbE25a48RIsSs\nhO9AkERRVY0C9QTuEbJn0NPVm3Wincf1VpiLoqoLzl1rjjO6zAarg+FL0Yuk\nMV+YLXNLAla/M1uW0h/mogPmfyFe3HE0XJAtCcNYXDsRvsMTbbClQ1l+fDmI\ncVST3wMLACUpkA3Kj4d6wXeA+Q2IWLzo2S48Krk5OcfC+R72R53vaVswFh2z\nsWLB3pZapM14hQ/2k3mrkXmK8mWLPWrPvw2Xb2Z+836vvjl2qOKHG9xhURh3\ndEaNdO5AWr/aCwDbNYX0Mi9pf3ZL3ZWh67706pRutwPI1Fwcr/aKD1BpKKhv\nVfA9w2UI72gIWY1zbqn+FkR0l6TeNf4J+zL0L1w0AsJa9olk98321w2R09aU\nIyDrf4MR68FYJM8x5NpwBP2jDUWGq0ZXhiilQumdIR9fRdoUMjryzMHEP1zq\nzm/Atyw/eonp3vRns+HC01zmbPJQVdiNlvHBLF9RgPT2zIoRCXR7IXyH+4A7\nsl3TqEEnIf1ocKO+HPAEu0GD6JTwQ/ezhwSJsLETOhSdWfcO0u8Z8Rj5IFUf\nKQJm6vEKZwuE8IMczgyrcIpsxp/b7kIAYDzFqSaMZgWrJOdVcTAXC+Qc0LSs\nSwha7BhsuPuEHZe8GPa88rpUv/SKs38bqgAas5QngLBbkFTXqyffXUZpFNwL\n29j1L6I4ZE0XLXGA0s2GSfyIPidVNxDYtFJMBsfrx9xYkHAX3f28eBge3hwE\nvMGY9mErSZywn/dXZns/S6z+ysKl3cDVcnNZlHXsr4FUuf0frb+2XU9biNf5\nsUhMw8Cyq6wa4MKYMMlbORKTTGndXO5jRSuQGo0JTPTEMvuDoBfMs07ky5X3\no4UdkIeiVaVcvX3UZSEQ2IgXvjHEh2MUaxccQdTdZ265Xw7NvCq8suLX0T/Z\nf3F82NGbnpr/dvNJWDxNtXItgJBhWjXQIZKp6f3G+kW4WunCzZ+RGMwqpke/\njI1+RMTODyK9O87fj2OUL+x19TF9zUFXY/lth2/ibBsm+kZ4wMHJl/VHt2IS\n/dl5rquIF751xSjlceNUh4vbJBcZZhAjL9qAQx4/GzqUnkIJUxRIdB7q8kHx\n3Ni1wiBXWv16EdsFi/y8NLomKpb5ZiDkuJSTH16fuZOuStQCsLv0Hz3+NE64\nKwNxRHm1uhj1Hx7Irq/oepIYmJsW8JGC8Xdw4XmE4r/KM6BcP31fYfLEdO3e\ngkMT4kCKcRT6zsdn1hNaRSS2LQ4G3+x1M8nOngpn0dqxjbI5NPKklNDhAdgy\nDv8tMrd+saFs3HUMAotOj9kv9agUsVw/2w73EWR1k5CEyi4xkk/JsysFDBeU\nDqOKM9ZLYaFi7q7J9oYJG7QkEOoQtR8Ctk3n/LokZseTO92ehQfY7dZF6KXi\n7WTSWl04tjkHsnN0fpUk6+W0Obp0h74qu7gbnRtyTXoBwc8mH4dnp6EQCRH7\nYXRahSWWK2QjBm5tCao=\n", + "iv": "ZwwZ+OH+cj0zP+Zi7IHJmw==\n", + "version": 1, + "cipher": "aes-256-cbc" + }, + "ssl_key": { + "encrypted_data": "d5kBGSohWVr/dDww5nNB/ZOUWpPnLUGHZ7NQ1So62TP/f2OHZ9BEKtHmRnV1\nVVevqwezB93GTIURKbMXUG2Vd56sa5Lb/ayJPO97GpuxPJ2yyw4Uj5iI6ZWy\nl/9QERgE2zsogi5l9vSMLxuEwpTI9WqcQgjQc9YkppoZHipSmJxUuWqCEpEC\n784//MuBRBJsOFvn9nkCvkVXkksSolRljmgOfsb+8svqaOwTvDe+HPwLkjdi\nFGMvFrQqywYPnPmyZrkmSWB87O3B3HoS6fEyUNFph/C5SfKMdRNJkCptWx5R\n4hLkD7dDHLXMagSqGwJ/VpKVHythy23ezNCDM0mXHoyFRAtUv29KQJVZOMlR\nymOZvToA6wE9hzH/xoaEozAtXYI5YnNMmEjGGk11DkUfpRyOAnmyy7ECjbHm\nVZid7iqhiRk93gRcoOUFS/XFLKTXndSVFVf8FizDf5cpPSHYJ28ZyModTiS1\nwThHUERUrgPcqFT2EX/9HGvBcI+yZ3eiroiH7IakswQczD0FHkWLi5RgVC9T\n4KzEAuvj8V8qsvbqbwI1st0ohPn0Ve2VsHYmG2Vpyd5zvFmGp/WS5oYFlzTd\ntRG1QejXD/PivI2Iih449p+84qnTzCMdkLnbsha/W0322dlKKXCZfMWCtWRH\nmnzfKQDczKWp1VSED8M2Eu/0wy8hnQXnUodghA88m5uk1SYyd8n6Gds+hENi\nFaNb+CwEcDauahujIo34DmqFbiMin1JlRj8vcWAngDk4Q1Mc7s72is+8e+p9\ntrQk183OSmtIR5UkmdSZcLrmdSacuSeoNmcxREmJwxeJCFi2nCiaWLan/3dv\nxiBvIr0upIrnXgou4va58lV6b2qLNKeaXAPUCQlbjpVk7YzWsLjTJWwyvOKT\n1tDmozD1u3PUUG2QZ0uMErBWyi8rNH2aytuXikXPLywZowopfjkP+Sx5Rnus\nVVQ+Q7OJVQpuErq+rAEtoG90+Zj4spKOat8ISqZOC2oBEPuU7/dOK8RsRLtn\nhYuPIjgF21lFHqB5cuyxVTZ+X/pdJ7siD6Zk0viMQtngFn+FC+oTxXUGHncU\n2VvotnGeB2dawsfSKKeYxSsz9aRSD9wuF3kRNgAcCGiAeerI30M1TnZYTnum\nApgjsxrfQw3qsEupEAjvsPiJLYmWqijL6Yb+30JUjIE9PwcNqp+5O7mUdchs\nPjvnMxPIlLKL2iPK+/6QKeIhT9Jv46sctrR9ZL+qYZprn4HMzYaY88v+Dua9\nzQ4ZKnY2a54nb+dsur2P8Xzq0pC4EMIilNCh8Kfk19h9mHMBLIwvnJ0ppavH\nAb/TaUGuH7c6Gm6nsSsISuGDslpgQnApJ7Okupyq4gzEZlkOTFeWYhPHkEvN\n0Iqdt8I110L68D3Pw67O+VX4ksjkyGFfkp/jP8RUz9z0ZR8SzJ1++AZODyxd\n6cGE633FKz471to6OKjdhH3o17ymHK6qTAiphYPDiCKyI+Xo/XylqGnaE0dY\nMjo4HCKxLoX5MeBFtBoGFReVF5+ZEDgtDvI6ZETELtpuvF5zz2kFs8Fn8c/E\n/907ObWNR5wtQLyotq++UseDJgsKC1p3SDFS0OIAuhVp6sOZ6fZ7CjP5wohC\n34iTjC55ThPBRNvESxwoNdjKi0mQR1ex16XCMoE+EZKrtb58uilcfWbb1Ug6\n5lRTbNytQMFYNXqGzuxubPSFQzG9dOEqWyrd6k3w599qeCf3Re6NE9NX+/Jo\n9FgqbNYj0WQGvtgFs/GaYf7b14iip6wlFAgy39iW7bcWT/M6VzXLDRoX6UM6\nYwcxouwH+skhNn+6SMD/2KHl6x6wRhozc5/i6lq5Ddy9YleIdQdLSFlKPYI1\njRUNsN4qVjJuu3qwRWAf4Ex28m0Qs5UgAV18jWOMQ4kiIp4XkVrBAY0o/5Y2\neZnByT1mFMu3/LNG7QyClfuqLhBI8v6xsXqlb0nqr2/EThqQ693SaA3XwLj4\nBIUSNr7NVwS5D5bLHOWn35h9hjtHUrZ/CfZxz/PGcRBroEHNaL/d2LQsEfxO\n66FxuJK+iI6irzlLVAQvJk8wq/LA3QwKUylFK+YhinV2V62cSFO6Jz+NALc7\nmDuD1mUUHEekuePJq3zu4wW2VojtcYusDjjI8ZeLeKwjzU2ynCR/zIbR9xc7\nItwT8+b88xDYXdD3b9gIanCvSRfR9nAUT6f00LHhP0tEIp2mSkcXA6SK2McC\nIQMq29X7pSli7sJdG4/OAmgTFgoYYoEeFIZsA9ZdG7LPZ2I70WZHRCvB9pCI\nfnTgdPYDlHqEiyAMDmxu6CLTV3QPo7WRfg/EdX8+jDyLfXWuqiKySvln3GfZ\nCb8u818=\n", + "iv": "NpqZvfmGKPUWFaEDddjAeg==\n", + "version": 1, + "cipher": "aes-256-cbc" + } +} \ No newline at end of file diff --git a/data_bags/credentials/hal8000_freenode.json b/data_bags/credentials/hal8000_freenode.json new file mode 100644 index 0000000..0bf116b --- /dev/null +++ b/data_bags/credentials/hal8000_freenode.json @@ -0,0 +1,15 @@ +{ + "id": "hal8000_freenode", + "nickserv_password": { + "encrypted_data": "8UeeJPuL8BOqky5xYxyVqTMiVRzUM8fiCv9SBl3eUUDkjBDHiuK2gQdUvAlj\naS2apyzs9icCV0m2WYqqatCoS8us1CTHVKCHHMGZLgRDQQU=\n", + "iv": "gWBMu8ncZMcEIicewQi6sQ==\n", + "version": 1, + "cipher": "aes-256-cbc" + }, + "rs_logger_token": { + "encrypted_data": "GHXhXbtXXk2DgHe1MTr26FZZ+gC9LpnN78DdOFbcwZUfQFhwHVd65UgwoPXD\nk2mG2S6VaLOj5/dsY1ME4uXtPQ==\n", + "iv": "esuIe6UsToO+lRjeyi38rw==\n", + "version": 1, + "cipher": "aes-256-cbc" + } +} \ No newline at end of file diff --git a/data_bags/credentials/schlupp_5apps.json b/data_bags/credentials/schlupp_5apps.json new file mode 100644 index 0000000..ebeff24 --- /dev/null +++ b/data_bags/credentials/schlupp_5apps.json @@ -0,0 +1,15 @@ +{ + "id": "schlupp_5apps", + "nickserv_password": { + "encrypted_data": "JHVqbMe7aNbvxZtDAAvvt1+z38oXUxaaWWA4dCx8xX8=\n", + "iv": "VvKvfWJtAL4jVKjV6TXrEg==\n", + "version": 1, + "cipher": "aes-256-cbc" + }, + "rs_logger_token": { + "encrypted_data": "veTO2Iv3YEEzDq0L4nDV1YoOpS9fSYb1aIeY5cbtdWE4PCNJLQLM3pN1JVaB\nzPa0FNfM5+CkBYGnDmMZHwDuZA==\n", + "iv": "pJQnZZIwfFNDMmOl48hTNQ==\n", + "version": 1, + "cipher": "aes-256-cbc" + } +} \ No newline at end of file diff --git a/data_bags/credentials/smtp.json b/data_bags/credentials/smtp.json new file mode 100644 index 0000000..9735569 --- /dev/null +++ b/data_bags/credentials/smtp.json @@ -0,0 +1,21 @@ +{ + "id": "smtp", + "user_name": { + "encrypted_data": "tYas5OBhgg9vdJBZFhRNAlgrKpWXlZX5JKMeFz4Jykvv7WC1+ZwJ/xLdqxuy\nhq9s\n", + "iv": "tN0tGwfiswWVOJfkplAMNA==\n", + "version": 1, + "cipher": "aes-256-cbc" + }, + "password": { + "encrypted_data": "l92Z8uS1fC23nHdnUT/xWTwKDvjPTVRrdys+K0vydk+n1jri8FMcg6VItU1o\n+Q8aCTz+ppG63PoJiSbIQ1tT7w==\n", + "iv": "rAUVE3OD1W9YcTwPZeHlNw==\n", + "version": 1, + "cipher": "aes-256-cbc" + }, + "relayhost": { + "encrypted_data": "yBgk7NHy+wsr3AK1WKVp/uUPnqsbAowGBIZBYA20RVx6ilptjxMAoh/OR7rx\nQ7tJ\n", + "iv": "+fd4sY1EiUxtV1lBjuKixA==\n", + "version": 1, + "cipher": "aes-256-cbc" + } +} \ No newline at end of file diff --git a/data_bags/users/kare.json b/data_bags/users/kare.json new file mode 100644 index 0000000..8a04704 --- /dev/null +++ b/data_bags/users/kare.json @@ -0,0 +1,9 @@ +{ + "id": "kare", + "ssh_keys": [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCw0I82gT8R4tpsqWGovLyjm2SR2F863MqNz224h3h/wl0xA5Eu0eRro+ELLv2hoebqQbcMsb89X5+7ObhDRar+b7tzDlXq4x+ECkAy6WbDSmBp3kNVd7muT4c9Zw7UxKsIvIm1ven1TkJ3UG80o6PyGiAUlBj4puIQwhp7OVknVutBBe8Rpp4f6BEuWluwpnPxc3KSaGhhr9p10xeX69cfspH40r8vHpI0zp19O5GpfYSOEH64UbwRpN2QypNB8ISmDHFsNGwdz0Ba4qrEOSGU9GveyOcsvEtt630/0fHqtbPBovOYu/FJISQZya2tofDig4EngBCJNfsPCbXFHtlp greg@karekinian.com" + ], + "groups": ["sysadmin"], + "uid": 2001, + "shell": "\/bin\/bash" +} diff --git a/doc/encrypted_data_bags.md b/doc/encrypted_data_bags.md new file mode 100644 index 0000000..01a125b --- /dev/null +++ b/doc/encrypted_data_bags.md @@ -0,0 +1,2 @@ + +https://github.com/thbishop/knife-solo_data_bag diff --git a/environments/.gitkeep b/environments/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/nodes/dev.kosmos.org.json b/nodes/dev.kosmos.org.json new file mode 100644 index 0000000..ff5e719 --- /dev/null +++ b/nodes/dev.kosmos.org.json @@ -0,0 +1,11 @@ +{ + "run_list": [ + "kosmos-base", + "sockethub", + "sockethub::proxy", + "kosmos-hubot" + ], + "automatic": { + "ipaddress": "dev.kosmos.org" + } +} diff --git a/roles/.gitkeep b/roles/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/site-cookbooks/kosmos-base/README.md b/site-cookbooks/kosmos-base/README.md new file mode 100644 index 0000000..04e0070 --- /dev/null +++ b/site-cookbooks/kosmos-base/README.md @@ -0,0 +1,4 @@ +5apps-base Cookbook +====================== + +This sets up base behaviour for our servers diff --git a/site-cookbooks/kosmos-base/metadata.rb b/site-cookbooks/kosmos-base/metadata.rb new file mode 100644 index 0000000..7fabd33 --- /dev/null +++ b/site-cookbooks/kosmos-base/metadata.rb @@ -0,0 +1,16 @@ +name 'kosmos-base' +maintainer 'Kosmos' +maintainer_email 'mail@kosmos.org' +license 'All rights reserved' +description 'The Kosmos base cookbook' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '0.1.0' + +depends 'unattended-upgrades' +depends 'users' +depends 'chef-solo-search' +depends 'sudo' +depends 'kosmos-postfix' +depends 'hostname' +depends 'ufw' +depends 'omnibus_updater' diff --git a/site-cookbooks/kosmos-base/recipes/default.rb b/site-cookbooks/kosmos-base/recipes/default.rb new file mode 100644 index 0000000..7833582 --- /dev/null +++ b/site-cookbooks/kosmos-base/recipes/default.rb @@ -0,0 +1,49 @@ +# +# Cookbook Name:: kosmos-base +# Recipe:: default +# +# Copyright 2015, Kosmos +# +# All rights reserved - Do Not Redistribute +# + +node.override['omnibus_updater']['version'] = '12.4.1' +node.override['omnibus_updater']['kill_chef_on_upgrade'] = false +include_recipe 'omnibus_updater' + +package 'mailutils' +node.override['unattended-upgrades']['admin_email'] = 'ops@5apps.com' +include_recipe 'unattended-upgrades' + +package 'ruby2.1' +package 'ruby2.1-dev' + +include_recipe 'users::sysadmins' + +node.override['authorization']['sudo']['passwordless'] = true +include_recipe 'sudo' + +include_recipe 'kosmos-postfix' + +node.override['set_fqdn'] = '*' +include_recipe 'hostname' + +include_recipe 'kosmos-base::firewall' + +package 'ca-certificates' + +directory '/usr/local/share/ca-certificates/cacert' do + action :create +end + +['http://www.cacert.org/certs/root.crt', 'http://www.cacert.org/certs/class3.crt'].each do |cert| + remote_file "/usr/local/share/ca-certificates/cacert/#{File.basename(cert)}" do + source cert + action :create_if_missing + notifies :run, 'execute[update-ca-certificates]', :immediately + end +end + +execute 'update-ca-certificates' do + action :nothing +end diff --git a/site-cookbooks/kosmos-base/recipes/firewall.rb b/site-cookbooks/kosmos-base/recipes/firewall.rb new file mode 100644 index 0000000..d46565e --- /dev/null +++ b/site-cookbooks/kosmos-base/recipes/firewall.rb @@ -0,0 +1,19 @@ +# +# Cookbook Name:: kosmos-base +# Recipe:: firewall +# +# Copyright 2015, Kosmos +# +# All rights reserved - Do Not Redistribute +# + +# enable default firewall +firewall 'ufw' do + action :enable +end + +firewall_rule 'ssh' do + port 22 + protocol :tcp + action :allow +end diff --git a/site-cookbooks/kosmos-hubot/README.md b/site-cookbooks/kosmos-hubot/README.md new file mode 100644 index 0000000..7781499 --- /dev/null +++ b/site-cookbooks/kosmos-hubot/README.md @@ -0,0 +1,4 @@ +kosmos-hubot Cookbook +===================== + +This cookbook sets up our hubots diff --git a/site-cookbooks/kosmos-hubot/metadata.rb b/site-cookbooks/kosmos-hubot/metadata.rb new file mode 100644 index 0000000..d9998dc --- /dev/null +++ b/site-cookbooks/kosmos-hubot/metadata.rb @@ -0,0 +1,9 @@ +name 'kosmos-hubot' +maintainer 'Kosmos' +maintainer_email 'mail@kosmos.org' +license 'All rights reserved' +description 'Installs/Configures kosmos-hubot' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '0.1.0' + +depends 'kosmos-nodejs' diff --git a/site-cookbooks/kosmos-hubot/recipes/default.rb b/site-cookbooks/kosmos-hubot/recipes/default.rb new file mode 100644 index 0000000..02cc613 --- /dev/null +++ b/site-cookbooks/kosmos-hubot/recipes/default.rb @@ -0,0 +1,96 @@ +# +# Cookbook Name:: kosmos-hubot +# Recipe:: default +# +# Copyright 2015, Kosmos +# +# All rights reserved - Do Not Redistribute +# + +include_recipe "kosmos-nodejs" +include_recipe "kosmos-redis" + +group "hubot" do + gid 48268 +end + +user "hubot" do + comment "hubot user" + uid 48268 + gid 48268 + shell "/bin/bash" + home "/srv/hal8000" +end + +hal8000_freenode_data_bag_item = Chef::EncryptedDataBagItem.load('credentials', 'hal8000_freenode') + +application "hal8000" do + path "/srv/hal8000" + owner "hubot" + group "hubot" + + action :deploy + + repository "https://github.com/67P/hal8000.git" + revision "master" + + nodejs do + entry_point "/srv/hal8000/current/bin/hubot -a irc" + # Use our own systemd service that depends on redis-server + template "nodejs.systemd.service.erb" + environment "HUBOT_IRC_SERVER" => "irc.freenode.net", + "HUBOT_IRC_ROOMS" => "#5apps,#67p,#kosmos,#kosmos-dev,#remotestorage,#hackerbeach,#unhosted,#sockethub", + "HUBOT_IRC_NICK" => "hal8000", + "HUBOT_IRC_NICKSERV_USERNAME" => "hal8000", + "HUBOT_IRC_NICKSERV_PASSWORD" => hal8000_freenode_data_bag_item['nickserv_password'], + "HUBOT_IRC_UNFLOOD" => "100", + "HUBOT_RSS_PRINTSUMMARY" => "false", + "HUBOT_RSS_IRCCOLORS" => "true", + # "HUBOT_LOG_LEVEL" => "error", + "EXPRESS_PORT" => "8080", + "HUBOT_RSS_HEADER" => "Update:", + "HUBOT_AUTH_ADMIN" => "bkero,derbumi,galfert,gregkare,jaaan,slvrbckt,raucao", + "RS_LOGGER_USER" => "kosmos@5apps.com", + "RS_LOGGER_TOKEN" => hal8000_freenode_data_bag_item['rs_logger_token'], + "RS_LOGGER_SERVER_NAME" => "freenode", + "RS_LOGGER_PUBLIC" => "true" + end +end + +schlupp_5apps_data_bag_item = Chef::EncryptedDataBagItem.load('credentials', 'schlupp_5apps') + +application "schlupp" do + path "/srv/schlupp" + owner "hubot" + group "hubot" + + action :deploy + + repository "https://github.com/67P/hal8000.git" + revision "master" + + nodejs do + entry_point "/srv/schlupp/current/bin/hubot -a irc" + # Use our own systemd service that depends on redis-server + template "nodejs.systemd.service.erb" + environment "HUBOT_IRC_SERVER" => "5apps.irc.grove.io", + "HUBOT_IRC_ROOMS" => "#5ops,#core", + "HUBOT_IRC_NICK" => "schlupp", + "HUBOT_IRC_NICKSERV_PASSWORD" => schlupp_5apps_data_bag_item['nickserv_password'], + "HUBOT_IRC_PASSWORD" => "5apps", + "HUBOT_IRC_UNFLOOD" => "100", + "HUBOT_RSS_PRINTSUMMARY" => "false", + "HUBOT_RSS_IRCCOLORS" => "true", + "HUBOT_LOG_LEVEL" => "error", + "EXPRESS_PORT" => "8081", + "HUBOT_RSS_HEADER" => "Update:", + "HUBOT_AUTH_ADMIN" => "galfert,gregkare,basti", + "HUBOT_IRC_USESSL" => "true", + "REDIS_URL" => "redis://localhost:6379/5apps", + "HUBOT_IRC_PORT" => "6697", + "RS_LOGGER_USER" => "5apps@5apps.com", + "RS_LOGGER_TOKEN" => schlupp_5apps_data_bag_item['rs_logger_token'], + "RS_LOGGER_SERVER_NAME" => "grove-5apps" + end +end + diff --git a/site-cookbooks/kosmos-hubot/templates/default/nodejs.systemd.service.erb b/site-cookbooks/kosmos-hubot/templates/default/nodejs.systemd.service.erb new file mode 100644 index 0000000..8dc98a9 --- /dev/null +++ b/site-cookbooks/kosmos-hubot/templates/default/nodejs.systemd.service.erb @@ -0,0 +1,17 @@ +[Unit] +Description=Start nodejs app +Requires=redis-server.service +After=redis-server.service + +[Service] +ExecStart=<%= @entry %> +WorkingDirectory=<%= @app_dir %> +User=<%= @user %> +Group=<%= @group %> +<% unless @environment.empty? -%> +Environment=<% @environment.each do |key, value| -%>'<%= key %>=<%= value %>' <% end %> +<% end -%> +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/site-cookbooks/kosmos-nginx/README.md b/site-cookbooks/kosmos-nginx/README.md new file mode 100644 index 0000000..9df5cf6 --- /dev/null +++ b/site-cookbooks/kosmos-nginx/README.md @@ -0,0 +1,4 @@ +kosmos-nginx Cookbook +===================== + +This is a wrapper cookbook for nginx diff --git a/site-cookbooks/kosmos-nginx/metadata.rb b/site-cookbooks/kosmos-nginx/metadata.rb new file mode 100644 index 0000000..23f8528 --- /dev/null +++ b/site-cookbooks/kosmos-nginx/metadata.rb @@ -0,0 +1,9 @@ +name 'kosmos-nginx' +maintainer 'Kosmos' +maintainer_email 'mail@kosmos.org' +license 'All rights reserved' +description 'Installs/Configures kosmos-nginx' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '0.1.0' + +depends 'nginx' diff --git a/site-cookbooks/kosmos-nginx/recipes/default.rb b/site-cookbooks/kosmos-nginx/recipes/default.rb new file mode 100644 index 0000000..1cac1eb --- /dev/null +++ b/site-cookbooks/kosmos-nginx/recipes/default.rb @@ -0,0 +1,32 @@ +# +# Cookbook Name:: kosmos-nginx +# Recipe:: default +# +# Copyright 2015, Kosmos +# +# All rights reserved - Do Not Redistribute +# +node.override['nginx']['default_site_enabled'] = false +node.override['nginx']['server_tokens'] = 'off' +node.override['nginx']['log_formats']['json'] = <<-EOF +'{"ip":"$remote_addr",' +'"time":"$time_local",' +'"host":"$host",' +'"method":"$request_method",' +'"uri":"$uri",' +'"status":$status,' +'"size":$body_bytes_sent,' +'"referer":"$http_referer",' +'"upstream_addr":"$upstream_addr",' +'"upstream_response_time":"$upstream_response_time",' +'"ua":"$http_user_agent"}' +EOF + + +include_recipe 'nginx' + +firewall_rule 'http/https' do + port [80, 443] + protocol :tcp + action :allow +end diff --git a/site-cookbooks/kosmos-nodejs/README.md b/site-cookbooks/kosmos-nodejs/README.md new file mode 100644 index 0000000..123d6e8 --- /dev/null +++ b/site-cookbooks/kosmos-nodejs/README.md @@ -0,0 +1,68 @@ +kosmos-nodejs Cookbook +====================== +TODO: Enter the cookbook description here. + +e.g. +This cookbook makes your favorite breakfast sandwich. + +Requirements +------------ +TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc. + +e.g. +#### packages +- `toaster` - kosmos-nodejs needs toaster to brown your bagel. + +Attributes +---------- +TODO: List your cookbook attributes here. + +e.g. +#### kosmos-nodejs::default + + + + + + + + + + + + + +
    KeyTypeDescriptionDefault
    ['kosmos-nodejs']['bacon']Booleanwhether to include bacontrue
    + +Usage +----- +#### kosmos-nodejs::default +TODO: Write usage instructions for each cookbook. + +e.g. +Just include `kosmos-nodejs` in your node's `run_list`: + +```json +{ + "name":"my_node", + "run_list": [ + "recipe[kosmos-nodejs]" + ] +} +``` + +Contributing +------------ +TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section. + +e.g. +1. Fork the repository on Github +2. Create a named feature branch (like `add_component_x`) +3. Write your change +4. Write tests for your change (if applicable) +5. Run the tests, ensuring they all pass +6. Submit a Pull Request using Github + +License and Authors +------------------- +Authors: TODO: List authors diff --git a/site-cookbooks/kosmos-nodejs/metadata.rb b/site-cookbooks/kosmos-nodejs/metadata.rb new file mode 100644 index 0000000..73c962a --- /dev/null +++ b/site-cookbooks/kosmos-nodejs/metadata.rb @@ -0,0 +1,7 @@ +name 'kosmos-nodejs' +maintainer 'Kosmos' +maintainer_email 'mail@kosmos.org' +license 'All rights reserved' +description 'Installs/Configures kosmos-nodejs' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '0.1.0' diff --git a/site-cookbooks/kosmos-nodejs/recipes/default.rb b/site-cookbooks/kosmos-nodejs/recipes/default.rb new file mode 100644 index 0000000..d017c78 --- /dev/null +++ b/site-cookbooks/kosmos-nodejs/recipes/default.rb @@ -0,0 +1,18 @@ +# +# Cookbook Name:: kosmos-nodejs +# Recipe:: default +# +# Copyright 2015, Kosmos +# +# All rights reserved - Do Not Redistribute +# + +include_recipe 'build-essential' +node.override['nodejs']['repo'] = 'https://deb.nodesource.com/node_0.12' +include_recipe 'nodejs::nodejs_from_package' + +# Update npm +nodejs_npm "npm" do + version "2.13.0" +end + diff --git a/site-cookbooks/kosmos-postfix/README.md b/site-cookbooks/kosmos-postfix/README.md new file mode 100644 index 0000000..0a3f698 --- /dev/null +++ b/site-cookbooks/kosmos-postfix/README.md @@ -0,0 +1,5 @@ +5apps-postfix Cookbook +====================== + +This cookbook is a wrapper for the postfix cookbook. It sets up postfix the way +we like it. diff --git a/site-cookbooks/kosmos-postfix/metadata.rb b/site-cookbooks/kosmos-postfix/metadata.rb new file mode 100644 index 0000000..456783a --- /dev/null +++ b/site-cookbooks/kosmos-postfix/metadata.rb @@ -0,0 +1,9 @@ +name 'kosmos-postfix' +maintainer 'Kosmos' +maintainer_email 'mail@kosmos.org' +license 'All rights reserved' +description 'A wrapper cookbook for postfix' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '0.1.0' + +depends 'postfix' diff --git a/site-cookbooks/kosmos-postfix/recipes/default.rb b/site-cookbooks/kosmos-postfix/recipes/default.rb new file mode 100644 index 0000000..c45bc8c --- /dev/null +++ b/site-cookbooks/kosmos-postfix/recipes/default.rb @@ -0,0 +1,23 @@ +# +# Cookbook Name:: kosmos-postfix +# Recipe:: default +# +# Copyright 2015, Kosmos +# +# All rights reserved - Do Not Redistribute +# + +smtp_credentials = Chef::EncryptedDataBagItem.load('credentials', 'smtp') + +node.override['postfix']['sasl']['smtp_sasl_user_name'] = smtp_credentials['user_name'] +node.override['postfix']['sasl']['smtp_sasl_passwd'] = smtp_credentials['password'] +node.override['postfix']['sasl_password_file'] = "#{node['postfix']['conf_dir']}/sasl_passwd" +# Postfix doesn't support smtps relayhost, use STARTSSL instead +node.override['postfix']['main']['relayhost'] = smtp_credentials['relayhost'] +node.override['postfix']['main']['smtp_sasl_auth_enable'] = 'yes' +node.override['postfix']['main']['smtp_sasl_password_maps'] = "hash:#{node['postfix']['sasl_password_file']}" +node.override['postfix']['main']['smtp_sasl_security_options'] = 'noanonymous' +node.override['postfix']['main']['smtp_tls_CAfile'] = '/etc/ssl/certs/ca-certificates.crt' +node.override['postfix']['main']['smtpd_tls_CAfile'] = '/etc/ssl/certs/ca-certificates.crt' + +include_recipe 'postfix::default' diff --git a/site-cookbooks/kosmos-redis/README.md b/site-cookbooks/kosmos-redis/README.md new file mode 100644 index 0000000..4b76bdb --- /dev/null +++ b/site-cookbooks/kosmos-redis/README.md @@ -0,0 +1,4 @@ +kosmos-redis Cookbook +===================== + +redis wrapper cookbook diff --git a/site-cookbooks/kosmos-redis/metadata.rb b/site-cookbooks/kosmos-redis/metadata.rb new file mode 100644 index 0000000..f596e4c --- /dev/null +++ b/site-cookbooks/kosmos-redis/metadata.rb @@ -0,0 +1,9 @@ +name 'kosmos-redis' +maintainer 'Kosmos' +maintainer_email 'mail@kosmos.org' +license 'All rights reserved' +description 'redis wrapper cookbook' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '0.1.0' + +depends 'redis' diff --git a/site-cookbooks/kosmos-redis/recipes/default.rb b/site-cookbooks/kosmos-redis/recipes/default.rb new file mode 100644 index 0000000..b264353 --- /dev/null +++ b/site-cookbooks/kosmos-redis/recipes/default.rb @@ -0,0 +1,11 @@ +# +# Cookbook Name:: kosmos-redis +# Recipe:: default +# +# Copyright 2015, Kosmos +# +# All rights reserved - Do Not Redistribute +# + +node.override['redis']['unixsocket'] = '' +include_recipe 'redis::server' diff --git a/site-cookbooks/sockethub/CHANGELOG.md b/site-cookbooks/sockethub/CHANGELOG.md new file mode 100644 index 0000000..c02f029 --- /dev/null +++ b/site-cookbooks/sockethub/CHANGELOG.md @@ -0,0 +1,13 @@ +sockethub CHANGELOG +=================== + +This file is used to list changes made in each version of the sockethub cookbook. + +0.1.0 +----- +- [your_name] - Initial release of sockethub + +- - - +Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown. + +The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown. diff --git a/site-cookbooks/sockethub/README.md b/site-cookbooks/sockethub/README.md new file mode 100644 index 0000000..f171323 --- /dev/null +++ b/site-cookbooks/sockethub/README.md @@ -0,0 +1,68 @@ +sockethub Cookbook +================== +TODO: Enter the cookbook description here. + +e.g. +This cookbook makes your favorite breakfast sandwich. + +Requirements +------------ +TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc. + +e.g. +#### packages +- `toaster` - sockethub needs toaster to brown your bagel. + +Attributes +---------- +TODO: List your cookbook attributes here. + +e.g. +#### sockethub::default + + + + + + + + + + + + + +
    KeyTypeDescriptionDefault
    ['sockethub']['bacon']Booleanwhether to include bacontrue
    + +Usage +----- +#### sockethub::default +TODO: Write usage instructions for each cookbook. + +e.g. +Just include `sockethub` in your node's `run_list`: + +```json +{ + "name":"my_node", + "run_list": [ + "recipe[sockethub]" + ] +} +``` + +Contributing +------------ +TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section. + +e.g. +1. Fork the repository on Github +2. Create a named feature branch (like `add_component_x`) +3. Write your change +4. Write tests for your change (if applicable) +5. Run the tests, ensuring they all pass +6. Submit a Pull Request using Github + +License and Authors +------------------- +Authors: TODO: List authors diff --git a/site-cookbooks/sockethub/attributes/default.rb b/site-cookbooks/sockethub/attributes/default.rb new file mode 100644 index 0000000..e55cd92 --- /dev/null +++ b/site-cookbooks/sockethub/attributes/default.rb @@ -0,0 +1,2 @@ +node.default['sockethub']['port'] = '10551' +node.default['sockethub']['external_port'] = '10550' diff --git a/site-cookbooks/sockethub/metadata.rb b/site-cookbooks/sockethub/metadata.rb new file mode 100644 index 0000000..f574a74 --- /dev/null +++ b/site-cookbooks/sockethub/metadata.rb @@ -0,0 +1,14 @@ +name 'sockethub' +maintainer 'Kosmos' +maintainer_email 'mail@kosmos.org' +license 'All rights reserved' +description 'Installs/Configures sockethub' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '0.1.0' + +depends 'application' +depends 'application_nodejs' +depends 'kosmos-redis' +depends 'kosmos-nodejs' +depends 'kosmos-nginx' +depends 'firewall' diff --git a/site-cookbooks/sockethub/recipes/default.rb b/site-cookbooks/sockethub/recipes/default.rb new file mode 100644 index 0000000..11945a6 --- /dev/null +++ b/site-cookbooks/sockethub/recipes/default.rb @@ -0,0 +1,32 @@ +# +# Cookbook Name:: sockethub +# Recipe:: default +# +# Copyright 2015, Kosmos +# +# All rights reserved - Do Not Redistribute +# + +include_recipe 'kosmos-nodejs' + +include_recipe 'kosmos-redis' + +application "sockethub" do + path "/srv/sockethub" + owner "www-data" + group "www-data" + + action :deploy + + repository 'https://github.com/sockethub/sockethub.git' + revision 'experimental_v1_0' + + nodejs do + entry_point '/srv/sockethub/current/bin/sockethub' + # Use our own systemd service that depends on redis-server + template 'nodejs.systemd.service.erb' + environment 'DEBUG' => '*', + 'PORT' => node['sockethub']['port'] + end +end + diff --git a/site-cookbooks/sockethub/recipes/proxy.rb b/site-cookbooks/sockethub/recipes/proxy.rb new file mode 100644 index 0000000..34faa42 --- /dev/null +++ b/site-cookbooks/sockethub/recipes/proxy.rb @@ -0,0 +1,50 @@ +# +# Cookbook Name:: sockethub +# Recipe:: proxy +# +# Copyright 2015, Kosmos +# +# All rights reserved - Do Not Redistribute +# + +firewall_rule 'sockethub' do + port node['sockethub']['external_port'].to_i + protocol :tcp + action :allow +end + +include_recipe 'kosmos-nginx' + +data_bag_item = Chef::EncryptedDataBagItem.load('certificates', 'wildcard_kosmos_org') + +ssl_cert_path = "/etc/ssl/private/wildcard.kosmos.org.crt" +file ssl_cert_path do + content data_bag_item['ssl_cert'] + mode 0600 + owner 'www-data' + sensitive true +end + +ssl_key_path = "/etc/ssl/private/wildcard.kosmos.org.key" +file ssl_key_path do + content data_bag_item['ssl_key'] + mode 0600 + owner 'www-data' + sensitive true +end + +template "#{node['nginx']['dir']}/sites-available/sockethub" do + source 'nginx_conf_sockethub.erb' + owner 'www-data' + mode 0640 + variables sockethub_port: node['sockethub']['port'], + sockethub_external_port: node['sockethub']['external_port'], + server_name: 'sockethub.kosmos.org', + ssl_cert: ssl_cert_path, + ssl_key: ssl_key_path + notifies :reload, 'service[nginx]', :delayed +end + +nginx_site 'sockethub' do + enable true +end diff --git a/site-cookbooks/sockethub/templates/default/nginx_conf_sockethub.erb b/site-cookbooks/sockethub/templates/default/nginx_conf_sockethub.erb new file mode 100644 index 0000000..df4d2eb --- /dev/null +++ b/site-cookbooks/sockethub/templates/default/nginx_conf_sockethub.erb @@ -0,0 +1,55 @@ +# Generated by Chef +upstream _sockethub { + server localhost:<%= @sockethub_port %>; +} + +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +server { + listen <%= @sockethub_external_port %> ssl spdy; + add_header Strict-Transport-Security "max-age=15768000"; + + server_name <%= @server_name %>; + + access_log <%= node[:nginx][:log_dir] %>/sockethub.access.log json; + error_log <%= node[:nginx][:log_dir] %>/sockethub.error.log warn; + + # We might need real ETags, disable those for now + gzip off; + + location / { + # an HTTP header important enough to have its own Wikipedia entry: + # http://en.wikipedia.org/wiki/X-Forwarded-For + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # enable this if and only if you use HTTPS, this helps Rack + # set the proper protocol for doing redirects: + proxy_set_header X-Forwarded-Proto https; + + # pass the Host: header from the client right along so redirects + # can be set properly within the Rack application + proxy_set_header Host $http_host; + + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + + # Increase number of buffers. Default is 8 + proxy_buffers 1024 8k; + + proxy_pass http://_sockethub; + proxy_http_version 1.1; + # Enable WebSockets + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # CORS + add_header 'Access-Control-Allow-Origin' '*'; + } + + ssl_certificate <%= @ssl_cert %>; + ssl_certificate_key <%= @ssl_key %>; +} diff --git a/site-cookbooks/sockethub/templates/default/nodejs.systemd.service.erb b/site-cookbooks/sockethub/templates/default/nodejs.systemd.service.erb new file mode 100644 index 0000000..2c42623 --- /dev/null +++ b/site-cookbooks/sockethub/templates/default/nodejs.systemd.service.erb @@ -0,0 +1,16 @@ +[Unit] +Description=Start nodejs app +Requires=redis-server.service +After=redis-server.service + +[Service] +ExecStart=<%= @entry %> +User=<%= @user %> +Group=<%= @group %> +<% unless @environment.empty? -%> +Environment=<% @environment.each do |key, value| -%>'<%= key %>=<%= value %>' <% end %> +<% end -%> +Restart=always + +[Install] +WantedBy=multi-user.target